[med-svn] [beads] 01/02: New upstream version 1.1.13

Andreas Tille tille at debian.org
Thu Oct 19 14:06:44 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository beads.

commit bb583e91255d45ff437e2d7df1257689374e5621
Author: Andreas Tille <tille at debian.org>
Date:   Thu Oct 19 16:05:59 2017 +0200

    New upstream version 1.1.13
---
 AUTHORS                                |     2 +
 CMakeLists.txt                         |   197 +
 COPYING                                |   505 +
 INSTALL                                |    13 +
 README                                 |    15 +
 cmake_modules/FindMagick.cmake         |    48 +
 cmake_modules/FindPTHREADSConfig.cmake |    27 +
 cmake_modules/FindPthreads.cmake       |    98 +
 debian/changelog                       |   153 +
 debian/compat                          |     1 +
 debian/control                         |    15 +
 debian/copyright                       |     9 +
 debian/rules                           |    91 +
 doc/beads.html                         |    88 +
 doc/man1/beads.1                       |    41 +
 share/applications/beads.desktop       |    11 +
 share/beads/beads_blue.conf            |    51 +
 share/beads/beads_icon.svg             |    75 +
 share/beads/beads_silver.conf          |    51 +
 share/beads/beads_silver_small.conf    |    51 +
 src/CImg.h                             | 39604 +++++++++++++++++++++++++++++++
 src/CMakeLists.txt                     |    68 +
 src/ConfigFile/AntBlueMaize.jpg        |   Bin 0 -> 6533 bytes
 src/ConfigFile/ArrowHome.gif           |   Bin 0 -> 187 bytes
 src/ConfigFile/ConfigFile.cpp          |   142 +
 src/ConfigFile/ConfigFile.h            |   253 +
 src/ConfigFile/ConfigFile.html         |    72 +
 src/ConfigFile/README                  |    57 +
 src/ConfigFile/example.inp             |    14 +
 src/ConfigFile/main.css                |    37 +
 src/ConfigFile/test.inp                |    83 +
 src/beads.cpp                          |   615 +
 src/beads.h                            |    43 +
 src/config.h                           |    21 +
 src/config.h.cmake                     |    21 +
 src/detection.cpp                      |   304 +
 src/detection.h                        |    67 +
 src/encode/base64.cpp                  |   105 +
 src/encode/base64.h                    |    23 +
 src/images/imageCode.h                 |    34 +
 src/images/imageConfluent.cpp          |   126 +
 src/images/imageConfluent.h            |    26 +
 src/images/imageContours.cpp           |   113 +
 src/images/imageContours.h             |    28 +
 src/images/imageCoule.cpp              |    40 +
 src/images/imageCoule.h                |    28 +
 src/images/imageDeNovo.cpp             |   150 +
 src/images/imageDeNovo.h               |    22 +
 src/images/imageDetection.cpp          |    14 +
 src/images/imageDetection.h            |    23 +
 src/images/imageDirect.cpp             |   171 +
 src/images/imageDirect.h               |    62 +
 src/images/imageIntensity.cpp          |   119 +
 src/images/imageIntensity.h            |    60 +
 src/images/imageNumber.cpp             |   525 +
 src/images/imageNumber.h               |    43 +
 src/images/imagePaths.cpp              |    38 +
 src/images/imagePaths.h                |    29 +
 src/images/imageProb.cpp               |   167 +
 src/images/imageProb.h                 |    34 +
 src/images/parameterConfluent.cpp      |    18 +
 src/images/parameterConfluent.h        |    72 +
 src/images/parameterDirect.h           |    30 +
 src/images/parameterNumber.cpp         |    13 +
 src/images/parameterNumber.h           |    45 +
 src/images/parameterProb.cpp           |    49 +
 src/images/parameterProb.h             |    66 +
 src/parameterDetection.cpp             |    12 +
 src/parameterDetection.h               |    27 +
 src/parameters.cpp                     |    62 +
 src/parameters.h                       |    36 +
 src/qtbeads/CMakeLists.txt             |   124 +
 src/qtbeads/QCimg.cpp                  |    52 +
 src/qtbeads/QCimg.h                    |    22 +
 src/qtbeads/beads_results.cpp          |   113 +
 src/qtbeads/beads_results.h            |    45 +
 src/qtbeads/main_window.cpp            |   612 +
 src/qtbeads/main_window.h              |    98 +
 src/qtbeads/properties.cpp             |   227 +
 src/qtbeads/properties.h               |    48 +
 src/qtbeads/q_gel_image.cpp            |    66 +
 src/qtbeads/q_gel_image.h              |    52 +
 src/qtbeads/q_gel_image_scroll.cpp     |    46 +
 src/qtbeads/q_gel_image_scroll.h       |    36 +
 src/qtbeads/qtbeads.cpp                |    14 +
 src/qtbeads/qtbeads_error.h            |    35 +
 src/qtbeads/translations/beads_fr.qm   |   Bin 0 -> 453 bytes
 src/qtbeads/translations/beads_fr.ts   |   146 +
 src/spot.cpp                           |   307 +
 src/spot.h                             |   153 +
 src/spotDocument.cpp                   |    81 +
 src/spotDocument.h                     |    40 +
 src/spotPROTICdbDocument.cpp           |   225 +
 src/spotPROTICdbDocument.h             |    28 +
 src/spotSvgDocument.cpp                |   380 +
 src/spotSvgDocument.h                  |    69 +
 src/spot_document_gnumeric.cpp         |   412 +
 src/spot_document_gnumeric.h           |    32 +
 98 files changed, 48816 insertions(+)

diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..960e2a0
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,2 @@
+Michel Zivy <michel.zivy at moulon.inra.fr>
+http://moulon.inra.fr/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e089185
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,197 @@
+
+# cmake helps to compile "beads"
+# please install the cmake package depending on your linux distribution
+# and type in the beads root directory :
+# cmake .
+# make
+# make install
+cmake_minimum_required(VERSION 2.6)
+
+
+# The name of our project is "beads".  CMakeLists files in this project can
+# refer to the root source directory of the project as ${beads_SOURCE_DIR} and
+# to the root binary directory of the project as ${beads_BINARY_DIR}.
+PROJECT(beads CXX C)
+
+
+#message ("compiler : ${CMAKE_CXX_COMPILER} ")
+
+
+IF ( CMAKE_BASE_NAME MATCHES "cl")
+	message ("on est sous Windows")
+	SET( WIN32 "true")
+ENDIF(CMAKE_BASE_NAME MATCHES "cl")
+
+
+SET( CMAKE_BUILD_TYPE "Release")
+if (NOT CMAKE_INSTALL_PREFIX)
+	SET (CMAKE_INSTALL_PREFIX /usr/local)
+ENDIF(NOT CMAKE_INSTALL_PREFIX)
+
+IF (CMAKE_INSTALL_PREFIX MATCHES "/usr/local")
+	SET( CMAKE_BUILD_TYPE "Debug")
+	#SET( CMAKE_BUILD_TYPE "Release")
+ENDIF (CMAKE_INSTALL_PREFIX MATCHES "/usr/local")
+
+
+IF ( CMAKE_BUILD_TYPE MATCHES "Release")
+	MESSAGE("compiling as release version")
+	ADD_DEFINITIONS("-DQT_NO_DEBUG_OUTPUT")
+ELSE ( CMAKE_BUILD_TYPE MATCHES "Release" )
+	MESSAGE("compiling as debug version")
+ENDIF( CMAKE_BUILD_TYPE MATCHES "Release" )
+
+
+
+#SET(WIN32 "true")
+#SET(MINGW "true")
+
+set(CMAKE_MODULE_PATH ${beads_SOURCE_DIR}/cmake_modules)
+#FIND_PACKAGE ( FindPTHREADS REQUIRED )
+#are we using pthreads ?
+#IF ( CMAKE_USE_PTHREADS_INIT )
+#	INCLUDE_DIRECTORIES ( ${EXPAT_INCLUDE_DIRS} )
+#ELSE ( CMAKE_USE_PTHREADS_INIT )
+#	MESSAGE( FATAL_ERROR "No pthread installed" )
+#ENDIF( CMAKE_USE_PTHREADS_INIT )
+
+#IF ( PTHREADS_FOUND )
+	INCLUDE_DIRECTORIES ( ${PTHREADS_INCLUDE_DIR} )
+#ELSE ( PTHREADS_FOUND )
+#	MESSAGE( FATAL_ERROR "No pthread installed" )
+#ENDIF( PTHREADS_FOUND )
+
+
+cmake_minimum_required(VERSION 2.6)
+
+
+SET ( EXTRA_CIMG_LIBRARY "")
+
+SET ( CIMG_OS 0)
+IF(WIN32)
+	SET ( CIMG_OS 2)
+	#-O2 -lgdi32
+	#SET ( EXTRA_CIMG_LIBRARY "gdi32")
+	#SET(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG")
+#	FIND_PACKAGE(FindMagick REQUIRED)
+ELSE(WIN32)
+	FIND_PACKAGE(Pthreads REQUIRED)
+	SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
+	SET ( CIMG_OS 1)
+ENDIF(WIN32)
+
+#
+#CMAKE_CXX_FLAGS
+SET(CMAKE_CXX_FLAGS_DEBUG "-g")
+SET(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
+SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
+
+SET (BEADS_VERSION "1.1.13")
+
+SET(CPACK_CMAKE_GENERATOR "Unix Makefiles")
+SET(CPACK_GENERATOR "STGZ;TGZ;TZ")
+#SET(CPACK_INSTALL_CMAKE_PROJECTS "/tmp;beads;src;/src")
+#SET(CPACK_NSIS_DISPLAY_NAME "CMake 2.5")
+SET(CPACK_OUTPUT_CONFIG_FILE "./CPackConfig.cmake")
+SET(CPACK_PACKAGE_DESCRIPTION_FILE ${beads_SOURCE_DIR}/COPYING)
+SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "beads is a detection software")
+SET(CPACK_PACKAGE_EXECUTABLES "beads")
+SET(CPACK_SOURCE_PACKAGE_FILE_NAME "beads-${BEADS_VERSION}")
+SET(CPACK_PACKAGE_FILE_NAME "beads-${BEADS_VERSION}-Linux-i686")
+SET(CPACK_PACKAGE_INSTALL_DIRECTORY "beads ${BEADS_VERSION}")
+SET(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "beads ${BEADS_VERSION}")
+SET(CPACK_PACKAGE_NAME "beads")
+SET(CPACK_PACKAGE_VENDOR "CNRS")
+
+IF ( CMAKE_BUILD_TYPE MATCHES "Debug")
+	SET (BEADS_VERSION "${BEADS_VERSION}d")
+ENDIF( CMAKE_BUILD_TYPE MATCHES "Debug")
+SET(CPACK_PACKAGE_VERSION ${BEADS_VERSION})
+SET(CPACK_PACKAGE_VERSION_MAJOR "1")
+SET(CPACK_PACKAGE_VERSION_MINOR "1")
+SET(CPACK_PACKAGE_VERSION_PATCH "13")
+SET(CPACK_RESOURCE_FILE_LICENSE ${beads_SOURCE_DIR}/COPYING)
+SET(CPACK_RESOURCE_FILE_README ${beads_SOURCE_DIR}/README)
+SET(CPACK_RESOURCE_FILE_WELCOME ${beads_SOURCE_DIR}/INSTALL)
+SET(CPACK_SOURCE_GENERATOR "TGZ;TZ")
+SET(CPACK_SOURCE_OUTPUT_CONFIG_FILE "./CPackSourceConfig.cmake")
+SET(CPACK_SOURCE_STRIP_FILES "")
+SET(CPACK_STRIP_FILES "bin/beads")
+SET(CPACK_SYSTEM_NAME "Linux-i686")
+SET(CPACK_TOPLEVEL_TAG "Linux-i686")
+
+SET(CPACK_SOURCE_IGNORE_FILES 
+	"moc_.*cxx"
+	"/devel_archives/"
+	"/figures/"
+	"/Soumis/"
+	"/samples/"
+	"/win32/"
+	"/tests/"
+	"Makefile"
+	"install_manifest.txt"
+	"cmake_install.cmake"
+	"cmake_install.cmake"
+	"CMakeCache.txt"
+	"CPackConfig.cmake"
+	"CPackSourceConfig.cmake"
+	"install_manifest.txt"
+	"/CMakeFiles/"
+	"/_CPack_Packages/"
+	"/Debug/"
+	"/Release/"
+	"/\\\\.externalToolBuilders/"
+	"/\\\\.svn/"
+	"/\\\\.settings/"
+	"Makefile"
+	"\\\\.cdtbuild"
+	"\\\\.cdtproject"
+	"\\\\.project"
+	"\\\\.cproject"
+	"beads$"
+	".*\\\\.tar\\\\.gz"
+)
+# cpack -G TGZ --config CPackSourceConfig.cmake
+# dpkg-buildpackage -rfakeroot
+
+#.dput.cf
+#[olivier-langella]
+#fqdn = ppa.launchpad.net
+#method = ftp
+#incoming = ~olivier-langella/ubuntu/
+#login = olivier-langella
+#allow_unsigned_uploads = 0
+#
+#  debuild -S -sa
+# dput -f olivier-langella *changes
+
+configure_file (${beads_SOURCE_DIR}/src/config.h.cmake ${beads_SOURCE_DIR}/src/config.h)
+
+
+SET(CPACK_PACKAGE_EXECUTABLES "beads" "beads")
+
+
+INCLUDE(CPack)
+
+
+#INCLUDE(CPack)
+
+
+SET(CPACK_STRIP_FILES "src/beads;src/qtbeads/qtbeads")
+
+
+# Recurse into the "src" subdirectories.  This does not actually
+# cause another cmake executable to run.  The same process will walk through
+# the project's entire directory structure.
+SUBDIRS (src)
+
+INSTALL(PROGRAMS src/beads DESTINATION bin)
+INSTALL(PROGRAMS src/qtbeads/qtbeads DESTINATION bin)
+#INSTALL(DIRECTORY doc/man1 DESTINATION share/man/man1)
+INSTALL(FILES doc/man1/beads.1 DESTINATION share/man/man1)
+INSTALL(FILES share/applications/beads.desktop DESTINATION share/applications)
+INSTALL(FILES share/beads/beads_blue.conf DESTINATION share/beads)
+INSTALL(FILES share/beads/beads_silver.conf DESTINATION share/beads)
+INSTALL(FILES share/beads/beads_silver_small.conf DESTINATION share/beads)
+INSTALL(FILES share/beads/beads_icon.svg DESTINATION share/beads)
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..0fbbf18
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,505 @@
+CeCILL FREE SOFTWARE LICENSE AGREEMENT
+
+
+    Notice
+
+This Agreement is a Free Software license agreement that is the result
+of discussions between its authors in order to ensure compliance with
+the two main principles guiding its drafting:
+
+    * firstly, compliance with the principles governing the distribution
+      of Free Software: access to source code, broad rights granted to
+      users,
+    * secondly, the election of a governing law, French law, with which
+      it is conformant, both as regards the law of torts and
+      intellectual property law, and the protection that it offers to
+      both authors and holders of the economic rights over software.
+
+The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre])
+license are:
+
+Commissariat à l'Energie Atomique - CEA, a public scientific, technical
+and industrial research establishment, having its principal place of
+business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France.
+
+Centre National de la Recherche Scientifique - CNRS, a public scientific
+and technological establishment, having its principal place of business
+at 3 rue Michel-Ange, 75794 Paris cedex 16, France.
+
+Institut National de Recherche en Informatique et en Automatique -
+INRIA, a public scientific and technological establishment, having its
+principal place of business at Domaine de Voluceau, Rocquencourt, BP
+105, 78153 Le Chesnay cedex, France.
+
+
+    Preamble
+
+The purpose of this Free Software license agreement is to grant users
+the right to modify and redistribute the software governed by this
+license within the framework of an open source distribution model.
+
+The exercising of these rights is conditional upon certain obligations
+for users so as to preserve this status for all subsequent redistributions.
+
+In consideration of access to the source code and the 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 only have limited liability.
+
+In this respect, the risks associated with loading, using, modifying
+and/or developing or reproducing the software by the user are brought to
+the user's attention, given its Free Software status, which may make it
+complicated to use, with the result that its use is reserved for
+developers and experienced professionals having in-depth computer
+knowledge. Users are therefore encouraged to load and test the
+suitability of the software 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 of
+security. This Agreement may be freely reproduced and published,
+provided it is not altered, and that no provisions are either added or
+removed herefrom.
+
+This Agreement may apply to any or all software for which the holder of
+the economic rights decides to submit the use thereof to its provisions.
+
+
+    Article 1 - DEFINITIONS
+
+For the purpose of this Agreement, when the following expressions
+commence with a capital letter, they shall have the following meaning:
+
+Agreement: means this license agreement, and its possible subsequent
+versions and annexes.
+
+Software: means the software in its Object Code and/or Source Code form
+and, where applicable, its documentation, "as is" when the Licensee
+accepts the Agreement.
+
+Initial Software: means the Software in its Source Code and possibly its
+Object Code form and, where applicable, its documentation, "as is" when
+it is first distributed under the terms and conditions of the Agreement.
+
+Modified Software: means the Software modified by at least one
+Contribution.
+
+Source Code: means all the Software's instructions and program lines to
+which access is required so as to modify the Software.
+
+Object Code: means the binary files originating from the compilation of
+the Source Code.
+
+Holder: means the holder(s) of the economic rights over the Initial
+Software.
+
+Licensee: means the Software user(s) having accepted the Agreement.
+
+Contributor: means a Licensee having made at least one Contribution.
+
+Licensor: means the Holder, or any other individual or legal entity, who
+distributes the Software under the Agreement.
+
+Contribution: means any or all modifications, corrections, translations,
+adaptations and/or new functions integrated into the Software by any or
+all Contributors, as well as any or all Internal Modules.
+
+Module: means a set of sources files including their documentation that
+enables supplementary functions or services in addition to those offered
+by the Software.
+
+External Module: means any or all Modules, not derived from the
+Software, so that this Module and the Software run in separate address
+spaces, with one calling the other when they are run.
+
+Internal Module: means any or all Module, connected to the Software so
+that they both execute in the same address space.
+
+GNU GPL: means the GNU General Public License version 2 or any
+subsequent version, as published by the Free Software Foundation Inc.
+
+Parties: mean both the Licensee and the Licensor.
+
+These expressions may be used both in singular and plural form.
+
+
+    Article 2 - PURPOSE
+
+The purpose of the Agreement is the grant by the Licensor to the
+Licensee of a non-exclusive, transferable and worldwide license for the
+Software as set forth in Article 5 hereinafter for the whole term of the
+protection granted by the rights over said Software. 
+
+
+    Article 3 - ACCEPTANCE
+
+3.1 The Licensee shall be deemed as having accepted the terms and
+conditions of this Agreement upon the occurrence of the first of the
+following events:
+
+    * (i) loading the Software by any or all means, notably, by
+      downloading from a remote server, or by loading from a physical
+      medium;
+    * (ii) the first time the Licensee exercises any of the rights
+      granted hereunder.
+
+3.2 One copy of the Agreement, containing a notice relating to the
+characteristics of the Software, to the limited warranty, and to the
+fact that its use is restricted to experienced users has been provided
+to the Licensee prior to its acceptance as set forth in Article 3.1
+hereinabove, and the Licensee hereby acknowledges that it has read and
+understood it.
+
+
+    Article 4 - EFFECTIVE DATE AND TERM
+
+
+      4.1 EFFECTIVE DATE
+
+The Agreement shall become effective on the date when it is accepted by
+the Licensee as set forth in Article 3.1.
+
+
+      4.2 TERM
+
+The Agreement shall remain in force for the entire legal term of
+protection of the economic rights over the Software.
+
+
+    Article 5 - SCOPE OF RIGHTS GRANTED
+
+The Licensor hereby grants to the Licensee, who accepts, the following
+rights over the Software for any or all use, and for the term of the
+Agreement, on the basis of the terms and conditions set forth hereinafter.
+
+Besides, if the Licensor owns or comes to own one or more patents
+protecting all or part of the functions of the Software or of its
+components, the Licensor undertakes not to enforce the rights granted by
+these patents against successive Licensees using, exploiting or
+modifying the Software. If these patents are transferred, the Licensor
+undertakes to have the transferees subscribe to the obligations set
+forth in this paragraph.
+
+
+      5.1 RIGHT OF USE
+
+The Licensee is authorized to use the Software, without any limitation
+as to its fields of application, with it being hereinafter specified
+that this comprises:
+
+   1. permanent or temporary reproduction of all or part of the Software
+      by any or all means and in any or all form.
+
+   2. loading, displaying, running, or storing the Software on any or
+      all medium.
+
+   3. entitlement to observe, study or test its operation so as to
+      determine the ideas and principles behind any or all constituent
+      elements of said Software. This shall apply when the Licensee
+      carries out any or all loading, displaying, running, transmission
+      or storage operation as regards the Software, that it is entitled
+      to carry out hereunder.
+
+
+      5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS
+
+The right to make Contributions includes the right to translate, adapt,
+arrange, or make any or all modifications to the Software, and the right
+to reproduce the resulting software.
+
+The Licensee is authorized to make any or all Contributions to the
+Software provided that it includes an explicit notice that it is the
+author of said Contribution and indicates the date of the creation thereof.
+
+
+      5.3 RIGHT OF DISTRIBUTION
+
+In particular, the right of distribution includes the right to publish,
+transmit and communicate the Software to the general public on any or
+all medium, and by any or all means, and the right to market, either in
+consideration of a fee, or free of charge, one or more copies of the
+Software by any means.
+
+The Licensee is further authorized to distribute copies of the modified
+or unmodified Software to third parties according to the terms and
+conditions set forth hereinafter.
+
+
+        5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION
+
+The Licensee is authorized to distribute true copies of the Software in
+Source Code or Object Code form, provided that said distribution
+complies with all the provisions of the Agreement and is accompanied by:
+
+   1. a copy of the Agreement,
+
+   2. a notice relating to the limitation of both the Licensor's
+      warranty and liability as set forth in Articles 8 and 9,
+
+and that, in the event that only the Object Code of the Software is
+redistributed, the Licensee allows future Licensees unhindered access to
+the full Source Code of the Software by indicating how to access it, it
+being understood that the additional cost of acquiring the Source Code
+shall not exceed the cost of transferring the data.
+
+
+        5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE
+
+When the Licensee makes a Contribution to the Software, the terms and
+conditions for the distribution of the resulting Modified Software
+become subject to all the provisions of this Agreement.
+
+The Licensee is authorized to distribute the Modified Software, in
+source code or object code form, provided that said distribution
+complies with all the provisions of the Agreement and is accompanied by:
+
+   1. a copy of the Agreement,
+
+   2. a notice relating to the limitation of both the Licensor's
+      warranty and liability as set forth in Articles 8 and 9,
+
+and that, in the event that only the object code of the Modified
+Software is redistributed, the Licensee allows future Licensees
+unhindered access to the full source code of the Modified Software by
+indicating how to access it, it being understood that the additional
+cost of acquiring the source code shall not exceed the cost of
+transferring the data.
+
+
+        5.3.3 DISTRIBUTION OF EXTERNAL MODULES
+
+When the Licensee has developed an External Module, the terms and
+conditions of this Agreement do not apply to said External Module, that
+may be distributed under a separate license agreement.
+
+
+        5.3.4 COMPATIBILITY WITH THE GNU GPL
+
+The Licensee can include a code that is subject to the provisions of one
+of the versions of the GNU GPL in the Modified or unmodified Software,
+and distribute that entire code under the terms of the same version of
+the GNU GPL.
+
+The Licensee can include the Modified or unmodified Software in a code
+that is subject to the provisions of one of the versions of the GNU GPL,
+and distribute that entire code under the terms of the same version of
+the GNU GPL.
+
+
+    Article 6 - INTELLECTUAL PROPERTY
+
+
+      6.1 OVER THE INITIAL SOFTWARE
+
+The Holder owns the economic rights over the Initial Software. Any or
+all use of the Initial Software is subject to compliance with the terms
+and conditions under which the Holder has elected to distribute its work
+and no one shall be entitled to modify the terms and conditions for the
+distribution of said Initial Software.
+
+The Holder undertakes that the Initial Software will remain ruled at
+least by this Agreement, for the duration set forth in Article 4.2.
+
+
+      6.2 OVER THE CONTRIBUTIONS
+
+The Licensee who develops a Contribution is the owner of the
+intellectual property rights over this Contribution as defined by
+applicable law.
+
+
+      6.3 OVER THE EXTERNAL MODULES
+
+The Licensee who develops an External Module is the owner of the
+intellectual property rights over this External Module as defined by
+applicable law and is free to choose the type of agreement that shall
+govern its distribution.
+
+
+      6.4 JOINT PROVISIONS
+
+The Licensee expressly undertakes:
+
+   1. not to remove, or modify, in any manner, the intellectual property
+      notices attached to the Software;
+
+   2. to reproduce said notices, in an identical manner, in the copies
+      of the Software modified or not.
+
+The Licensee undertakes not to directly or indirectly infringe the
+intellectual property rights of the Holder and/or Contributors on the
+Software and to take, where applicable, vis-à-vis its staff, any and all
+measures required to ensure respect of said intellectual property rights
+of the Holder and/or Contributors.
+
+
+    Article 7 - RELATED SERVICES
+
+7.1 Under no circumstances shall the Agreement oblige the Licensor to
+provide technical assistance or maintenance services for the Software.
+
+However, the Licensor is entitled to offer this type of services. The
+terms and conditions of such technical assistance, and/or such
+maintenance, shall be set forth in a separate instrument. Only the
+Licensor offering said maintenance and/or technical assistance services
+shall incur liability therefor.
+
+7.2 Similarly, any Licensor is entitled to offer to its licensees, under
+its sole responsibility, a warranty, that shall only be binding upon
+itself, for the redistribution of the Software and/or the Modified
+Software, under terms and conditions that it is free to decide. Said
+warranty, and the financial terms and conditions of its application,
+shall be subject of a separate instrument executed between the Licensor
+and the Licensee.
+
+
+    Article 8 - LIABILITY
+
+8.1 Subject to the provisions of Article 8.2, the Licensee shall be
+entitled to claim compensation for any direct loss it may have suffered
+from the Software as a result of a fault on the part of the relevant
+Licensor, subject to providing evidence thereof.
+
+8.2 The Licensor's liability is limited to the commitments made under
+this Agreement and shall not be incurred as a result of in particular:
+(i) loss due the Licensee's total or partial failure to fulfill its
+obligations, (ii) direct or consequential loss that is suffered by the
+Licensee due to the use or performance of the Software, and (iii) more
+generally, any consequential loss. In particular the Parties expressly
+agree that any or all pecuniary or business loss (i.e. loss of data,
+loss of profits, operating loss, loss of customers or orders,
+opportunity cost, any disturbance to business activities) or any or all
+legal proceedings instituted against the Licensee by a third party,
+shall constitute consequential loss and shall not provide entitlement to
+any or all compensation from the Licensor.
+
+
+    Article 9 - WARRANTY
+
+9.1 The Licensee acknowledges that the scientific and technical
+state-of-the-art when the Software was distributed did not enable all
+possible uses to be tested and verified, nor for the presence of
+possible defects to be detected. In this respect, the Licensee's
+attention has been drawn to the risks associated with loading, using,
+modifying and/or developing and reproducing the Software which are
+reserved for experienced users.
+
+The Licensee shall be responsible for verifying, by any or all means,
+the suitability of the product for its requirements, its good working
+order, and for ensuring that it shall not cause damage to either persons
+or properties.
+
+9.2 The Licensor hereby represents, in good faith, that it is entitled
+to grant all the rights over the Software (including in particular the
+rights set forth in Article 5).
+
+9.3 The Licensee acknowledges that the Software is supplied "as is" by
+the Licensor without any other express or tacit warranty, other than
+that provided for in Article 9.2 and, in particular, without any warranty 
+as to its commercial value, its secured, safe, innovative or relevant
+nature.
+
+Specifically, the Licensor does not warrant that the Software is free
+from any error, that it will operate without interruption, that it will
+be compatible with the Licensee's own equipment and software
+configuration, nor that it will meet the Licensee's requirements.
+
+9.4 The Licensor does not either expressly or tacitly warrant that the
+Software does not infringe any third party intellectual property right
+relating to a patent, software or any other property right. Therefore,
+the Licensor disclaims any and all liability towards the Licensee
+arising out of any or all proceedings for infringement that may be
+instituted in respect of the use, modification and redistribution of the
+Software. Nevertheless, should such proceedings be instituted against
+the Licensee, the Licensor shall provide it with technical and legal
+assistance for its defense. Such technical and legal assistance shall be
+decided on a case-by-case basis between the relevant Licensor and the
+Licensee pursuant to a memorandum of understanding. The Licensor
+disclaims any and all liability as regards the Licensee's use of the
+name of the Software. No warranty is given as regards the existence of
+prior rights over the name of the Software or as regards the existence
+of a trademark.
+
+
+    Article 10 - TERMINATION
+
+10.1 In the event of a breach by the Licensee of its obligations
+hereunder, the Licensor may automatically terminate this Agreement
+thirty (30) days after notice has been sent to the Licensee and has
+remained ineffective.
+
+10.2 A Licensee whose Agreement is terminated shall no longer be
+authorized to use, modify or distribute the Software. However, any
+licenses that it may have granted prior to termination of the Agreement
+shall remain valid subject to their having been granted in compliance
+with the terms and conditions hereof.
+
+
+    Article 11 - MISCELLANEOUS
+
+
+      11.1 EXCUSABLE EVENTS
+
+Neither Party shall be liable for any or all delay, or failure to
+perform the Agreement, that may be attributable to an event of force
+majeure, an act of God or an outside cause, such as defective
+functioning or interruptions of the electricity or telecommunications
+networks, network paralysis following a virus attack, intervention by
+government authorities, natural disasters, water damage, earthquakes,
+fire, explosions, strikes and labor unrest, war, etc.
+
+11.2 Any failure by either Party, on one or more occasions, to invoke
+one or more of the provisions hereof, shall under no circumstances be
+interpreted as being a waiver by the interested Party of its right to
+invoke said provision(s) subsequently.
+
+11.3 The Agreement cancels and replaces any or all previous agreements,
+whether written or oral, between the Parties and having the same
+purpose, and constitutes the entirety of the agreement between said
+Parties concerning said purpose. No supplement or modification to the
+terms and conditions hereof shall be effective as between the Parties
+unless it is made in writing and signed by their duly authorized
+representatives.
+
+11.4 In the event that one or more of the provisions hereof were to
+conflict with a current or future applicable act or legislative text,
+said act or legislative text shall prevail, and the Parties shall make
+the necessary amendments so as to comply with said act or legislative
+text. All other provisions shall remain effective. Similarly, invalidity
+of a provision of the Agreement, for any reason whatsoever, shall not
+cause the Agreement as a whole to be invalid.
+
+
+      11.5 LANGUAGE
+
+The Agreement is drafted in both French and English and both versions
+are deemed authentic.
+
+
+    Article 12 - NEW VERSIONS OF THE AGREEMENT
+
+12.1 Any person is authorized to duplicate and distribute copies of this
+Agreement.
+
+12.2 So as to ensure coherence, the wording of this Agreement is
+protected and may only be modified by the authors of the License, who
+reserve the right to periodically publish updates or new versions of the
+Agreement, each with a separate number. These subsequent versions may
+address new issues encountered by Free Software.
+
+12.3 Any Software distributed under a given version of the Agreement may
+only be subsequently distributed under the same version of the Agreement
+or a subsequent version, subject to the provisions of Article 5.3.4.
+
+
+    Article 13 - GOVERNING LAW AND JURISDICTION
+
+13.1 The Agreement is governed by French law. The Parties agree to
+endeavor to seek an amicable solution to any disagreements or disputes
+that may arise during the performance of the Agreement.
+
+13.2 Failing an amicable solution within two (2) months as from their
+occurrence, and unless emergency proceedings are necessary, the
+disagreements or disputes shall be referred to the Paris Courts having
+jurisdiction, by the more diligent Party.
+
+
+Version 2.0 dated 2006-09-05.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..1a98b6a
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,13 @@
+
+requirements :
+on Ubuntu 7.10, before compilation, please install packages:
+libmagick++9-dev
+imagemagick
+build-essential
+cmake
+
+Compilation and installation :
+
+cmake .
+make
+make install
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..641be1e
--- /dev/null
+++ b/README
@@ -0,0 +1,15 @@
+#  Description : "beads" is a software to detect and quantify spots 
+#					on a 2-DE electrophoresis gel image 
+#
+#
+#  Copyright   : Michel Zivy
+#                ( http://moulon.inra.fr/ )
+#
+#  License     : CeCILL
+#
+#  This software is governed by the CeCILL 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
+#  license as circulated by CEA, CNRS and INRIA at the following URL
+#  "http://www.cecill.info".
+#	Please refer to "COPYING" file for the complete licence terms
diff --git a/cmake_modules/FindMagick.cmake b/cmake_modules/FindMagick.cmake
new file mode 100644
index 0000000..aa76b65
--- /dev/null
+++ b/cmake_modules/FindMagick.cmake
@@ -0,0 +1,48 @@
+# - Find Magick
+# This module finds if the Magick++ libraries and headers (distributed
+# with ImageMagick are installed.  If you are looking for the ImageMagick
+# programs please see FIND_PACKAGE(ImageMagick).
+#
+# This module sets the following variables:
+#
+# MAGICK_FOUND
+#    True if the Magick++ library was found
+# MAGICK_INCLUDE_DIR
+#    The include path of the Magick++.h header file
+# MAGICK_LIBRARY
+#    The location of the Magick library
+# MAGICK++_LIBRARY
+#    THe location of the Magick++ library
+#
+
+# Find the Magick++ include path
+FIND_PATH(MAGICK_INCLUDE_DIR Magick++.h
+    "[HKEY_LOCAL_MACHINE\\SOFTWARE\\ImageMagick\\Current;BinPath]/include"
+)
+
+FIND_LIBRARY(MAGICK_LIBRARY Magick CORE_RL_Magick_
+    PATHS 
+    "[HKEY_LOCAL_MACHINE\\SOFTWARE\\ImageMagick\\Current;BinPath]/lib"
+)
+
+FIND_LIBRARY(MAGICK++_LIBRARY Magick++ CORE_RL_Magick++_
+    PATHS
+    "[HKEY_LOCAL_MACHINE\\SOFTWARE\\ImageMagick\\Current;BinPath]/lib"
+)
+
+IF(MAGICK_INCLUDE_DIR AND MAGICK_LIBRARY AND MAGICK++_LIBRARY)
+    SET(MAGICK_FOUND true)
+    SET(MAGICK_INCLUDE_DIRS ${MAGICK_INCLUDE_DIR})
+    SET(MAGICK_LIBRARIES    ${MAGICK_LIBRARY} ${MAGICK++_LIBRARY})
+ENDIF(MAGICK_INCLUDE_DIR AND MAGICK_LIBRARY AND MAGICK++_LIBRARY)
+
+IF(MAGICK_FOUND)
+    IF(NOT MAGICK_FIND_QUIETLY)
+        MESSAGE(STATUS "Found Magick: ${MAGICK_LIBRARY}")
+        MESSAGE(STATUS "Found Magick++: ${MAGICK++_LIBRARY}")
+    ENDIF(NOT MAGICK_FIND_QUIETLY)
+ELSE(MAGICK_FOUND) 
+    IF(MAGICK_FIND_REQUIRED)
+        MESSAGE(FATAL_ERROR "Could not find the Magick library")
+    ENDIF(MAGICK_FIND_REQUIRED)
+ENDIF(MAGICK_FOUND)
diff --git a/cmake_modules/FindPTHREADSConfig.cmake b/cmake_modules/FindPTHREADSConfig.cmake
new file mode 100644
index 0000000..20491ba
--- /dev/null
+++ b/cmake_modules/FindPTHREADSConfig.cmake
@@ -0,0 +1,27 @@
+# The following variables are set
+#  CMAKE_THREAD_LIBS_INIT     - the thread library
+#  CMAKE_USE_SPROC_INIT       - are we using sproc?
+#  CMAKE_USE_WIN32_THREADS_INIT - using WIN32 threads?
+#  CMAKE_USE_PTHREADS_INIT    - are we using pthreads
+#  CMAKE_HP_PTHREADS_INIT     - are we using hp pthreads
+
+IF( WIN32 )
+	FIND_PACKAGE(Threads REQUIRED)
+	
+	
+	#FIND_PATH(EXPAT_INCLUDE_DIR NAMES pthread.h /MinGW/include)
+	#MARK_AS_ADVANCED(EXPAT_INCLUDE_DIR)
+	#SET (EXPAT_INCLUDE_DIRS /MinGW/include)
+	# Look for the library.
+	
+	
+	#FIND_LIBRARY(CMAKE_THREAD_LIBS_INIT NAMES pthreadGCE2 PATHS /MinGW/lib)
+	#SET(CMAKE_USE_PTHREADS_INIT 1)
+	#MARK_AS_ADVANCED(CMAKE_USE_PTHREADS_INIT)
+	
+	
+	#ADD_LIBRARY(pthreadGCE2 STATIC)
+ELSE (WIN32)
+	FIND_PACKAGE(Threads REQUIRED)
+	#ADD_LIBRARY(pthread SHARED ${CMAKE_THREAD_LIBS_INIT})
+ENDIF( WIN32 )
\ No newline at end of file
diff --git a/cmake_modules/FindPthreads.cmake b/cmake_modules/FindPthreads.cmake
new file mode 100644
index 0000000..ce591df
--- /dev/null
+++ b/cmake_modules/FindPthreads.cmake
@@ -0,0 +1,98 @@
+# - Find the Pthreads library
+# This module searches for the Pthreads library (including the
+# pthreads-win32 port).
+#
+# This module defines these variables:
+#
+#  PTHREADS_FOUND
+#      True if the Pthreads library was found
+#  PTHREADS_LIBRARY
+#      The location of the Pthreads library
+#  PTHREADS_INCLUDE_DIR
+#      The include path of the Pthreads library
+#  PTHREADS_DEFINITIONS
+#      Preprocessor definitions to define
+#
+# This module responds to the PTHREADS_EXCEPTION_SCHEME
+# variable on Win32 to allow the user to control the
+# library linked against.  The Pthreads-win32 port
+# provides the ability to link against a version of the
+# library with exception handling.  IT IS NOT RECOMMENDED
+# THAT YOU USE THIS because most POSIX thread implementations
+# do not support stack unwinding.
+#
+#  PTHREADS_EXCEPTION_SCHEME
+#      C  = no exceptions (default)
+#         (NOTE: This is the default scheme on most POSIX thread
+#          implementations and what you should probably be using)
+#      CE = C++ Exception Handling
+#      SE = Structure Exception Handling (MSVC only)
+#
+
+#
+# Define a default exception scheme to link against
+# and validate user choice.
+#
+IF(NOT DEFINED PTHREADS_EXCEPTION_SCHEME)
+    # Assign default if needed
+    SET(PTHREADS_EXCEPTION_SCHEME "C")
+ELSE(NOT DEFINED PTHREADS_EXCEPTION_SCHEME)
+    # Validate
+    IF(NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "C" AND
+       NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "CE" AND
+       NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "SE")
+
+    MESSAGE(FATAL_ERROR "See documentation for FindPthreads.cmake, only C, CE, and SE modes are allowed")
+
+    ENDIF(NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "C" AND
+          NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "CE" AND
+          NOT PTHREADS_EXCEPTION_SCHEME STREQUAL "SE")
+
+     IF(NOT MSVC AND PTHREADS_EXCEPTION_SCHEME STREQUAL "SE")
+         MESSAGE(FATAL_ERROR "Structured Exception Handling is only allowed for MSVC")
+     ENDIF(NOT MSVC AND PTHREADS_EXCEPTION_SCHEME STREQUAL "SE")
+
+ENDIF(NOT DEFINED PTHREADS_EXCEPTION_SCHEME)
+
+#
+# Find the header file
+#
+FIND_PATH(PTHREADS_INCLUDE_DIR pthread.h)
+
+#
+# Find the library
+#
+SET(names)
+IF(MSVC)
+    SET(names
+            pthreadV${PTHREADS_EXCEPTION_SCHEME}2
+            pthread
+    )
+ELSEIF(MINGW)
+    SET(names
+            pthreadG${PTHREADS_EXCEPTION_SCHEME}2
+            pthread
+    )
+ELSE(MSVC) # Unix / Cygwin / Apple
+    SET(names pthread)
+ENDIF(MSVC)
+    
+FIND_LIBRARY(PTHREADS_LIBRARY ${names}
+    DOC "The Portable Threads Library")
+
+IF(PTHREADS_INCLUDE_DIR AND PTHREADS_LIBRARY)
+    SET(PTHREADS_FOUND true)
+    SET(PTHREADS_DEFINITIONS -DHAVE_PTHREAD_H)
+    SET(PTHREADS_INCLUDE_DIRS ${PTHREADS_INCLUDE_DIR})
+    SET(PTHREADS_LIBRARIES    ${PTHREADS_LIBRARY})
+ENDIF(PTHREADS_INCLUDE_DIR AND PTHREADS_LIBRARY)
+
+IF(PTHREADS_FOUND)
+    IF(NOT PTHREADS_FIND_QUIETLY)
+        MESSAGE(STATUS "Found Pthreads: ${PTHREADS_LIBRARY}")
+    ENDIF(NOT PTHREADS_FIND_QUIETLY)
+ELSE(PTHREADS_FOUND) 
+    IF(PTHREADS_FIND_REQUIRED)
+        MESSAGE(FATAL_ERROR "Could not find the Pthreads Library")
+    ENDIF(PTHREADS_FIND_REQUIRED)
+ENDIF(PTHREADS_FOUND)
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..8779d55
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,153 @@
+beads (1.1.13-1~lucid1) lucid; urgency=low
+
+  * fixed bug when trying to save results from sypro images (detected by T. Balliau)
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 9 Sep 2010 10:18:51 +0200
+
+beads (1.1.12-1~lucid1) lucid; urgency=low
+
+  * command line output extension detection problem fixed
+  * integration of Cimg 1.3.9
+  * memory leak fixed on qt properties object
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 26 Aug 2010 10:18:51 +0200
+
+beads (1.1.11-1~lucid1) lucid; urgency=low
+
+  * UTF8 image file path
+  * parameter "atom" renamed in "minbeads"
+  * rename CONFLUENTS image to SELECT image
+  * parameter "minconfl" renamed in "minpath"
+  * parameter "flux" renamed in "minflux"
+  * confluent_ prefix removed from 	minflux, minpath, winconfl, minbeads
+  * prob_ prefix removed from sx, sy, sx_bottom, sy_bottom
+  * rename detection_threshold to minproba
+  * rename quantification_npass to npass
+  * inverse image checked by default
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 18 May 2010 10:18:51 +0200
+
+beads (1.1.10-1~lucid1) lucid; urgency=low
+
+  * write matchset with matched spot num
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Mon, 10 Apr 2010 10:18:51 +0200
+
+beads (1.1.9-2~lucid1) lucid; urgency=low
+
+  * bug fixed writing sample name in PROTICdbML (thanks to Thierry Balliau)
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 28 Apr 2010 10:18:51 +0200
+
+beads (1.1.9-1~lucid1) karmic; urgency=low
+
+  * edit spot_num dialog
+  * new menu item to save all beads steps in one directory (PROBABILITIES, NUMBERS, PATH...)
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 28 Apr 2010 10:18:51 +0200
+
+beads (1.1.8-1~karmic1) karmic; urgency=low
+
+  * poor protection of detection in BeadsResults fixed (bug found by Thierry Balliau)
+  * gnumeric support
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Fri, 12 Mar 2010 10:18:51 +0200
+
+beads (1.1.7-1~karmic1) karmic; urgency=low
+
+  * CMakeLists.txt changes
+  * use of QXmlStreamWriter to produce well formed and well coded XML documents
+  * use of QString and QDebug in the core library
+  * qt about disabled
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Fri, 24 Feb 2010 10:18:51 +0200
+
+beads (1.1.6-1~karmic1) karmic; urgency=low
+
+  * bug reported by Thierry Balliau fixed (depth of recursive functions)
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 13 Jan 2010 02:18:51 +0200
+
+beads (1.1.5-1~karmic1) karmic; urgency=low
+
+  * cimg_OS defined by cmake
+  * SVG beads icon added to the main windows
+  * image intensity reads correctly UTF8 file paths (uses QFileInfo)
+  * action "fit to windows" is disabled
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 16 Dec 2009 02:18:51 +0200
+
+beads (1.1.4-1~karmic1) karmic; urgency=low
+
+  * save picked num in PROTICdbML file
+  * edit picked num on detected gel image
+  * display spot number on detected gel image
+  * display x,y position of the cursor on gel image
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 16 Dec 2009 02:18:51 +0200
+
+beads (1.1.3-1~karmic1) karmic; urgency=low
+
+  * contour edges problem fixed
+  * qt compilation with win32
+  * new steps argument
+  * updated man page
+  * desktop menu for qtbeads
+  * parameters files examples in /usr/share/beads
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Mon, 7 Dec 2009 18:18:51 +0200
+
+beads (1.1.2-1~karmic1) karmic; urgency=low
+
+  * load beads parameter file
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 30 Nov 2009 10:18:51 +0200
+
+beads (1.1.1-2~karmic1) karmic; urgency=low
+
+  * package dependencies fixed
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 29 Nov 2009 10:18:51 +0200
+
+beads (1.1.1-1~karmic1) karmic; urgency=low
+
+  * saves results to output files (images, quantifications, svg, PROTICdbML)
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 29 Nov 2009 10:18:51 +0200
+
+beads (1.1.0-1~karmic1) karmic; urgency=low
+
+  * new cimg library (1.3.2)
+  * first version of qtbeads, GUI for beads
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 28 Nov 2009 10:18:51 +0200
+
+beads (1.0-2~karmic1) karmic; urgency=low
+
+  * repackaging for Ubuntu Karmic Koala
+
+ --  Olivier Langella <Olivier.Langella at moulon.inra.fr>  Thu, 12 Nov 2009 10:18:51 +0200
+ 
+beads (1.0-1~hardy1) hardy; urgency=low
+
+  * published in Proteomics : http://www.ncbi.nlm.nih.gov/pubmed/19003871
+
+ -- Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 26 Nov 2008 11:18:51 +0200
+
+beads (1.0-1) intrepid; urgency=low
+
+  * published in Proteomics : http://www.ncbi.nlm.nih.gov/pubmed/19003871
+
+ -- Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 26 Nov 2008 11:18:51 +0200
+
+beads (0.1-1~hardy1) hardy; urgency=low
+
+  * ppa build for Hardy and Intrepid.
+
+ -- Olivier Langella <Olivier.Langella at moulon.inra.fr>  Fri, 07 Nov 2008 20:08:38 +0200
+
+beads (0.1-1) intrepid; urgency=low
+
+  * Initial release.
+
+ -- Olivier Langella <Olivier.Langella at moulon.inra.fr>  Wed, 25 Jun 2008 11:18:51 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..b8626c4
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..b381868
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,15 @@
+Source: beads
+Section: science
+Priority: optional
+Maintainer: Olivier Langella <olivier.langella at moulon.inra.fr>
+Homepage: http://pappso.inra.fr/bioinfo/beads
+DM-Upload-Allowed: yes
+Uploaders: Olivier Langella <olivier.langella at moulon.inra.fr>
+Standards-Version: 1.0
+Build-Depends: debhelper (>= 5), cmake (>= 2), libx11-dev, libc6-dev,libqt4-dev (>= 4.5.2)
+
+Package: beads
+Architecture: any
+Depends: ${shlibs:Depends}, libx11-6, imagemagick,libqtgui4 (>= 4.5.2),libqt4-xml (>= 4.5.2),libqt4-svg (>= 4.5.2)
+Description: 2-DE electrophoresis gel image spot detection
+ 2-DE electrophoresis gel image spot detection
\ No newline at end of file
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..8051220
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,9 @@
+X-Format-Specification: http://wiki.debian.org/Proposals/CopyrightFormat
+X-Debianized-By: Olivier Langella <olivier.langella at moulon.inra.fr>
+X-Debianized-Date: Wed, 25 Jun 2008 11:18:51 +0200
+X-Source-Downloaded-From:
+X-Upstream-Author:
+
+Files: *
+Copyright: © 2008 Michel Zivy <zivy at moulon.inra.fr>, Olivier Langella <olivier.langella at moulon.inra.fr>
+License: CeCILL	   
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..aa141b1
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,91 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+
+
+configure: configure-stamp
+configure-stamp:
+	cmake -DCMAKE_INSTALL_PREFIX=/usr .
+	dh_testdir
+	# Add here commands to configure the package.
+
+	touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp 
+	dh_testdir
+
+	# Add here commands to compile the package.
+	$(MAKE)
+	#docbook-to-man debian/beads.sgml > beads.1
+
+	touch $@
+
+clean:
+	dh_testdir
+	dh_testroot
+	rm -f build-stamp configure-stamp CMakeCache.txt
+
+	# Add here commands to clean up after the build process.
+	-$(MAKE) clean
+
+	dh_clean 
+
+install: build
+	dh_testdir
+	dh_testroot
+	dh_clean -k 
+	dh_installdirs
+
+	# Add here commands to install the package into debian/beads.
+	$(MAKE) DESTDIR=$(CURDIR)/debian/beads install
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+	dh_testdir
+	dh_testroot
+#	dh_installchangelogs ChangeLog
+	dh_installdocs
+	dh_installexamples
+#	dh_install
+#	dh_installmenu
+#	dh_installdebconf	
+#	dh_installlogrotate
+#	dh_installemacsen
+#	dh_installpam
+#	dh_installmime
+#	dh_python
+#	dh_installinit
+#	dh_installcron
+#	dh_installinfo
+	dh_installman
+	dh_link
+	dh_strip
+	dh_compress
+	dh_fixperms
+#	dh_perl
+#	dh_makeshlibs
+	dh_installdeb
+	dh_shlibdeps
+	dh_gencontrol
+	dh_md5sums
+	dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/doc/beads.html b/doc/beads.html
new file mode 100644
index 0000000..2bdca67
--- /dev/null
+++ b/doc/beads.html
@@ -0,0 +1,88 @@
+Content-type: text/html
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML><HEAD><TITLE>Man page of BEADS</TITLE>
+</HEAD><BODY>
+<H1>BEADS</H1>
+Section: User Commands  (1)<BR>Updated: 2009 Dec 5<BR><A HREF="#index">Index</A>
+<A HREF="/cgi-bin/man/man2html">Return to Main Contents</A><HR>
+
+<A NAME="lbAB"> </A>
+<H2>NAME</H2>
+
+beads - 2D gel electrophoresis spot detection
+<P>
+<A NAME="lbAC"> </A>
+<H2>SYNOPSIS</H2>
+
+<B>beads</B>
+
+[<I>--inverse --steps -p parameter file</I>]... <I>-i INPUT -o OUTPUT
+<BR>
+
+</I><A NAME="lbAD"> </A>
+<H2>DESCRIPTION</H2>
+
+
+<P>
+
+detect and quantify spots in INPUT gel image file.
+<DL COMPACT>
+<DT><B>-i</B><DD>
+path to the gel image input file
+<DT><B>-o</B><DD>
+path to the output file
+<DT><B>-p</B>, <B>--param</B><DD>
+path to the parameter file
+<DT><B>--inverse</B><DD>
+inverse pixel intensity in the source gel image, must be "true" or "false" ("true" by default)
+<DT><B>--steps</B><DD>
+save each image computed by beads at each step of the process into TIFF files. This option can be used to tune parameter file.
+<BR>
+
+</DL>
+<A NAME="lbAE"> </A>
+<H2>OUTPUT</H2>
+
+beads produces several files format as output: xml, txt, jpg, tif and svg. To choose the output file format, just write the corresponding extension in the output filename OUTPUT
+<BR>
+
+<A NAME="lbAF"> </A>
+<H2>AUTHORS</H2>
+
+beads was written by Michel Zivy <<A HREF="mailto:michel.zivy at moulon.inra.fr">michel.zivy at moulon.inra.fr</A>> and Olivier Langella <<A HREF="mailto:olivier.langella at moulon.inra.fr">olivier.langella at moulon.inra.fr</A>>.
+<BR>
+
+<A NAME="lbAG"> </A>
+<H2>COPYRIGHT</H2>
+
+Copyright CeCILL
+<BR>
+
+<A NAME="lbAH"> </A>
+<H2>SEE ALSO</H2>
+
+The beads homepage :
+<BR>
+
+<A HREF="http://pappso.inra.fr/bioinfo/beads/index.php">http://pappso.inra.fr/bioinfo/beads/index.php</A>
+<P>
+
+<HR>
+<A NAME="index"> </A><H2>Index</H2>
+<DL>
+<DT><A HREF="#lbAB">NAME</A><DD>
+<DT><A HREF="#lbAC">SYNOPSIS</A><DD>
+<DT><A HREF="#lbAD">DESCRIPTION</A><DD>
+<DT><A HREF="#lbAE">OUTPUT</A><DD>
+<DT><A HREF="#lbAF">AUTHORS</A><DD>
+<DT><A HREF="#lbAG">COPYRIGHT</A><DD>
+<DT><A HREF="#lbAH">SEE ALSO</A><DD>
+</DL>
+<HR>
+This document was created by
+<A HREF="/cgi-bin/man/man2html">man2html</A>,
+using the manual pages.<BR>
+Time: 14:36:04 GMT, December 08, 2009
+</BODY>
+</HTML>
diff --git a/doc/man1/beads.1 b/doc/man1/beads.1
new file mode 100644
index 0000000..8b97bb5
--- /dev/null
+++ b/doc/man1/beads.1
@@ -0,0 +1,41 @@
+.TH BEADS 1 "2009 Dec 5" "Manual for beads"
+.SH NAME
+beads \- 2D gel electrophoresis spot detection
+
+.SH SYNOPSIS
+.B beads
+[\fI--inverse --steps -p parameter file\fR]... \fI-i INPUT \fI-o OUTPUT
+.br
+.SH DESCRIPTION
+.\" Add any additional description here
+.PP
+detect and quantify spots in INPUT gel image file.
+.TP
+\fB\-i\fR
+path to the gel image input file
+.TP
+\fB\-o\fR
+path to the output file
+.TP
+\fB\-p\fR, \fB\-\-param\fR
+path to the parameter file
+.TP
+\fB\-\-inverse\fR
+inverse pixel intensity in the source gel image, must be "true" or "false" ("true" by default)
+.TP
+\fB\-\-steps\fR
+save each image computed by beads at each step of the process into TIFF files. This option can be used to tune parameter file.
+.br
+.SH OUTPUT
+beads produces several files format as output: xml, txt, jpg, tif and svg. To choose the output file format, just write the corresponding extension in the output filename OUTPUT
+.br
+.SH AUTHORS
+beads was written by Michel Zivy <michel.zivy at moulon.inra.fr> and Olivier Langella <olivier.langella at moulon.inra.fr>.
+.br
+.SH COPYRIGHT
+Copyright CeCILL
+.br
+.SH SEE ALSO
+The beads homepage :
+.br
+http://pappso.inra.fr/bioinfo/beads/index.php
diff --git a/share/applications/beads.desktop b/share/applications/beads.desktop
new file mode 100644
index 0000000..8d8fda0
--- /dev/null
+++ b/share/applications/beads.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=Beads
+Categories=Education;Science;Math;
+Comment=2-DE gel spot detection
+Comment[fr]=détection de spots sur des images de gels d'électrophorèse 2D
+Exec=qtbeads %U
+Icon=/usr/share/beads/beads_icon.svg
+Terminal=false
+Type=Application
+StartupNotify=true
+X-Ubuntu-Gettext-Domain=beads
diff --git a/share/beads/beads_blue.conf b/share/beads/beads_blue.conf
new file mode 100644
index 0000000..3a51783
--- /dev/null
+++ b/share/beads/beads_blue.conf
@@ -0,0 +1,51 @@
+# beads.conf
+# beads configuration file
+
+
+################################
+# parameters for DIRECTIONS    #
+################################
+
+burned_pixel_threshold = 60000
+
+
+################################
+# parameters for SELECT        #
+################################
+
+minflux = 100
+minpath = 2
+winconfl = 300
+minbeads = 200
+confluent_intmax = 50000
+confluent_minpct = 1        # donc pas utilisé 
+
+
+################################
+# parameters for PROBABILITIES #
+################################
+
+prob_threshold = 10         # donc pas utilisé
+sx = 3
+sy = 3
+#sx_bottom = 0
+#sy_bottom = 0
+
+
+################################
+# parameters for detections #
+################################
+
+minproba  = 400       
+
+
+################################
+# parameters for quantification #
+################################
+
+quantification_enlarge_fusion = 3  
+quantification_enlarge = 3
+npass = 10
+
+
+
diff --git a/share/beads/beads_icon.svg b/share/beads/beads_icon.svg
new file mode 100644
index 0000000..f731fab
--- /dev/null
+++ b/share/beads/beads_icon.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="48"
+   height="48"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47pre4 r22446"
+   sodipodi:docname="Nouveau document 1">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8.1875"
+     inkscape:cx="45.467557"
+     inkscape:cy="17.037303"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1141"
+     inkscape:window-height="744"
+     inkscape:window-x="352"
+     inkscape:window-y="193"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1004.3622)">
+    <text
+       xml:space="preserve"
+       style="font-size:25.60545349px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
+       x="1.7171028"
+       y="547.47839"
+       id="text2849"
+       transform="scale(0.52382629,1.9090298)"><tspan
+         sodipodi:role="line"
+         id="tspan2851"
+         x="1.7171028"
+         y="547.47839">BEADS</tspan></text>
+  </g>
+</svg>
diff --git a/share/beads/beads_silver.conf b/share/beads/beads_silver.conf
new file mode 100644
index 0000000..49ef618
--- /dev/null
+++ b/share/beads/beads_silver.conf
@@ -0,0 +1,51 @@
+# beads.conf
+# beads configuration file
+
+
+################################
+# parameters for DIRECTIONS    #
+################################
+
+burned_pixel_threshold = 63000
+
+
+################################
+# parameters for SELECT        #
+################################
+
+minflux = 100
+minpath = 2
+winconfl = 300
+minbeads = 200
+confluent_intmax = 60000
+confluent_minpct = 1        # pas utilisé 
+
+
+################################
+# parameters for PROBABILITIES #
+################################
+
+prob_threshold = 10         # pas utilisé
+sx = 3
+sy = 3
+sx_bottom = 9
+sy_bottom = 9
+
+
+################################
+# parameters for detections #
+################################
+
+minproba  = 400       
+
+
+################################
+# parameters for quantification #
+################################
+
+quantification_enlarge_fusion = 3
+quantification_enlarge = 3
+npass = 10
+
+
+
diff --git a/share/beads/beads_silver_small.conf b/share/beads/beads_silver_small.conf
new file mode 100644
index 0000000..d24d536
--- /dev/null
+++ b/share/beads/beads_silver_small.conf
@@ -0,0 +1,51 @@
+# billes.conf
+# billes configuration file
+
+
+################################
+# parameters for DIRECTIONS    #
+################################
+
+burned_pixel_threshold = 63000
+
+
+################################
+# parameters for SELECT        #
+################################
+
+minflux = 50
+minpath = 1
+winconfl = 150
+minbeads = 30
+confluent_intmax = 60000
+confluent_minpct = 1        # pas utilisé 
+
+
+################################
+# parameters for PROBABILITIES #
+################################
+
+prob_threshold = 10         # pas utilisé
+sx = 5
+sy = 5
+sx_bottom = 9
+sy_bottom = 9
+
+
+################################
+# parameters for detections #
+################################
+
+minproba  = 100       
+
+
+################################
+# parameters for quantification #
+################################
+
+quantification_enlarge_fusion = 3
+quantification_enlarge = 3
+npass = 10
+
+
+
diff --git a/src/CImg.h b/src/CImg.h
new file mode 100644
index 0000000..f6df85c
--- /dev/null
+++ b/src/CImg.h
@@ -0,0 +1,39604 @@
+/*
+ #
+ #  File            : CImg.h
+ #                    ( C++ header file )
+ #
+ #  Description     : The C++ Template Image Processing Toolkit.
+ #                    This file is the main component of the CImg Library project.
+ #                    ( http://cimg.sourceforge.net )
+ #
+ #  Project manager : David Tschumperle.
+ #                    ( http://www.greyc.ensicaen.fr/~dtschump/ )
+ #
+ #                    The complete list of contributors is available in file 'README.txt'
+ #                    distributed within the CImg package.
+ #
+ #  Licenses        : This file is 'dual-licensed', you have to choose one
+ #                    of the two licenses below to apply.
+ #
+ #                    CeCILL-C
+ #                    The CeCILL-C license is close to the GNU LGPL.
+ #                    ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html )
+ #
+ #                or  CeCILL v2.0
+ #                    The CeCILL license is compatible with the GNU GPL.
+ #                    ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
+ #
+ #  This software is governed either by the CeCILL or the CeCILL-C license
+ #  under French law and abiding by the rules of distribution of free software.
+ #  You can  use, modify and or redistribute the software under the terms of
+ #  the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA
+ #  at the following URL : "http://www.cecill.info".
+ #
+ #  As a counterpart to the access to the source code and  rights to copy,
+ #  modify and redistribute granted by the license, users are provided only
+ #  with a limited warranty  and the software's author,  the holder of the
+ #  economic rights,  and the successive licensors  have only  limited
+ #  liability.
+ #
+ #  In this respect, the user's attention is drawn to the risks associated
+ #  with loading,  using,  modifying and/or developing or reproducing the
+ #  software by the user in light of its specific status of free software,
+ #  that may mean  that it is complicated to manipulate,  and  that  also
+ #  therefore means  that it is reserved for developers  and  experienced
+ #  professionals having in-depth computer knowledge. Users are therefore
+ #  encouraged to load and test the software's suitability as regards their
+ #  requirements in conditions enabling the security of their systems and/or
+ #  data to be ensured and,  more generally, to use and operate it in the
+ #  same conditions as regards security.
+ #
+ #  The fact that you are presently reading this means that you have had
+ #  knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms.
+ #
+*/
+
+// Define version number of the library file.
+#ifndef cimg_version
+#define cimg_version 139
+
+/*-----------------------------------------------------------
+ #
+ # Test and auto-set CImg configuration variables
+ # and include required headers.
+ #
+ # If you find that default configuration variables are
+ # not adapted to your case, you can override their values
+ # before including the header file "CImg.h"
+ # (use the #define directive).
+ #
+ ------------------------------------------------------------*/
+
+// Include required standard C++ headers.
+#include <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+#include <cstring>
+#include <cmath>
+#include <ctime>
+#include <exception>
+
+// Operating system configuration.
+//
+// Define 'cimg_OS' to : '0' for an unknown OS (will try to minize library dependancies).
+//                       '1' for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...).
+//                       '2' for Microsoft Windows.
+//                       (autodetection is done by default).
+#ifndef cimg_OS
+#if defined(unix)        || defined(__unix)      || defined(__unix__) \
+ || defined(linux)       || defined(__linux)     || defined(__linux__) \
+ || defined(sun)         || defined(__sun) \
+ || defined(BSD)         || defined(__OpenBSD__) || defined(__NetBSD__) \
+ || defined(__FreeBSD__) || defined __DragonFly__ \
+ || defined(sgi)         || defined(__sgi) \
+ || defined(__MACOSX__)  || defined(__APPLE__) \
+ || defined(__CYGWIN__)
+#define cimg_OS 1
+#elif defined(_MSC_VER) || defined(WIN32)  || defined(_WIN32) || defined(__WIN32__) \
+   || defined(WIN64)    || defined(_WIN64) || defined(__WIN64__)
+#define cimg_OS 2
+#else
+#define cimg_OS 0
+#endif
+#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2)
+#error CImg Library : Configuration variable 'cimg_OS' is badly defined.
+#error (valid values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows').
+#endif
+
+// Disable silly warnings on Microsoft VC++ compilers.
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4311)
+#pragma warning(disable:4312)
+#pragma warning(disable:4800)
+#pragma warning(disable:4804)
+#pragma warning(disable:4996)
+#define _CRT_SECURE_NO_DEPRECATE 1
+#define _CRT_NONSTDC_NO_DEPRECATE 1
+#endif
+
+// Include OS-specific headers for system management.
+#if cimg_OS==1
+#include <sys/types.h>
+#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>
+#define cimg_snprintf _snprintf
+#define cimg_vsnprintf _vsnprintf
+#endif
+#ifndef cimg_snprintf
+#include <stdio.h>
+#define cimg_snprintf snprintf
+#define cimg_vsnprintf vsnprintf
+#endif
+
+// Filename separator configuration.
+//
+// Default separator is '/' for Unix-based OS, and '\' or Windows.
+#ifndef cimg_file_separator
+#if cimg_OS==2
+#define cimg_file_separator '\\'
+#else
+#define cimg_file_separator '/'
+#endif
+#endif
+
+// Output messages verbosity configuration.
+//
+// Define 'cimg_verbosity' to : '0' to hide library messages (quiet mode).
+//                              '1' to print library messages on the console.
+//                              '2' to display library messages on a dialog window (default behavior).
+//                              '3' to do as '1' + add extra warnings (may slow down the code !).
+//                              '4' to do as '2' + add extra warnings (may slow down the code !).
+//
+// Define 'cimg_strict_warnings' to replace warning messages by exception throwns.
+//
+// Define 'cimg_use_vt100' to allow output of color messages (require VT100-compatible terminal).
+#ifndef cimg_verbosity
+#define cimg_verbosity 2
+#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4)
+#error CImg Library : Configuration variable 'cimg_verbosity' is badly defined.
+#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }).
+#endif
+
+// Display framework configuration.
+//
+// Define 'cimg_display' to : '0' to disable display capabilities.
+//                            '1' to use X-Window framework (X11).
+//                            '2' to use Microsoft GDI32 framework.
+#ifndef cimg_display
+#if cimg_OS==0
+#define cimg_display 0
+#elif cimg_OS==1
+#if defined(__MACOSX__) || defined(__APPLE__)
+#define cimg_display 1
+#else
+#define cimg_display 1
+#endif
+#elif cimg_OS==2
+#define cimg_display 2
+#endif
+#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2)
+#error CImg Library : Configuration variable 'cimg_display' is badly defined.
+#error (should be { 0=none | 1=X-Window (X11) | 2=Microsoft GDI32 }).
+#endif
+
+// Include display-specific headers.
+#if cimg_display==1
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <pthread.h>
+#ifdef cimg_use_xshm
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+#ifdef cimg_use_xrandr
+#include <X11/extensions/Xrandr.h>
+#endif
+#endif
+
+// OpenMP configuration.
+// (http://www.openmp.org)
+//
+// Define 'cimg_use_openmp' to enable OpenMP support.
+//
+// OpenMP directives can be used in few CImg functions to get
+// advantages of multi-core CPUs. Using OpenMP is not mandatory.
+#ifdef cimg_use_openmp
+#include "omp.h"
+#endif
+
+// LibPNG configuration.
+// (http://www.libpng.org)
+//
+// Define 'cimg_use_png' to enable LibPNG support.
+//
+// LibPNG can be used in functions 'CImg<T>::{load,save}_png()'
+// to get a builtin support of PNG files. Using LibPNG is not mandatory.
+#ifdef cimg_use_png
+extern "C" {
+#include "png.h"
+}
+#endif
+
+// LibJPEG configuration.
+// (http://en.wikipedia.org/wiki/Libjpeg)
+//
+// Define 'cimg_use_jpeg' to enable LibJPEG support.
+//
+// LibJPEG can be used in functions 'CImg<T>::{load,save}_jpeg()'
+// to get a builtin support of JPEG files. Using LibJPEG is not mandatory.
+#ifdef cimg_use_jpeg
+extern "C" {
+#include "jpeglib.h"
+#include "setjmp.h"
+}
+#endif
+
+// LibTIFF configuration.
+// (http://www.libtiff.org)
+//
+// Define 'cimg_use_tiff' to enable LibTIFF support.
+//
+// LibTIFF can be used in functions 'CImg[List]<T>::{load,save}_tiff()'
+// to get a builtin support of TIFF files. Using LibTIFF is not mandatory.
+#ifdef cimg_use_tiff
+extern "C" {
+#include "tiffio.h"
+}
+#endif
+
+// FFMPEG Avcodec and Avformat libraries configuration.
+// (http://www.ffmpeg.org)
+//
+// Define 'cimg_use_ffmpeg' to enable FFMPEG lib support.
+//
+// Avcodec and Avformat libraries can be used in functions
+// 'CImg[List]<T>::load_ffmpeg()' to get a builtin
+// support of various image sequences files.
+// Using FFMPEG libraries is not mandatory.
+#ifdef cimg_use_ffmpeg
+extern "C" {
+#define __STDC_CONSTANT_MACROS
+#include "avformat.h"
+#include "avcodec.h"
+#include "swscale.h"
+}
+#endif
+
+// Zlib configuration
+// (http://www.zlib.net)
+//
+// Define 'cimg_use_zlib' to enable Zlib support.
+//
+// Zlib can be used in functions 'CImg[List]<T>::{load,save}_cimg()'
+// to allow compressed data in '.cimg' files. Using Zlib is not mandatory.
+#ifdef cimg_use_zlib
+extern "C" {
+#include "zlib.h"
+}
+#endif
+
+// Magick++ configuration.
+// (http://www.imagemagick.org/Magick++)
+//
+// Define 'cimg_use_magick' to enable Magick++ support.
+//
+// Magick++ library can be used in functions 'CImg<T>::{load,save}()'
+// to get a builtin support of various image formats (PNG,JPEG,TIFF,...).
+// Using Magick++ is not mandatory.
+#ifdef cimg_use_magick
+#include "Magick++.h"
+#endif
+
+// FFTW3 configuration.
+// (http://www.fftw.org)
+//
+// Define 'cimg_use_fftw3' to enable libFFTW3 support.
+//
+// FFTW3 library can be used in functions 'CImg[List]<T>::FFT()' to
+// efficiently compute the Fast Fourier Transform of image data.
+#ifdef cimg_use_fftw3
+extern "C" {
+#include "fftw3.h"
+}
+#endif
+
+// Board configuration
+// (http://libboard.sourceforge.net/)
+//
+// Define 'cimg_use_board' to enable Board support.
+//
+// Board library can be used in functions 'CImg<T>::draw_object3d()'
+// to draw objects 3d in vector-graphics canvas that can be saved
+// as .PS or .SVG files afterwards.
+#ifdef cimg_use_board
+#ifdef None
+#undef None
+#define _cimg_redefine_None
+#endif
+#include "Board.h"
+#endif
+
+// OpenEXR configuration
+// (http://www.openexr.com/)
+//
+// Define 'cimg_use_openexr' to enable OpenEXR support.
+//
+// OpenEXR can be used to read/write .exr file formats.
+#ifdef cimg_use_openexr
+#include "ImfRgbaFile.h"
+#include "ImfInputFile.h"
+#include "ImfChannelList.h"
+#include "ImfMatrixAttribute.h"
+#include "ImfArray.h"
+#endif
+
+// Lapack configuration.
+// (http://www.netlib.org/lapack)
+//
+// Define 'cimg_use_lapack' to enable LAPACK support.
+//
+// Lapack can be used in various CImg functions dealing with
+// matrix computation and algorithms (eigenvalues, inverse, ...).
+// Using Lapack is not mandatory.
+#ifdef cimg_use_lapack
+extern "C" {
+  extern void sgetrf_(int*, int*, float*, int*, int*, int*);
+  extern void sgetri_(int*, float*, int*, int*, float*, int*, int*);
+  extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*);
+  extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*);
+  extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*);
+  extern void dgetrf_(int*, int*, double*, int*, int*, int*);
+  extern void dgetri_(int*, double*, int*, int*, double*, int*, int*);
+  extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*);
+  extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, int*, double*, int*, double*, int*, int*);
+  extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*);
+}
+#endif
+
+// Check if min/max/PI macros are defined.
+//
+// CImg does not compile if macros 'min', 'max' or 'PI' are defined,
+// because min(), max() and PI labels are defined and used in the cimg:: namespace.
+// so it '#undef' these macros if necessary, and restore them to reasonable
+// values at the end of the file.
+#ifdef min
+#undef min
+#define _cimg_redefine_min
+#endif
+#ifdef max
+#undef max
+#define _cimg_redefine_max
+#endif
+#ifdef PI
+#undef PI
+#define _cimg_redefine_PI
+#endif
+
+/*------------------------------------------------------------------------------
+  #
+  # Define user-friendly macros.
+  #
+  # User macros are prefixed by 'cimg_' and can be used in your own code.
+  # They are particularly useful for option parsing, and image loops creation.
+  #
+  ------------------------------------------------------------------------------*/
+
+// Define the program usage, and retrieve command line arguments.
+#define cimg_usage(usage) cimg_library::cimg::option((char*)0,argc,argv,(char*)0,usage,false)
+#define cimg_help(str) cimg_library::cimg::option((char*)0,argc,argv,str,(char*)0)
+#define cimg_option(name,defaut,usage) cimg_library::cimg::option(name,argc,argv,defaut,usage)
+#define cimg_argument(pos) cimg_library::cimg::argument(pos,argc,argv)
+#define cimg_argument1(pos,s0) cimg_library::cimg::argument(pos,argc,argv,1,s0)
+#define cimg_argument2(pos,s0,s1) cimg_library::cimg::argument(pos,argc,argv,2,s0,s1)
+#define cimg_argument3(pos,s0,s1,s2) cimg_library::cimg::argument(pos,argc,argv,3,s0,s1,s2)
+#define cimg_argument4(pos,s0,s1,s2,s3) cimg_library::cimg::argument(pos,argc,argv,4,s0,s1,s2,s3)
+#define cimg_argument5(pos,s0,s1,s2,s3,s4) cimg_library::cimg::argument(pos,argc,argv,5,s0,s1,s2,s3,s4)
+#define cimg_argument6(pos,s0,s1,s2,s3,s4,s5) cimg_library::cimg::argument(pos,argc,argv,6,s0,s1,s2,s3,s4,s5)
+#define cimg_argument7(pos,s0,s1,s2,s3,s4,s5,s6) cimg_library::cimg::argument(pos,argc,argv,7,s0,s1,s2,s3,s4,s5,s6)
+#define cimg_argument8(pos,s0,s1,s2,s3,s4,s5,s6,s7) cimg_library::cimg::argument(pos,argc,argv,8,s0,s1,s2,s3,s4,s5,s6,s7)
+#define cimg_argument9(pos,s0,s1,s2,s3,s4,s5,s6,s7,s8) cimg_library::cimg::argument(pos,argc,argv,9,s0,s1,s2,s3,s4,s5,s6,s7,s8)
+
+// Define and manipulate local neighborhoods.
+#define CImg_2x2(I,T) T I[4]; \
+                      T& I##cc = I[0]; T& I##nc = I[1]; \
+                      T& I##cn = I[2]; T& I##nn = I[3]; \
+                      I##cc = I##nc = \
+                      I##cn = I##nn = 0
+
+#define CImg_3x3(I,T) T I[9]; \
+                      T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \
+                      T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \
+                      T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \
+                      I##pp = I##cp = I##np = \
+                      I##pc = I##cc = I##nc = \
+                      I##pn = I##cn = I##nn = 0
+
+#define CImg_4x4(I,T) T I[16]; \
+                      T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \
+                      T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \
+                      T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \
+                      T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \
+                      I##pp = I##cp = I##np = I##ap = \
+                      I##pc = I##cc = I##nc = I##ac = \
+                      I##pn = I##cn = I##nn = I##an = \
+                      I##pa = I##ca = I##na = I##aa = 0
+
+#define CImg_5x5(I,T) T I[25]; \
+                      T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \
+                      T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \
+                      T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \
+                      T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \
+                      T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \
+                      I##bb = I##pb = I##cb = I##nb = I##ab = \
+                      I##bp = I##pp = I##cp = I##np = I##ap = \
+                      I##bc = I##pc = I##cc = I##nc = I##ac = \
+                      I##bn = I##pn = I##cn = I##nn = I##an = \
+                      I##ba = I##pa = I##ca = I##na = I##aa = 0
+
+#define CImg_2x2x2(I,T) T I[8]; \
+                      T& I##ccc = I[0]; T& I##ncc = I[1]; \
+                      T& I##cnc = I[2]; T& I##nnc = I[3]; \
+                      T& I##ccn = I[4]; T& I##ncn = I[5]; \
+                      T& I##cnn = I[6]; T& I##nnn = I[7]; \
+                      I##ccc = I##ncc = \
+                      I##cnc = I##nnc = \
+                      I##ccn = I##ncn = \
+                      I##cnn = I##nnn = 0
+
+#define CImg_3x3x3(I,T) T I[27]; \
+                      T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \
+                      T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \
+                      T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \
+                      T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \
+                      T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \
+                      T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \
+                      T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \
+                      T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \
+                      T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \
+                      I##ppp = I##cpp = I##npp = \
+                      I##pcp = I##ccp = I##ncp = \
+                      I##pnp = I##cnp = I##nnp = \
+                      I##ppc = I##cpc = I##npc = \
+                      I##pcc = I##ccc = I##ncc = \
+                      I##pnc = I##cnc = I##nnc = \
+                      I##ppn = I##cpn = I##npn = \
+                      I##pcn = I##ccn = I##ncn = \
+                      I##pnn = I##cnn = I##nnn = 0
+
+#define cimg_get2x2(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), I[3] = (T)(img)(_n1##x,_n1##y,z,c)
+
+#define cimg_get3x3(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), I[3] = (T)(img)(_p1##x,y,z,c), \
+  I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), \
+  I[8] = (T)(img)(_n1##x,_n1##y,z,c)
+
+#define cimg_get4x4(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), I[3] = (T)(img)(_n2##x,_p1##y,z,c), \
+  I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), \
+  I[8] = (T)(img)(_p1##x,_n1##y,z,c), I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \
+  I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), I[15] = (T)(img)(_n2##x,_n2##y,z,c)
+
+#define cimg_get5x5(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), I[3] = (T)(img)(_n1##x,_p2##y,z,c), \
+  I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), \
+  I[8] = (T)(img)(_n1##x,_p1##y,z,c), I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \
+  I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), I[15] = (T)(img)(_p2##x,_n1##y,z,c), \
+  I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), \
+  I[20] = (T)(img)(_p2##x,_n2##y,z,c), I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \
+  I[24] = (T)(img)(_n2##x,_n2##y,z,c)
+
+#define cimg_get6x6(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), I[3] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), \
+ I[8] = (T)(img)(x,_p1##y,z,c), I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \
+ I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), I[15] = (T)(img)(_n1##x,y,z,c), \
+ I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), \
+ I[20] = (T)(img)(x,_n1##y,z,c), I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \
+ I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), I[27] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), \
+ I[32] = (T)(img)(x,_n3##y,z,c), I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c)
+
+#define cimg_get7x7(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), I[3] = (T)(img)(x,_p3##y,z,c), \
+ I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), \
+ I[8] = (T)(img)(_p2##x,_p2##y,z,c), I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), I[15] = (T)(img)(_p2##x,_p1##y,z,c), \
+ I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), \
+ I[20] = (T)(img)(_n3##x,_p1##y,z,c), I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \
+ I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), I[27] = (T)(img)(_n3##x,y,z,c), \
+ I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), \
+ I[32] = (T)(img)(_n1##x,_n1##y,z,c), I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \
+ I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), I[39] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), \
+ I[44] = (T)(img)(_p1##x,_n3##y,z,c), I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \
+ I[48] = (T)(img)(_n3##x,_n3##y,z,c)
+
+#define cimg_get8x8(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), I[3] = (T)(img)(x,_p3##y,z,c), \
+ I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), \
+ I[8] = (T)(img)(_p3##x,_p2##y,z,c), I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \
+ I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), I[15] = (T)(img)(_n4##x,_p2##y,z,c), \
+ I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), \
+ I[20] = (T)(img)(_n1##x,_p1##y,z,c), I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \
+ I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), I[27] = (T)(img)(x,y,z,c), \
+ I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), \
+ I[32] = (T)(img)(_p3##x,_n1##y,z,c), I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \
+ I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), I[39] = (T)(img)(_n4##x,_n1##y,z,c), \
+ I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), \
+ I[44] = (T)(img)(_n1##x,_n2##y,z,c), I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \
+ I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), I[51] = (T)(img)(x,_n3##y,z,c), \
+ I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), \
+ I[56] = (T)(img)(_p3##x,_n4##y,z,c), I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \
+ I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), I[63] = (T)(img)(_n4##x,_n4##y,z,c);
+
+#define cimg_get9x9(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), I[3] = (T)(img)(_p1##x,_p4##y,z,c), \
+ I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), \
+ I[8] = (T)(img)(_n4##x,_p4##y,z,c), I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \
+ I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), I[15] = (T)(img)(_n2##x,_p3##y,z,c), \
+ I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), \
+ I[20] = (T)(img)(_p2##x,_p2##y,z,c), I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), I[27] = (T)(img)(_p4##x,_p1##y,z,c), \
+ I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), \
+ I[32] = (T)(img)(_n1##x,_p1##y,z,c), I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \
+ I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), I[39] = (T)(img)(_p1##x,y,z,c), \
+ I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), \
+ I[44] = (T)(img)(_n4##x,y,z,c), I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \
+ I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), I[51] = (T)(img)(_n2##x,_n1##y,z,c), \
+ I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), \
+ I[56] = (T)(img)(_p2##x,_n2##y,z,c), I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), I[63] = (T)(img)(_p4##x,_n3##y,z,c), \
+ I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), \
+ I[68] = (T)(img)(_n1##x,_n3##y,z,c), I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \
+ I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), I[75] = (T)(img)(_p1##x,_n4##y,z,c), \
+ I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), \
+ I[80] = (T)(img)(_n4##x,_n4##y,z,c)
+
+#define cimg_get2x2x2(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), I[3] = (T)(img)(_n1##x,_n1##y,z,c), \
+  I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)
+
+#define cimg_get3x3x3(img,x,y,z,c,I,T) \
+  I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), \
+  I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), I[5] = (T)(img)(_n1##x,y,_p1##z,c), \
+  I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), \
+  I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), I[11] = (T)(img)(_n1##x,_p1##y,z,c), \
+  I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), I[14] = (T)(img)(_n1##x,y,z,c), \
+  I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), I[17] = (T)(img)(_n1##x,_n1##y,z,c), \
+  I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), \
+  I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), I[23] = (T)(img)(_n1##x,y,_n1##z,c), \
+  I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)
+
+// Define various image loops.
+//
+// These macros generally avoid the use of iterators, but you are not forced to used them !
+#define cimg_for(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size(); (ptrs--)>(img)._data; )
+#define cimg_foroff(img,off) for (unsigned int off = 0, _max##off = (unsigned int)(img).size(); off<_max##off; ++off)
+
+#define cimg_for1(bound,i) for (int i = 0; i<(int)(bound); ++i)
+#define cimg_forX(img,x) cimg_for1((img)._width,x)
+#define cimg_forY(img,y) cimg_for1((img)._height,y)
+#define cimg_forZ(img,z) cimg_for1((img)._depth,z)
+#define cimg_forC(img,c) cimg_for1((img)._spectrum,c)
+#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x)
+#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x)
+#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y)
+#define cimg_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x)
+#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y)
+#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z)
+#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y)
+#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y)
+#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z)
+#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z)
+#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) cimg_forXYZ(img,x,y,z)
+
+#define cimg_for_in1(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound)-1; i<=_max##i; ++i)
+#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img)._width,x0,x1,x)
+#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img)._height,y0,y1,y)
+#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img)._depth,z0,z1,z)
+#define cimg_for_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c)
+#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y)
+#define cimg_for_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y)
+#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inZ(img,z0,z1,z)
+#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z)
+#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img)._width-1-(n),x)
+#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img)._height-1-(n),y)
+#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img)._depth-1-(n),z)
+#define cimg_for_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum-1-(n),c)
+#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img)._width-1-(n),(img)._height-1-(n),x,y)
+#define cimg_for_insideXYZ(img,x,y,z,n) cimg_for_inXYZ(img,n,n,n,(img)._width-1-(n),(img)._height-1-(n),(img)._depth-1-(n),x,y,z)
+#define cimg_for_insideXYZC(img,x,y,z,c,n) cimg_for_inXYZ(img,n,n,n,(img)._width-1-(n),(img)._height-1-(n),(img)._depth-1-(n),x,y,z)
+
+#define cimg_for_out1(boundi,i0,i1,i) \
+ for (int i = (int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1)+1:i)
+#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \
+ for (int j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
+  ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1)+1:i))
+#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \
+ for (int k = 0; k<(int)(boundk); ++k) \
+ for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
+  ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1)+1:i))
+#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \
+ for (int l = 0; l<(int)(boundl); ++l) \
+ for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \
+ for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
+  ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1)+1:i))
+#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img)._width,x0,x1,x)
+#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img)._height,y0,y1,y)
+#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img)._depth,z0,z1,z)
+#define cimg_for_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c)
+#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img)._width,(img)._height,x0,y0,x1,y1,x,y)
+#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img)._width,(img)._depth,x0,z0,x1,z1,x,z)
+#define cimg_for_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c)
+#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img)._height,(img)._depth,y0,z0,y1,z1,y,z)
+#define cimg_for_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c)
+#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c)
+#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_out3((img)._width,(img)._height,(img)._depth,x0,y0,z0,x1,y1,z1,x,y,z)
+#define cimg_for_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c)
+#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c)
+#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c)
+#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c)
+#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img)._width-1-(n),x)
+#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img)._height-1-(n),y)
+#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img)._depth-1-(n),z)
+#define cimg_for_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum-1-(n),c)
+#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img)._width-1-(n),(img)._height-1-(n),x,y)
+#define cimg_for_borderXYZ(img,x,y,z,n) cimg_for_outXYZ(img,n,n,n,(img)._width-1-(n),(img)._height-1-(n),(img)._depth-1-(n),x,y,z)
+#define cimg_for_borderXYZC(img,x,y,z,c,n) \
+ cimg_for_outXYZC(img,n,n,n,n,(img)._width-1-(n),(img)._height-1-(n),(img)._depth-1-(n),(img)._spectrum-1-(n),x,y,z,c)
+
+#define cimg_for_spiralXY(img,x,y) \
+ for (int x = 0, y = 0, _n1##x = 1, _n1##y = (img).width()*(img).height(); _n1##y; \
+      --_n1##y, _n1##x+=(_n1##x>>2)-((!(_n1##x&3)?--y:((_n1##x&3)==1?(img)._width-1-++x:((_n1##x&3)==2?(img)._height-1-++y:--x))))?0:1)
+
+#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \
+ for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \
+      _dx=(x1)>(x0)?(int)(x1)-(int)(x0):(_sx=-1,(int)(x0)-(int)(x1)), \
+      _dy=(y1)>(y0)?(int)(y1)-(int)(y0):(_sy=-1,(int)(y0)-(int)(y1)), \
+      _counter = _dx, \
+      _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \
+      _counter>=0; \
+      --_counter, x+=_steep? \
+      (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \
+      (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx))
+
+#define cimg_for2(bound,i) \
+ for (int i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+      _n1##i<(int)(bound) || i==--_n1##i; \
+      ++i, ++_n1##i)
+#define cimg_for2X(img,x) cimg_for2((img)._width,x)
+#define cimg_for2Y(img,y) cimg_for2((img)._height,y)
+#define cimg_for2Z(img,z) cimg_for2((img)._depth,z)
+#define cimg_for2C(img,c) cimg_for2((img)._spectrum,c)
+#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x)
+#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x)
+#define cimg_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x)
+#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y)
+#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y)
+#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z)
+#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y)
+#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z)
+#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z)
+#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) cimg_for2XYZ(img,x,y,z)
+
+#define cimg_for_in2(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
+      i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
+      ++i, ++_n1##i)
+#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img)._width,x0,x1,x)
+#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img)._height,y0,y1,y)
+#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img)._depth,z0,z1,z)
+#define cimg_for_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c)
+#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y)
+#define cimg_for_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y)
+#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Z(img,z0,z1,z)
+#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+      _n1##i<(int)(bound) || i==--_n1##i; \
+      _p1##i = i++, ++_n1##i)
+#define cimg_for3X(img,x) cimg_for3((img)._width,x)
+#define cimg_for3Y(img,y) cimg_for3((img)._height,y)
+#define cimg_for3Z(img,z) cimg_for3((img)._depth,z)
+#define cimg_for3C(img,c) cimg_for3((img)._spectrum,c)
+#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x)
+#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x)
+#define cimg_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x)
+#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y)
+#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y)
+#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z)
+#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y)
+#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z)
+#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z)
+#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) cimg_for3XYZ(img,x,y,z)
+
+#define cimg_for_in3(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+      _p1##i = i-1<0?0:i-1, \
+      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
+      i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
+      _p1##i = i++, ++_n1##i)
+#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img)._width,x0,x1,x)
+#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img)._height,y0,y1,y)
+#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img)._depth,z0,z1,z)
+#define cimg_for_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c)
+#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y)
+#define cimg_for_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y)
+#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Z(img,z0,z1,z)
+#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for4(bound,i) \
+ for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+      _n2##i = 2>=(bound)?(int)(bound)-1:2; \
+      _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
+      _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for4X(img,x) cimg_for4((img)._width,x)
+#define cimg_for4Y(img,y) cimg_for4((img)._height,y)
+#define cimg_for4Z(img,z) cimg_for4((img)._depth,z)
+#define cimg_for4C(img,c) cimg_for4((img)._spectrum,c)
+#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x)
+#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x)
+#define cimg_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x)
+#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y)
+#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y)
+#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z)
+#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y)
+#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z)
+#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z)
+#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) cimg_for4XYZ(img,x,y,z)
+
+#define cimg_for_in4(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+      _p1##i = i-1<0?0:i-1, \
+      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
+      i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
+      _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img)._width,x0,x1,x)
+#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img)._height,y0,y1,y)
+#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img)._depth,z0,z1,z)
+#define cimg_for_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c)
+#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y)
+#define cimg_for_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y)
+#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Z(img,z0,z1,z)
+#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for5(bound,i) \
+ for (int i = 0, _p2##i = 0, _p1##i = 0, \
+      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+      _n2##i = 2>=(bound)?(int)(bound)-1:2; \
+      _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
+      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for5X(img,x) cimg_for5((img)._width,x)
+#define cimg_for5Y(img,y) cimg_for5((img)._height,y)
+#define cimg_for5Z(img,z) cimg_for5((img)._depth,z)
+#define cimg_for5C(img,c) cimg_for5((img)._spectrum,c)
+#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x)
+#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x)
+#define cimg_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x)
+#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y)
+#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y)
+#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z)
+#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y)
+#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z)
+#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z)
+#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) cimg_for5XYZ(img,x,y,z)
+
+#define cimg_for_in5(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+      _p2##i = i-2<0?0:i-2, \
+      _p1##i = i-1<0?0:i-1, \
+      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
+      i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
+      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img)._width,x0,x1,x)
+#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img)._height,y0,y1,y)
+#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img)._depth,z0,z1,z)
+#define cimg_for_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c)
+#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y)
+#define cimg_for_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y)
+#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Z(img,z0,z1,z)
+#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for6(bound,i) \
+ for (int i = 0, _p2##i = 0, _p1##i = 0, \
+      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+      _n2##i = 2>=(bound)?(int)(bound)-1:2, \
+      _n3##i = 3>=(bound)?(int)(bound)-1:3; \
+      _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
+      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for6X(img,x) cimg_for6((img)._width,x)
+#define cimg_for6Y(img,y) cimg_for6((img)._height,y)
+#define cimg_for6Z(img,z) cimg_for6((img)._depth,z)
+#define cimg_for6C(img,c) cimg_for6((img)._spectrum,c)
+#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x)
+#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x)
+#define cimg_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x)
+#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y)
+#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y)
+#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z)
+#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y)
+#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z)
+#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z)
+#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) cimg_for6XYZ(img,x,y,z)
+
+#define cimg_for_in6(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+      _p2##i = i-2<0?0:i-2, \
+      _p1##i = i-1<0?0:i-1, \
+      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+      _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
+      i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
+      _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img)._width,x0,x1,x)
+#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img)._height,y0,y1,y)
+#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img)._depth,z0,z1,z)
+#define cimg_for_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c)
+#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y)
+#define cimg_for_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y)
+#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Z(img,z0,z1,z)
+#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for7(bound,i) \
+ for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+      _n2##i = 2>=(bound)?(int)(bound)-1:2, \
+      _n3##i = 3>=(bound)?(int)(bound)-1:3; \
+      _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
+      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for7X(img,x) cimg_for7((img)._width,x)
+#define cimg_for7Y(img,y) cimg_for7((img)._height,y)
+#define cimg_for7Z(img,z) cimg_for7((img)._depth,z)
+#define cimg_for7C(img,c) cimg_for7((img)._spectrum,c)
+#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x)
+#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x)
+#define cimg_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x)
+#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y)
+#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y)
+#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z)
+#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y)
+#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z)
+#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z)
+#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) cimg_for7XYZ(img,x,y,z)
+
+#define cimg_for_in7(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+      _p3##i = i-3<0?0:i-3, \
+      _p2##i = i-2<0?0:i-2, \
+      _p1##i = i-1<0?0:i-1, \
+      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+      _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
+      i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
+      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img)._width,x0,x1,x)
+#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img)._height,y0,y1,y)
+#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img)._depth,z0,z1,z)
+#define cimg_for_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c)
+#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y)
+#define cimg_for_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y)
+#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Z(img,z0,z1,z)
+#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for8(bound,i) \
+ for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+      _n1##i = 1>=(bound)?(int)(bound)-1:1, \
+      _n2##i = 2>=(bound)?(int)(bound)-1:2, \
+      _n3##i = 3>=(bound)?(int)(bound)-1:3, \
+      _n4##i = 4>=(bound)?(int)(bound)-1:4; \
+      _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+      i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
+      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for8X(img,x) cimg_for8((img)._width,x)
+#define cimg_for8Y(img,y) cimg_for8((img)._height,y)
+#define cimg_for8Z(img,z) cimg_for8((img)._depth,z)
+#define cimg_for8C(img,c) cimg_for8((img)._spectrum,c)
+#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x)
+#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x)
+#define cimg_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x)
+#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y)
+#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y)
+#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z)
+#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y)
+#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z)
+#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z)
+#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) cimg_for8XYZ(img,x,y,z)
+
+#define cimg_for_in8(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+      _p3##i = i-3<0?0:i-3, \
+      _p2##i = i-2<0?0:i-2, \
+      _p1##i = i-1<0?0:i-1, \
+      _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+      _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+      _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
+      _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
+      i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+      i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
+      _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img)._width,x0,x1,x)
+#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img)._height,y0,y1,y)
+#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img)._depth,z0,z1,z)
+#define cimg_for_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c)
+#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y)
+#define cimg_for_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y)
+#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Z(img,z0,z1,z)
+#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for9(bound,i) \
+  for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+       _n1##i = 1>=(int)(bound)?(int)(bound)-1:1, \
+       _n2##i = 2>=(int)(bound)?(int)(bound)-1:2, \
+       _n3##i = 3>=(int)(bound)?(int)(bound)-1:3, \
+       _n4##i = 4>=(int)(bound)?(int)(bound)-1:4; \
+       _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+       i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
+       _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for9X(img,x) cimg_for9((img)._width,x)
+#define cimg_for9Y(img,y) cimg_for9((img)._height,y)
+#define cimg_for9Z(img,z) cimg_for9((img)._depth,z)
+#define cimg_for9C(img,c) cimg_for9((img)._spectrum,c)
+#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x)
+#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x)
+#define cimg_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x)
+#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y)
+#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y)
+#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z)
+#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y)
+#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z)
+#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z)
+#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) cimg_for9XYZ(img,x,y,z)
+
+#define cimg_for_in9(bound,i0,i1,i) \
+  for (int i = (int)(i0)<0?0:(int)(i0), \
+       _p4##i = i-4<0?0:i-4, \
+       _p3##i = i-3<0?0:i-3, \
+       _p2##i = i-2<0?0:i-2, \
+       _p1##i = i-1<0?0:i-1, \
+       _n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
+       _n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
+       _n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
+       _n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
+       i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+       i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
+       _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img)._width,x0,x1,x)
+#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img)._height,y0,y1,y)
+#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img)._depth,z0,z1,z)
+#define cimg_for_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c)
+#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y)
+#define cimg_for_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y)
+#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Z(img,z0,z1,z)
+#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for2x2(img,x,y,z,c,I,T) \
+  cimg_for2((img)._height,y) for (int x = 0, \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(0,y,z,c)), \
+   (I[2] = (T)(img)(0,_n1##y,z,c)), \
+   1>=(img)._width?(img).width()-1:1);  \
+   (_n1##x<(img).width() && ( \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+   x==--_n1##x; \
+   I[0] = I[1], \
+   I[2] = I[3], \
+   ++x, ++_n1##x)
+
+#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+  cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(x,y,z,c)), \
+   (I[2] = (T)(img)(x,_n1##y,z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && (  \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+   x==--_n1##x); \
+   I[0] = I[1], \
+   I[2] = I[3], \
+   ++x, ++_n1##x)
+
+#define cimg_for3x3(img,x,y,z,c,I,T) \
+  cimg_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[0] = I[1] = (T)(img)(0,_p1##y,z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+   (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)),      \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+   x==--_n1##x; \
+   I[0] = I[1], I[1] = I[2], \
+   I[3] = I[4], I[4] = I[5], \
+   I[6] = I[7], I[7] = I[8], \
+   _p1##x = x++, ++_n1##x)
+
+#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+  cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[3] = (T)(img)(_p1##x,y,z,c)), \
+   (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[1] = (T)(img)(x,_p1##y,z,c)), \
+   (I[4] = (T)(img)(x,y,z,c)), \
+   (I[7] = (T)(img)(x,_n1##y,z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+   x==--_n1##x);            \
+   I[0] = I[1], I[1] = I[2], \
+   I[3] = I[4], I[4] = I[5], \
+   I[6] = I[7], I[7] = I[8], \
+   _p1##x = x++, ++_n1##x)
+
+#define cimg_for4x4(img,x,y,z,c,I,T) \
+  cimg_for4((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = (int)( \
+   (I[0] = I[1] = (T)(img)(0,_p1##y,z,c)), \
+   (I[4] = I[5] = (T)(img)(0,y,z,c)), \
+   (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \
+   (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[6] = (T)(img)(_n1##x,y,z,c)), \
+   (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   2>=(img)._width?(img).width()-1:2); \
+   (_n2##x<(img).width() && ( \
+   (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[7] = (T)(img)(_n2##x,y,z,c)), \
+   (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+   _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], \
+   I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+   I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+   I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+   _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+  cimg_for_in4((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = x+1>=(int)(img)._width?(img).width()-1:x+1, \
+   _n2##x = (int)( \
+   (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[4] = (T)(img)(_p1##x,y,z,c)), \
+   (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[1] = (T)(img)(x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(x,y,z,c)), \
+   (I[9] = (T)(img)(x,_n1##y,z,c)), \
+   (I[13] = (T)(img)(x,_n2##y,z,c)), \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[6] = (T)(img)(_n1##x,y,z,c)), \
+   (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   x+2>=(int)(img)._width?(img).width()-1:x+2); \
+   x<=(int)(x1) && ((_n2##x<(img).width() && ( \
+   (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[7] = (T)(img)(_n2##x,y,z,c)), \
+   (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+   _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], \
+   I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+   I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+   I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+   _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for5x5(img,x,y,z,c,I,T) \
+ cimg_for5((img)._height,y) for (int x = 0, \
+   _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = (int)( \
+   (I[0] = I[1] = I[2] = (T)(img)(0,_p2##y,z,c)), \
+   (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \
+   (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \
+   (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \
+   (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(_n1##x,y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_n2##y,z,c)),  \
+   2>=(img)._width?(img).width()-1:2); \
+   (_n2##x<(img).width() && ( \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n2##x,y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+   _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
+   I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
+   I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
+   I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
+   I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in5((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _p2##x = x-2<0?0:x-2, \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = x+1>=(int)(img)._width?(img).width()-1:x+1, \
+   _n2##x = (int)( \
+   (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[10] = (T)(img)(_p2##x,y,z,c)), \
+   (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[11] = (T)(img)(_p1##x,y,z,c)), \
+   (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[2] = (T)(img)(x,_p2##y,z,c)), \
+   (I[7] = (T)(img)(x,_p1##y,z,c)), \
+   (I[12] = (T)(img)(x,y,z,c)), \
+   (I[17] = (T)(img)(x,_n1##y,z,c)), \
+   (I[22] = (T)(img)(x,_n2##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(_n1##x,y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   x+2>=(int)(img)._width?(img).width()-1:x+2); \
+   x<=(int)(x1) && ((_n2##x<(img).width() && ( \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n2##x,y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+   _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
+   I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
+   I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
+   I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
+   I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for6x6(img,x,y,z,c,I,T) \
+ cimg_for6((img)._height,y) for (int x = 0, \
+   _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = 2>=(img)._width?(img).width()-1:2, \
+   _n3##x = (int)( \
+   (I[0] = I[1] = I[2] = (T)(img)(0,_p2##y,z,c)), \
+   (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \
+   (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \
+   (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \
+   (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \
+   (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[15] = (T)(img)(_n1##x,y,z,c)), \
+   (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[16] = (T)(img)(_n2##x,y,z,c)), \
+   (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   3>=(img)._width?(img).width()-1:3); \
+   (_n3##x<(img).width() && ( \
+   (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[17] = (T)(img)(_n3##x,y,z,c)), \
+   (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
+   I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+   I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
+   I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+  cimg_for_in6((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \
+   _p2##x = x-2<0?0:x-2, \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = x+1>=(int)(img)._width?(img).width()-1:x+1, \
+   _n2##x = x+2>=(int)(img)._width?(img).width()-1:x+2, \
+   _n3##x = (int)( \
+   (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[12] = (T)(img)(_p2##x,y,z,c)), \
+   (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(_p1##x,y,z,c)), \
+   (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[2] = (T)(img)(x,_p2##y,z,c)), \
+   (I[8] = (T)(img)(x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(x,y,z,c)), \
+   (I[20] = (T)(img)(x,_n1##y,z,c)), \
+   (I[26] = (T)(img)(x,_n2##y,z,c)), \
+   (I[32] = (T)(img)(x,_n3##y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[15] = (T)(img)(_n1##x,y,z,c)), \
+   (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[16] = (T)(img)(_n2##x,y,z,c)), \
+   (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   x+3>=(int)(img)._width?(img).width()-1:x+3); \
+   x<=(int)(x1) && ((_n3##x<(img).width() && ( \
+   (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[17] = (T)(img)(_n3##x,y,z,c)), \
+   (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
+   I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+   I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
+   I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+   _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for7x7(img,x,y,z,c,I,T) \
+  cimg_for7((img)._height,y) for (int x = 0, \
+   _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=(img)._width?(img).width()-1:1, \
+   _n2##x = 2>=(img)._width?(img).width()-1:2, \
+   _n3##x = (int)( \
+   (I[0] = I[1] = I[2] = I[3] = (T)(img)(0,_p3##y,z,c)), \
+   (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \
+   (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \
+   (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \
+   (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \
+   (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \
+   (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[25] = (T)(img)(_n1##x,y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[26] = (T)(img)(_n2##x,y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   3>=(img)._width?(img).width()-1:3); \
+   (_n3##x<(img).width() && ( \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[27] = (T)(img)(_n3##x,y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
+   I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
+   I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
+   I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
+   I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
+   I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
+   I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+  cimg_for_in7((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _p3##x = x-3<0?0:x-3, \
+   _p2##x = x-2<0?0:x-2, \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = x+1>=(int)(img)._width?(img).width()-1:x+1, \
+   _n2##x = x+2>=(int)(img)._width?(img).width()-1:x+2, \
+   _n3##x = (int)( \
+   (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \
+   (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \
+   (I[21] = (T)(img)(_p3##x,y,z,c)), \
+   (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \
+   (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \
+   (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \
+   (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \
+   (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[22] = (T)(img)(_p2##x,y,z,c)), \
+   (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \
+   (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[23] = (T)(img)(_p1##x,y,z,c)), \
+   (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[3] = (T)(img)(x,_p3##y,z,c)), \
+   (I[10] = (T)(img)(x,_p2##y,z,c)), \
+   (I[17] = (T)(img)(x,_p1##y,z,c)), \
+   (I[24] = (T)(img)(x,y,z,c)), \
+   (I[31] = (T)(img)(x,_n1##y,z,c)), \
+   (I[38] = (T)(img)(x,_n2##y,z,c)), \
+   (I[45] = (T)(img)(x,_n3##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[25] = (T)(img)(_n1##x,y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[26] = (T)(img)(_n2##x,y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   x+3>=(int)(img)._width?(img).width()-1:x+3); \
+   x<=(int)(x1) && ((_n3##x<(img).width() && ( \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[27] = (T)(img)(_n3##x,y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+   _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
+   I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
+   I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
+   I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
+   I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
+   I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
+   I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for8x8(img,x,y,z,c,I,T) \
+  cimg_for8((img)._height,y) for (int x = 0, \
+   _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=((img)._width)?(img).width()-1:1, \
+   _n2##x = 2>=((img)._width)?(img).width()-1:2, \
+   _n3##x = 3>=((img)._width)?(img).width()-1:3, \
+   _n4##x = (int)( \
+   (I[0] = I[1] = I[2] = I[3] = (T)(img)(0,_p3##y,z,c)), \
+   (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \
+   (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \
+   (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \
+   (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \
+   (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \
+   (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \
+   (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[28] = (T)(img)(_n1##x,y,z,c)), \
+   (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[29] = (T)(img)(_n2##x,y,z,c)), \
+   (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[30] = (T)(img)(_n3##x,y,z,c)), \
+   (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   4>=((img)._width)?(img).width()-1:4); \
+   (_n4##x<(img).width() && ( \
+   (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[31] = (T)(img)(_n4##x,y,z,c)), \
+   (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+   I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+   I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
+   I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
+   I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
+   I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
+   I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
+   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+  cimg_for_in8((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _p3##x = x-3<0?0:x-3, \
+   _p2##x = x-2<0?0:x-2, \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = x+1>=(img).width()?(img).width()-1:x+1, \
+   _n2##x = x+2>=(img).width()?(img).width()-1:x+2, \
+   _n3##x = x+3>=(img).width()?(img).width()-1:x+3, \
+   _n4##x = (int)( \
+   (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \
+   (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \
+   (I[24] = (T)(img)(_p3##x,y,z,c)), \
+   (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \
+   (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \
+   (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \
+   (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \
+   (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \
+   (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[25] = (T)(img)(_p2##x,y,z,c)), \
+   (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \
+   (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \
+   (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[26] = (T)(img)(_p1##x,y,z,c)), \
+   (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \
+   (I[3] = (T)(img)(x,_p3##y,z,c)), \
+   (I[11] = (T)(img)(x,_p2##y,z,c)), \
+   (I[19] = (T)(img)(x,_p1##y,z,c)), \
+   (I[27] = (T)(img)(x,y,z,c)), \
+   (I[35] = (T)(img)(x,_n1##y,z,c)), \
+   (I[43] = (T)(img)(x,_n2##y,z,c)), \
+   (I[51] = (T)(img)(x,_n3##y,z,c)), \
+   (I[59] = (T)(img)(x,_n4##y,z,c)), \
+   (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[28] = (T)(img)(_n1##x,y,z,c)), \
+   (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[29] = (T)(img)(_n2##x,y,z,c)), \
+   (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[30] = (T)(img)(_n3##x,y,z,c)), \
+   (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   x+4>=(img).width()?(img).width()-1:x+4); \
+   x<=(int)(x1) && ((_n4##x<(img).width() && ( \
+   (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[31] = (T)(img)(_n4##x,y,z,c)), \
+   (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+   I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+   I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+   I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
+   I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
+   I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
+   I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
+   I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
+   _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for9x9(img,x,y,z,c,I,T) \
+  cimg_for9((img)._height,y) for (int x = 0, \
+   _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+   _n1##x = 1>=((img)._width)?(img).width()-1:1, \
+   _n2##x = 2>=((img)._width)?(img).width()-1:2, \
+   _n3##x = 3>=((img)._width)?(img).width()-1:3, \
+   _n4##x = (int)( \
+   (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(0,_p4##y,z,c)), \
+   (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \
+   (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \
+   (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \
+   (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \
+   (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \
+   (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \
+   (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \
+   (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[41] = (T)(img)(_n1##x,y,z,c)), \
+   (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[42] = (T)(img)(_n2##x,y,z,c)), \
+   (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \
+   (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[43] = (T)(img)(_n3##x,y,z,c)), \
+   (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   4>=((img)._width)?(img).width()-1:4); \
+   (_n4##x<(img).width() && ( \
+   (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \
+   (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[44] = (T)(img)(_n4##x,y,z,c)), \
+   (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
+   I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
+   I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+   I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
+   I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
+   I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
+   I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
+   I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
+   _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+  cimg_for_in9((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _p4##x = x-4<0?0:x-4, \
+   _p3##x = x-3<0?0:x-3, \
+   _p2##x = x-2<0?0:x-2, \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = x+1>=(img).width()?(img).width()-1:x+1, \
+   _n2##x = x+2>=(img).width()?(img).width()-1:x+2, \
+   _n3##x = x+3>=(img).width()?(img).width()-1:x+3, \
+   _n4##x = (int)( \
+   (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \
+   (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \
+   (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \
+   (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \
+   (I[36] = (T)(img)(_p4##x,y,z,c)), \
+   (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \
+   (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \
+   (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \
+   (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \
+   (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \
+   (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \
+   (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \
+   (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \
+   (I[37] = (T)(img)(_p3##x,y,z,c)), \
+   (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \
+   (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \
+   (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \
+   (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \
+   (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \
+   (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \
+   (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \
+   (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \
+   (I[38] = (T)(img)(_p2##x,y,z,c)), \
+   (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \
+   (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \
+   (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \
+   (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \
+   (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \
+   (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \
+   (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \
+   (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[39] = (T)(img)(_p1##x,y,z,c)), \
+   (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \
+   (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \
+   (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \
+   (I[4] = (T)(img)(x,_p4##y,z,c)), \
+   (I[13] = (T)(img)(x,_p3##y,z,c)), \
+   (I[22] = (T)(img)(x,_p2##y,z,c)), \
+   (I[31] = (T)(img)(x,_p1##y,z,c)), \
+   (I[40] = (T)(img)(x,y,z,c)), \
+   (I[49] = (T)(img)(x,_n1##y,z,c)), \
+   (I[58] = (T)(img)(x,_n2##y,z,c)), \
+   (I[67] = (T)(img)(x,_n3##y,z,c)), \
+   (I[76] = (T)(img)(x,_n4##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \
+   (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \
+   (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[41] = (T)(img)(_n1##x,y,z,c)), \
+   (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \
+   (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \
+   (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \
+   (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \
+   (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \
+   (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \
+   (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \
+   (I[42] = (T)(img)(_n2##x,y,z,c)), \
+   (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \
+   (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \
+   (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \
+   (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \
+   (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \
+   (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \
+   (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \
+   (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \
+   (I[43] = (T)(img)(_n3##x,y,z,c)), \
+   (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \
+   (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \
+   (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \
+   (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \
+   x+4>=(img).width()?(img).width()-1:x+4); \
+   x<=(int)(x1) && ((_n4##x<(img).width() && ( \
+   (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \
+   (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \
+   (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \
+   (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \
+   (I[44] = (T)(img)(_n4##x,y,z,c)), \
+   (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \
+   (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \
+   (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \
+   (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+   _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
+   I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
+   I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+   I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
+   I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+   I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
+   I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
+   I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
+   I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
+   I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
+   _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for2x2x2(img,x,y,z,c,I,T) \
+ cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(0,y,z,c)), \
+   (I[2] = (T)(img)(0,_n1##y,z,c)), \
+   (I[4] = (T)(img)(0,y,_n1##z,c)), \
+   (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+   x==--_n1##x; \
+   I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
+   ++x, ++_n1##x)
+
+#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \
+ cimg_for_in2((img)._depth,z0,z1,z) cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(x,y,z,c)), \
+   (I[2] = (T)(img)(x,_n1##y,z,c)), \
+   (I[4] = (T)(img)(x,y,_n1##z,c)), \
+   (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+   (I[1] = (T)(img)(_n1##x,y,z,c)), \
+   (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+   x==--_n1##x); \
+   I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
+   ++x, ++_n1##x)
+
+#define cimg_for3x3x3(img,x,y,z,c,I,T) \
+ cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[0] = I[1] = (T)(img)(0,_p1##y,_p1##z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)),  \
+   (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \
+   (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \
+   (I[12] = I[13] = (T)(img)(0,y,z,c)), \
+   (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \
+   (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \
+   (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \
+   (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,y,z,c)), \
+   (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \
+   (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+   x==--_n1##x; \
+   I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
+   I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
+   I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
+   _p1##x = x++, ++_n1##x)
+
+#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \
+ cimg_for_in3((img)._depth,z0,z1,z) cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+   _p1##x = x-1<0?0:x-1, \
+   _n1##x = (int)( \
+   (I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \
+   (I[3] = (T)(img)(_p1##x,y,_p1##z,c)),  \
+   (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \
+   (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \
+   (I[12] = (T)(img)(_p1##x,y,z,c)), \
+   (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \
+   (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \
+   (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \
+   (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \
+   (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \
+   (I[4] = (T)(img)(x,y,_p1##z,c)),  \
+   (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \
+   (I[10] = (T)(img)(x,_p1##y,z,c)), \
+   (I[13] = (T)(img)(x,y,z,c)), \
+   (I[16] = (T)(img)(x,_n1##y,z,c)), \
+   (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \
+   (I[22] = (T)(img)(x,y,_n1##z,c)), \
+   (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \
+   x+1>=(int)(img)._width?(img).width()-1:x+1); \
+   x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \
+   (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[14] = (T)(img)(_n1##x,y,z,c)), \
+   (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \
+   (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \
+   (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \
+   (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+   x==--_n1##x); \
+   I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
+   I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
+   I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
+   _p1##x = x++, ++_n1##x)
+
+#define cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l)
+#define cimglist_for_in(list,l0,l1,l) \
+  for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width-1; l<=_max##l; ++l)
+
+#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn
+
+// Define macros used when exceptions are thrown.
+#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::"
+#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']'
+#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::"
+#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type()
+#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::"
+#define cimglist_instance _width,_allocated_width,_data,pixel_type()
+
+/*------------------------------------------------
+ #
+ #
+ #  Definition of the cimg_library:: namespace
+ #
+ #
+ -------------------------------------------------*/
+//! This namespace encompasses all classes and functions of the %CImg library.
+/**
+   This namespace is defined to avoid functions and class names collisions
+   that could happen with the include of other C++ header files.
+   Anyway, it should not happen often and you should reasonnably start most of your
+   %CImg-based programs with
+   \code
+   #include "CImg.h"
+   using namespace cimg_library;
+   \endcode
+   to simplify the declaration of %CImg Library variables afterwards.
+**/
+namespace cimg_library {
+
+  // Declare the only four classes of the CImg Library.
+  template<typename T=float> struct CImg;
+  template<typename T=float> struct CImgList;
+  struct CImgDisplay;
+  struct CImgException;
+
+  // (Pre)declare the cimg namespace.
+  // This is not the complete namespace declaration. It only contains some
+  // necessary stuffs to ensure a correct declaration order of classes and functions
+  // defined afterwards.
+  namespace cimg {
+
+#ifdef cimg_use_vt100
+    const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 };
+    const char t_red[] = { 0x1b, '[', '4', ';', '3', '1', ';', '5', '9', 'm', 0 };
+    const char t_bold[] = { 0x1b, '[', '1', 'm', 0 };
+    const char t_purple[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 };
+    const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 };
+#else
+    const char t_normal[] = { 0 };
+    const char *const t_red = cimg::t_normal, *const t_bold = cimg::t_normal,
+      *const t_purple = cimg::t_normal, *const t_green = cimg::t_normal;
+#endif
+
+    inline std::FILE* output(std::FILE *file=0);
+    inline void info();
+
+    // Function used to avoid warning messages due to unused parameters.
+    template<typename T>
+    inline void unused(const T&, ...) {}
+
+    //! Get/set the current CImg exception mode.
+    /**
+       The way error messages are handled by CImg can be changed dynamically, using this function.
+       Possible values are :
+       - '0' to hide library messages (quiet mode).
+       - '1' to print library messages on the console.
+       - '2' to display library messages on a dialog window (default behavior).
+       - '3' to do as '1' + add extra warnings (may slow down the code !).
+       - '4' to do as '2' + add extra warnings (may slow down the code !).
+     **/
+    inline unsigned int& _exception_mode(const unsigned int value, const bool is_set) {
+      static unsigned int mode = cimg_verbosity;
+      if (is_set) mode = value;
+      return mode;
+    }
+    inline unsigned int& exception_mode() {
+      return _exception_mode(0,false);
+    }
+    inline unsigned int& exception_mode(const unsigned int mode) {
+      return _exception_mode(mode,true);
+    }
+
+    inline int dialog(const char *const title, const char *const msg, const char *const button1_label="OK",
+                      const char *const button2_label=0, const char *const button3_label=0,
+                      const char *const button4_label=0, const char *const button5_label=0,
+                      const char *const button6_label=0, const bool centering=false);
+
+    //! Evaluate math expression.
+    inline double eval(const char *const expression, const double x=0, const double y=0, const double z=0, const double v=0);
+  }
+
+  /*----------------------------------------------
+   #
+   # Definition of the CImgException structures
+   #
+   ----------------------------------------------*/
+  //! Instances of this class are thrown when errors occur during a %CImg library function call.
+  /**
+     \section ex1 Overview
+
+      CImgException is the base class of %CImg exceptions.
+      Exceptions are thrown by the %CImg Library when an error occured in a %CImg library function call.
+      CImgException is seldom thrown itself. Children classes that specify the kind of error encountered
+      are generally used instead. These sub-classes are :
+
+      - \b CImgInstanceException : Thrown when the instance associated to the called %CImg function is not
+      correctly defined. Generally, this exception is thrown when one tries to process \a empty images. The example
+      below will throw a \a CImgInstanceException.
+      \code
+      CImg<float> img;        // Construct an empty image.
+      img.blur(10);           // Try to blur the image.
+      \endcode
+
+      - \b CImgArgumentException : Thrown when one of the arguments given to the called %CImg function is not correct.
+      Generally, this exception is thrown when arguments passed to the function are outside an admissible range of values.
+      The example below will throw a \a CImgArgumentException.
+      \code
+      CImg<float> img(100,100,1,3);   // Define a 100x100 color image with float pixels.
+      img = 0;                     // Try to fill pixels from the 0 pointer (invalid argument to operator=() ).
+      \endcode
+
+      - \b CImgIOException : Thrown when an error occured when trying to load or save image files.
+      The example below will throw a \a CImgIOException.
+      \code
+      CImg<float> img("file_doesnt_exist.jpg");    // Try to load a file that doesn't exist.
+      \endcode
+
+      - \b CImgDisplayException : Thrown when an error occured when trying to display an image in a window.
+      This exception is thrown when image display request cannot be satisfied.
+
+      The parent class CImgException may be thrown itself when errors that cannot be classified in one of
+      the above type occur. It is recommended not to throw CImgExceptions yourself, since there are normally
+      reserved to %CImg Library functions.
+      \b CImgInstanceException, \b CImgArgumentException, \b CImgIOException and \b CImgDisplayException are simple
+      subclasses of CImgException and are thus not detailled more in this reference documentation.
+
+      \section ex2 Exception handling
+
+      When an error occurs, the %CImg Library first displays the error in a modal window.
+      Then, it throws an instance of the corresponding exception class, generally leading the program to stop
+      (this is the default behavior).
+      You can bypass this default behavior by handling the exceptions yourself,
+      using a code block <tt>try { ... } catch () { ... }</tt>.
+      In this case, you can avoid the apparition of the modal window, by
+      defining the environment variable <tt>cimg_verbosity</tt> to 0 before including the %CImg header file.
+      The example below shows how to cleanly handle %CImg Library exceptions :
+      \code
+      #define cimg_verbosity 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.what());  // Display your own error message
+          ...                                                       // Do what you want now.
+        }
+      }
+      \endcode
+  **/
+  struct CImgException : public std::exception {
+#define _cimg_exception_err(etype,disp_flag) \
+  std::va_list ap; va_start(ap,format); cimg_vsnprintf(_message,sizeof(_message),format,ap); va_end(ap); \
+  if (cimg::exception_mode()) { \
+    std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \
+    if (cimg_display && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } catch (CImgException&) {} \
+    if (cimg::exception_mode()>=3) cimg_library::cimg::info(); \
+  }
+
+    char _message[16384];
+    CImgException() { *_message = 0; }
+    CImgException(const char *const format, ...) { _cimg_exception_err("CImgException",true); }
+    const char *what() const throw() { return _message; }
+  };
+
+  // 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 *const 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 *const 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 *const format, ...) { _cimg_exception_err("CImgIOException",true); }
+  };
+
+  // The CImgDisplayException class is used to throw an exception related to display problems
+  // encountered in a library function call.
+  struct CImgDisplayException : public CImgException {
+    CImgDisplayException(const char *const format, ...) {
+      const unsigned int current_mode = cimg::exception_mode();
+      cimg::exception_mode(current_mode==2?1:current_mode==4?3:current_mode);
+      _cimg_exception_err("CImgDisplayException",false);
+    }
+  };
+
+  // The CImgWarningException class is used to throw an exception for warnings
+  // encountered in a library function call.
+  struct CImgWarningException : public CImgException {
+    CImgWarningException(const char *const format, ...) { _cimg_exception_err("CImgWarningException",false); }
+  };
+
+  /*-------------------------------------
+   #
+   # Definition of the namespace 'cimg'
+   #
+   --------------------------------------*/
+  //! Namespace that encompasses \a low-level functions and variables of the %CImg Library.
+  /**
+     Most of the functions and variables within this namespace are used by the library for low-level processing.
+     Nevertheless, documented variables and functions of this namespace may be used safely in your own source code.
+
+     \warning Never write <tt>using namespace cimg_library::cimg;</tt> in your source code, since a lot of functions of the
+     <tt>cimg::</tt> namespace have prototypes similar to standard C functions that could defined in the global namespace <tt>::</tt>.
+  **/
+  namespace cimg {
+
+    // Define the traits that will be used to determine the best data type to work with.
+    //
+    template<typename T> struct type {
+      static const char* string() {
+        static const char* s[] = { "unknown",   "unknown8",   "unknown16",  "unknown24",
+                                   "unknown32", "unknown40",  "unknown48",  "unknown56",
+                                   "unknown64", "unknown72",  "unknown80",  "unknown88",
+                                   "unknown96", "unknown104", "unknown112", "unknown120",
+                                   "unknown128" };
+        return s[(sizeof(T)<17)?sizeof(T):0];
+      }
+      static unsigned int id() { return 0U; }
+      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 T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; }
+      static const char* format() { return "%s"; }
+      static const char* format(const T val) { static const char *const s = "unknown"; return s; }
+    };
+
+    template<> struct type<bool> {
+      static const char* string() { static const char *const s = "bool"; return s; }
+      static unsigned int id() { return 1U; }
+      static bool is_float() { return false; }
+      static bool min() { return false; }
+      static bool max() { return true; }
+      static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; }
+      static const char* format() { return "%s"; }
+      static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; }
+    };
+
+    template<> struct type<unsigned char> {
+      static const char* string() { static const char *const s = "unsigned char"; return s; }
+      static unsigned int id() { return 2U; }
+      static bool is_float() { return false; }
+      static unsigned char min() { return 0; }
+      static unsigned char max() { return (unsigned char)~0U; }
+      static unsigned char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; }
+      static const char* format() { return "%u"; }
+      static unsigned int format(const unsigned char val) { return (unsigned int)val; }
+    };
+
+    template<> struct type<char> {
+      static const char* string() { static const char *const s = "char"; return s; }
+      static unsigned int id() { return 3U; }
+      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 char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; }
+      static const char* format() { return "%d"; }
+      static int format(const char val) { return (int)val; }
+    };
+
+    template<> struct type<signed char> {
+      static const char* string() { static const char *const s = "signed char"; return s; }
+      static unsigned int id() { return 4U; }
+      static bool is_float() { return false; }
+      static signed char min() { return (signed char)(-1L<<(8*sizeof(signed char)-1)); }
+      static signed char max() { return ~((signed char)(-1L<<(8*sizeof(signed char)-1))); }
+      static signed char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(signed char)val; }
+      static const char* format() { return "%d"; }
+      static unsigned int format(const signed char val) { return (int)val; }
+    };
+
+    template<> struct type<unsigned short> {
+      static const char* string() { static const char *const s = "unsigned short"; return s; }
+      static unsigned int id() { return 5U; }
+      static bool is_float() { return false; }
+      static unsigned short min() { return 0; }
+      static unsigned short max() { return (unsigned short)~0U; }
+      static unsigned short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; }
+      static const char* format() { return "%u"; }
+      static unsigned int format(const unsigned short val) { return (unsigned int)val; }
+    };
+
+    template<> struct type<short> {
+      static const char* string() { static const char *const s = "short"; return s; }
+      static unsigned int id() { return 6U; }
+      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 short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; }
+      static const char* format() { return "%d"; }
+      static int format(const short val) { return (int)val; }
+    };
+
+    template<> struct type<unsigned int> {
+      static const char* string() { static const char *const s = "unsigned int"; return s; }
+      static unsigned int id() { return 7U; }
+      static bool is_float() { return false; }
+      static unsigned int min() { return 0; }
+      static unsigned int max() { return (unsigned int)~0U; }
+      static unsigned int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; }
+      static const char* format() { return "%u"; }
+      static unsigned int format(const unsigned int val) { return val; }
+    };
+
+    template<> struct type<int> {
+      static const char* string() { static const char *const s = "int"; return s; }
+      static unsigned int id() { return 8U; }
+      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 int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; }
+      static const char* format() { return "%d"; }
+      static int format(const int val) { return val; }
+    };
+
+    template<> struct type<unsigned long> {
+      static const char* string() { static const char *const s = "unsigned long"; return s; }
+      static unsigned int id() { return 9U; }
+      static bool is_float() { return false; }
+      static unsigned long min() { return 0; }
+      static unsigned long max() { return (unsigned long)~0UL; }
+      static unsigned long cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned long)val; }
+      static const char* format() { return "%lu"; }
+      static unsigned long format(const unsigned long val) { return val; }
+    };
+
+    template<> struct type<long> {
+      static const char* string() { static const char *const s = "long"; return s; }
+      static unsigned int id() { return 10U; }
+      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 long cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(long)val; }
+      static const char* format() { return "%ld"; }
+      static long format(const long val) { return val; }
+    };
+
+    template<> struct type<double> {
+      static const char* string() { static const char *const s = "double"; return s; }
+      static unsigned int id() { return 11U; }
+      static bool is_float() { return true; }
+      static double min() { return -1.7E308; }
+      static double max() { return  1.7E308; }
+      static double cut(const double val) { return val<min()?min():val>max()?max():val; }
+      static double inf() { return max()*max(); }
+      static double nan() { static const double v_nan = std::sqrt(-1.0); return v_nan; }
+      static const char* format() { return "%g"; }
+      static double format(const double val) { return val; }
+    };
+
+    template<> struct type<float> {
+      static const char* string() { static const char *const s = "float"; return s; }
+      static unsigned int id() { return 12U; }
+      static bool is_float() { return true; }
+      static float min() { return -3.4E38f; }
+      static float max() { return  3.4E38f; }
+      static float cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(float)val; }
+      static float inf() { return (float)cimg::type<double>::inf(); }
+      static float nan() { return (float)cimg::type<double>::nan(); }
+      static const char* format() { return "%g"; }
+      static double format(const float val) { return (double)val; }
+    };
+
+    template<typename T, typename t> struct superset { typedef T type; };
+    template<> struct superset<bool,unsigned char> { typedef unsigned char type; };
+    template<> struct superset<bool,char> { typedef char type; };
+    template<> struct superset<bool,signed char> { typedef signed char type; };
+    template<> struct superset<bool,unsigned short> { typedef unsigned short type; };
+    template<> struct superset<bool,short> { typedef short type; };
+    template<> struct superset<bool,unsigned int> { typedef unsigned int type; };
+    template<> struct superset<bool,int> { typedef int type; };
+    template<> struct superset<bool,unsigned long> { typedef unsigned long type; };
+    template<> struct superset<bool,long> { typedef long type; };
+    template<> struct superset<bool,float> { typedef float type; };
+    template<> struct superset<bool,double> { typedef double type; };
+    template<> struct superset<unsigned char,char> { typedef short type; };
+    template<> struct superset<unsigned char,signed char> { typedef short type; };
+    template<> struct superset<unsigned char,unsigned short> { typedef unsigned short type; };
+    template<> struct superset<unsigned char,short> { typedef short type; };
+    template<> struct superset<unsigned char,unsigned int> { typedef unsigned int type; };
+    template<> struct superset<unsigned char,int> { typedef int type; };
+    template<> struct superset<unsigned char,unsigned long> { typedef unsigned long type; };
+    template<> struct superset<unsigned char,long> { typedef long type; };
+    template<> struct superset<unsigned char,float> { typedef float type; };
+    template<> struct superset<unsigned char,double> { typedef double type; };
+    template<> struct superset<signed char,unsigned char> { typedef short type; };
+    template<> struct superset<signed char,char> { typedef short type; };
+    template<> struct superset<signed char,unsigned short> { typedef int type; };
+    template<> struct superset<signed char,short> { typedef short type; };
+    template<> struct superset<signed char,unsigned int> { typedef long type; };
+    template<> struct superset<signed char,int> { typedef int type; };
+    template<> struct superset<signed char,unsigned long> { typedef long type; };
+    template<> struct superset<signed char,long> { typedef long type; };
+    template<> struct superset<signed char,float> { typedef float type; };
+    template<> struct superset<signed char,double> { typedef double type; };
+    template<> struct superset<char,unsigned char> { typedef short type; };
+    template<> struct superset<char,signed char> { typedef short type; };
+    template<> struct superset<char,unsigned short> { typedef int type; };
+    template<> struct superset<char,short> { typedef short type; };
+    template<> struct superset<char,unsigned int> { typedef long type; };
+    template<> struct superset<char,int> { typedef int type; };
+    template<> struct superset<char,unsigned long> { typedef long type; };
+    template<> struct superset<char,long> { typedef long type; };
+    template<> struct superset<char,float> { typedef float type; };
+    template<> struct superset<char,double> { typedef double type; };
+    template<> struct superset<unsigned short,char> { typedef int type; };
+    template<> struct superset<unsigned short,signed char> { typedef int type; };
+    template<> struct superset<unsigned short,short> { typedef int type; };
+    template<> struct superset<unsigned short,unsigned int> { typedef unsigned int type; };
+    template<> struct superset<unsigned short,int> { typedef int type; };
+    template<> struct superset<unsigned short,unsigned long> { typedef unsigned long type; };
+    template<> struct superset<unsigned short,long> { typedef long type; };
+    template<> struct superset<unsigned short,float> { typedef float type; };
+    template<> struct superset<unsigned short,double> { typedef double type; };
+    template<> struct superset<short,unsigned short> { typedef int type; };
+    template<> struct superset<short,unsigned int> { typedef long type; };
+    template<> struct superset<short,int> { typedef int type; };
+    template<> struct superset<short,unsigned long> { typedef long type; };
+    template<> struct superset<short,long> { typedef long type; };
+    template<> struct superset<short,float> { typedef float type; };
+    template<> struct superset<short,double> { typedef double type; };
+    template<> struct superset<unsigned int,char> { typedef long type; };
+    template<> struct superset<unsigned int,signed char> { typedef long type; };
+    template<> struct superset<unsigned int,short> { typedef long type; };
+    template<> struct superset<unsigned int,int> { typedef long type; };
+    template<> struct superset<unsigned int,unsigned long> { typedef unsigned long type; };
+    template<> struct superset<unsigned int,long> { typedef long type; };
+    template<> struct superset<unsigned int,float> { typedef float type; };
+    template<> struct superset<unsigned int,double> { typedef double type; };
+    template<> struct superset<int,unsigned int> { typedef long type; };
+    template<> struct superset<int,unsigned long> { typedef long type; };
+    template<> struct superset<int,long> { typedef long type; };
+    template<> struct superset<int,float> { typedef float type; };
+    template<> struct superset<int,double> { typedef double type; };
+    template<> struct superset<unsigned long,char> { typedef long type; };
+    template<> struct superset<unsigned long,signed char> { typedef long type; };
+    template<> struct superset<unsigned long,short> { typedef long type; };
+    template<> struct superset<unsigned long,int> { typedef long type; };
+    template<> struct superset<unsigned long,long> { typedef long type; };
+    template<> struct superset<unsigned long,float> { typedef float type; };
+    template<> struct superset<unsigned long,double> { typedef double type; };
+    template<> struct superset<long,float> { typedef float type; };
+    template<> struct superset<long,double> { typedef double type; };
+    template<> struct superset<float,double> { typedef double type; };
+
+    template<typename t1, typename t2, typename t3> struct superset2 {
+      typedef typename superset<t1, typename superset<t2,t3>::type>::type type;
+    };
+
+    template<typename t1, typename t2, typename t3, typename t4> struct superset3 {
+      typedef typename superset<t1, typename superset2<t2,t3,t4>::type>::type type;
+    };
+
+    template<typename t1, typename t2> struct last { typedef t2 type; };
+
+#define _cimg_Tt      typename cimg::superset<T,t>::type
+#define _cimg_Tfloat  typename cimg::superset<T,float>::type
+#define _cimg_Ttfloat typename cimg::superset2<T,t,float>::type
+
+    // Define internal library variables.
+#if cimg_display==1
+    struct X11_info {
+      volatile unsigned int nb_wins;
+      pthread_t*       event_thread;
+      CImgDisplay*     wins[1024];
+      Display*         display;
+      unsigned int     nb_bits;
+      GC*              gc;
+      bool             is_blue_first;
+      bool             is_shm_enabled;
+      bool             byte_order;
+#ifdef cimg_use_xrandr
+      XRRScreenSize *resolutions;
+      Rotation curr_rotation;
+      unsigned int curr_resolution;
+      unsigned int nb_resolutions;
+#endif
+      X11_info():nb_wins(0),event_thread(0),display(0),
+                 nb_bits(0),gc(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) {
+#ifdef cimg_use_xrandr
+        resolutions = 0;
+        curr_rotation = 0;
+        curr_resolution = nb_resolutions = 0;
+#endif
+      }
+    };
+#if defined(cimg_module)
+    X11_info& X11_attr();
+#elif defined(cimg_main)
+    X11_info& X11_attr() { static X11_info val; return val; }
+#else
+    inline X11_info& X11_attr() { static X11_info val; return val; }
+#endif
+
+#elif cimg_display==2
+    struct Win32_info {
+      HANDLE wait_event;
+      Win32_info() { wait_event = CreateEvent(0,FALSE,FALSE,0); }
+    };
+#if defined(cimg_module)
+    Win32_info& Win32_attr();
+#elif defined(cimg_main)
+    Win32_info& Win32_attr() { static Win32_info val; return val; }
+#else
+    inline Win32_info& Win32_attr() { static Win32_info val; return val; }
+#endif
+#endif
+
+#if defined(cimg_use_magick) && defined(InitializeMagick)
+    static struct Magick_info {
+      Magick_info() {
+        Magick::InitializeMagick("");
+      }
+      ~Magick_info() {
+        MagickLib::DestroyMagick();
+      }
+    } _Magick_info;
+#endif
+
+#if cimg_display==1
+    // Keycodes for X11-based graphical systems.
+    const unsigned int keyESC        = XK_Escape;
+    const unsigned int keyF1         = XK_F1;
+    const unsigned int keyF2         = XK_F2;
+    const unsigned int keyF3         = XK_F3;
+    const unsigned int keyF4         = XK_F4;
+    const unsigned int keyF5         = XK_F5;
+    const unsigned int keyF6         = XK_F6;
+    const unsigned int keyF7         = XK_F7;
+    const unsigned int keyF8         = XK_F8;
+    const unsigned int keyF9         = XK_F9;
+    const unsigned int keyF10        = XK_F10;
+    const unsigned int keyF11        = XK_F11;
+    const unsigned int keyF12        = XK_F12;
+    const unsigned int keyPAUSE      = XK_Pause;
+    const unsigned int key1          = XK_1;
+    const unsigned int key2          = XK_2;
+    const unsigned int key3          = XK_3;
+    const unsigned int key4          = XK_4;
+    const unsigned int key5          = XK_5;
+    const unsigned int key6          = XK_6;
+    const unsigned int key7          = XK_7;
+    const unsigned int key8          = XK_8;
+    const unsigned int key9          = XK_9;
+    const unsigned int key0          = XK_0;
+    const unsigned int keyBACKSPACE  = XK_BackSpace;
+    const unsigned int keyINSERT     = XK_Insert;
+    const unsigned int keyHOME       = XK_Home;
+    const unsigned int keyPAGEUP     = XK_Page_Up;
+    const unsigned int keyTAB        = XK_Tab;
+    const unsigned int keyQ          = XK_q;
+    const unsigned int keyW          = XK_w;
+    const unsigned int keyE          = XK_e;
+    const unsigned int keyR          = XK_r;
+    const unsigned int keyT          = XK_t;
+    const unsigned int keyY          = XK_y;
+    const unsigned int keyU          = XK_u;
+    const unsigned int keyI          = XK_i;
+    const unsigned int keyO          = XK_o;
+    const unsigned int keyP          = XK_p;
+    const unsigned int keyDELETE     = XK_Delete;
+    const unsigned int keyEND        = XK_End;
+    const unsigned int keyPAGEDOWN   = XK_Page_Down;
+    const unsigned int keyCAPSLOCK   = XK_Caps_Lock;
+    const unsigned int keyA          = XK_a;
+    const unsigned int keyS          = XK_s;
+    const unsigned int keyD          = XK_d;
+    const unsigned int keyF          = XK_f;
+    const unsigned int keyG          = XK_g;
+    const unsigned int keyH          = XK_h;
+    const unsigned int keyJ          = XK_j;
+    const unsigned int keyK          = XK_k;
+    const unsigned int keyL          = XK_l;
+    const unsigned int keyENTER      = XK_Return;
+    const unsigned int keySHIFTLEFT  = XK_Shift_L;
+    const unsigned int keyZ          = XK_z;
+    const unsigned int keyX          = XK_x;
+    const unsigned int keyC          = XK_c;
+    const unsigned int keyV          = XK_v;
+    const unsigned int keyB          = XK_b;
+    const unsigned int keyN          = XK_n;
+    const unsigned int keyM          = XK_m;
+    const unsigned int keySHIFTRIGHT = XK_Shift_R;
+    const unsigned int keyARROWUP    = XK_Up;
+    const unsigned int keyCTRLLEFT   = XK_Control_L;
+    const unsigned int keyAPPLEFT    = XK_Super_L;
+    const unsigned int keyALT        = XK_Alt_L;
+    const unsigned int keySPACE      = XK_space;
+    const unsigned int keyALTGR      = XK_Alt_R;
+    const unsigned int keyAPPRIGHT   = XK_Super_R;
+    const unsigned int keyMENU       = XK_Menu;
+    const unsigned int keyCTRLRIGHT  = XK_Control_R;
+    const unsigned int keyARROWLEFT  = XK_Left;
+    const unsigned int keyARROWDOWN  = XK_Down;
+    const unsigned int keyARROWRIGHT = XK_Right;
+    const unsigned int keyPAD0       = XK_KP_0;
+    const unsigned int keyPAD1       = XK_KP_1;
+    const unsigned int keyPAD2       = XK_KP_2;
+    const unsigned int keyPAD3       = XK_KP_3;
+    const unsigned int keyPAD4       = XK_KP_4;
+    const unsigned int keyPAD5       = XK_KP_5;
+    const unsigned int keyPAD6       = XK_KP_6;
+    const unsigned int keyPAD7       = XK_KP_7;
+    const unsigned int keyPAD8       = XK_KP_8;
+    const unsigned int keyPAD9       = XK_KP_9;
+    const unsigned int keyPADADD     = XK_KP_Add;
+    const unsigned int keyPADSUB     = XK_KP_Subtract;
+    const unsigned int keyPADMUL     = XK_KP_Multiply;
+    const unsigned int keyPADDIV     = XK_KP_Divide;
+
+#elif cimg_display==2
+    // Keycodes for Windows.
+    const unsigned int keyESC        = VK_ESCAPE;
+    const unsigned int keyF1         = VK_F1;
+    const unsigned int keyF2         = VK_F2;
+    const unsigned int keyF3         = VK_F3;
+    const unsigned int keyF4         = VK_F4;
+    const unsigned int keyF5         = VK_F5;
+    const unsigned int keyF6         = VK_F6;
+    const unsigned int keyF7         = VK_F7;
+    const unsigned int keyF8         = VK_F8;
+    const unsigned int keyF9         = VK_F9;
+    const unsigned int keyF10        = VK_F10;
+    const unsigned int keyF11        = VK_F11;
+    const unsigned int keyF12        = VK_F12;
+    const unsigned int keyPAUSE      = VK_PAUSE;
+    const unsigned int key1          = '1';
+    const unsigned int key2          = '2';
+    const unsigned int key3          = '3';
+    const unsigned int key4          = '4';
+    const unsigned int key5          = '5';
+    const unsigned int key6          = '6';
+    const unsigned int key7          = '7';
+    const unsigned int key8          = '8';
+    const unsigned int key9          = '9';
+    const unsigned int key0          = '0';
+    const unsigned int keyBACKSPACE  = VK_BACK;
+    const unsigned int keyINSERT     = VK_INSERT;
+    const unsigned int keyHOME       = VK_HOME;
+    const unsigned int keyPAGEUP     = VK_PRIOR;
+    const unsigned int keyTAB        = VK_TAB;
+    const unsigned int keyQ          = 'Q';
+    const unsigned int keyW          = 'W';
+    const unsigned int keyE          = 'E';
+    const unsigned int keyR          = 'R';
+    const unsigned int keyT          = 'T';
+    const unsigned int keyY          = 'Y';
+    const unsigned int keyU          = 'U';
+    const unsigned int keyI          = 'I';
+    const unsigned int keyO          = 'O';
+    const unsigned int keyP          = 'P';
+    const unsigned int keyDELETE     = VK_DELETE;
+    const unsigned int keyEND        = VK_END;
+    const unsigned int keyPAGEDOWN   = VK_NEXT;
+    const unsigned int keyCAPSLOCK   = VK_CAPITAL;
+    const unsigned int keyA          = 'A';
+    const unsigned int keyS          = 'S';
+    const unsigned int keyD          = 'D';
+    const unsigned int keyF          = 'F';
+    const unsigned int keyG          = 'G';
+    const unsigned int keyH          = 'H';
+    const unsigned int keyJ          = 'J';
+    const unsigned int keyK          = 'K';
+    const unsigned int keyL          = 'L';
+    const unsigned int keyENTER      = VK_RETURN;
+    const unsigned int keySHIFTLEFT  = VK_SHIFT;
+    const unsigned int keyZ          = 'Z';
+    const unsigned int keyX          = 'X';
+    const unsigned int keyC          = 'C';
+    const unsigned int keyV          = 'V';
+    const unsigned int keyB          = 'B';
+    const unsigned int keyN          = 'N';
+    const unsigned int keyM          = 'M';
+    const unsigned int keySHIFTRIGHT = VK_SHIFT;
+    const unsigned int keyARROWUP    = VK_UP;
+    const unsigned int keyCTRLLEFT   = VK_CONTROL;
+    const unsigned int keyAPPLEFT    = VK_LWIN;
+    const unsigned int keyALT        = VK_LMENU;
+    const unsigned int keySPACE      = VK_SPACE;
+    const unsigned int keyALTGR      = VK_CONTROL;
+    const unsigned int keyAPPRIGHT   = VK_RWIN;
+    const unsigned int keyMENU       = VK_APPS;
+    const unsigned int keyCTRLRIGHT  = VK_CONTROL;
+    const unsigned int keyARROWLEFT  = VK_LEFT;
+    const unsigned int keyARROWDOWN  = VK_DOWN;
+    const unsigned int keyARROWRIGHT = VK_RIGHT;
+    const unsigned int keyPAD0       = 0x60;
+    const unsigned int keyPAD1       = 0x61;
+    const unsigned int keyPAD2       = 0x62;
+    const unsigned int keyPAD3       = 0x63;
+    const unsigned int keyPAD4       = 0x64;
+    const unsigned int keyPAD5       = 0x65;
+    const unsigned int keyPAD6       = 0x66;
+    const unsigned int keyPAD7       = 0x67;
+    const unsigned int keyPAD8       = 0x68;
+    const unsigned int keyPAD9       = 0x69;
+    const unsigned int keyPADADD     = VK_ADD;
+    const unsigned int keyPADSUB     = VK_SUBTRACT;
+    const unsigned int keyPADMUL     = VK_MULTIPLY;
+    const unsigned int keyPADDIV     = VK_DIVIDE;
+
+#else
+    // Define unknow keycodes when no display are available.
+    // (should rarely be used then !).
+    const unsigned int keyESC        = 1U;
+    const unsigned int keyF1         = 2U;
+    const unsigned int keyF2         = 3U;
+    const unsigned int keyF3         = 4U;
+    const unsigned int keyF4         = 5U;
+    const unsigned int keyF5         = 6U;
+    const unsigned int keyF6         = 7U;
+    const unsigned int keyF7         = 8U;
+    const unsigned int keyF8         = 9U;
+    const unsigned int keyF9         = 10U;
+    const unsigned int keyF10        = 11U;
+    const unsigned int keyF11        = 12U;
+    const unsigned int keyF12        = 13U;
+    const unsigned int keyPAUSE      = 14U;
+    const unsigned int key1          = 15U;
+    const unsigned int key2          = 16U;
+    const unsigned int key3          = 17U;
+    const unsigned int key4          = 18U;
+    const unsigned int key5          = 19U;
+    const unsigned int key6          = 20U;
+    const unsigned int key7          = 21U;
+    const unsigned int key8          = 22U;
+    const unsigned int key9          = 23U;
+    const unsigned int key0          = 24U;
+    const unsigned int keyBACKSPACE  = 25U;
+    const unsigned int keyINSERT     = 26U;
+    const unsigned int keyHOME       = 27U;
+    const unsigned int keyPAGEUP     = 28U;
+    const unsigned int keyTAB        = 29U;
+    const unsigned int keyQ          = 30U;
+    const unsigned int keyW          = 31U;
+    const unsigned int keyE          = 32U;
+    const unsigned int keyR          = 33U;
+    const unsigned int keyT          = 34U;
+    const unsigned int keyY          = 35U;
+    const unsigned int keyU          = 36U;
+    const unsigned int keyI          = 37U;
+    const unsigned int keyO          = 38U;
+    const unsigned int keyP          = 39U;
+    const unsigned int keyDELETE     = 40U;
+    const unsigned int keyEND        = 41U;
+    const unsigned int keyPAGEDOWN   = 42U;
+    const unsigned int keyCAPSLOCK   = 43U;
+    const unsigned int keyA          = 44U;
+    const unsigned int keyS          = 45U;
+    const unsigned int keyD          = 46U;
+    const unsigned int keyF          = 47U;
+    const unsigned int keyG          = 48U;
+    const unsigned int keyH          = 49U;
+    const unsigned int keyJ          = 50U;
+    const unsigned int keyK          = 51U;
+    const unsigned int keyL          = 52U;
+    const unsigned int keyENTER      = 53U;
+    const unsigned int keySHIFTLEFT  = 54U;
+    const unsigned int keyZ          = 55U;
+    const unsigned int keyX          = 56U;
+    const unsigned int keyC          = 57U;
+    const unsigned int keyV          = 58U;
+    const unsigned int keyB          = 59U;
+    const unsigned int keyN          = 60U;
+    const unsigned int keyM          = 61U;
+    const unsigned int keySHIFTRIGHT = 62U;
+    const unsigned int keyARROWUP    = 63U;
+    const unsigned int keyCTRLLEFT   = 64U;
+    const unsigned int keyAPPLEFT    = 65U;
+    const unsigned int keyALT        = 66U;
+    const unsigned int keySPACE      = 67U;
+    const unsigned int keyALTGR      = 68U;
+    const unsigned int keyAPPRIGHT   = 69U;
+    const unsigned int keyMENU       = 70U;
+    const unsigned int keyCTRLRIGHT  = 71U;
+    const unsigned int keyARROWLEFT  = 72U;
+    const unsigned int keyARROWDOWN  = 73U;
+    const unsigned int keyARROWRIGHT = 74U;
+    const unsigned int keyPAD0       = 75U;
+    const unsigned int keyPAD1       = 76U;
+    const unsigned int keyPAD2       = 77U;
+    const unsigned int keyPAD3       = 78U;
+    const unsigned int keyPAD4       = 79U;
+    const unsigned int keyPAD5       = 80U;
+    const unsigned int keyPAD6       = 81U;
+    const unsigned int keyPAD7       = 82U;
+    const unsigned int keyPAD8       = 83U;
+    const unsigned int keyPAD9       = 84U;
+    const unsigned int keyPADADD     = 85U;
+    const unsigned int keyPADSUB     = 86U;
+    const unsigned int keyPADMUL     = 87U;
+    const unsigned int keyPADDIV     = 88U;
+#endif
+
+    const double PI = 3.14159265358979323846;   //!< Definition of the mathematical constant PI
+
+    // Definition of a 10x13 font (small size).
+    const unsigned int font10x13[256*10*13/32] = {
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80100c0,
+      0x68000300,0x801,0xc00010,0x100c000,0x68100,0x100c0680,0x2,0x403000,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x0,0x0,0x0,0x0,0x0,0x4020120,
+      0x58120480,0x402,0x1205008,0x2012050,0x58080,0x20120581,0x40000001,0x804812,0x2000000,0x0,0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x140,0x80000,0x200402,0x800000,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x7010,0x7000000,0x8000200,0x20000,0xc0002000,0x8008,0x0,0x0,0x0,0x0,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x80000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x80100c0,0x68000480,0x1001,
+      0xc00010,0x1018000,0x68100,0x100c0680,0x4,0x403000,0x1020000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,0x28081883,0x200801,
+      0x2a00000,0x10,0x1c0201c0,0x70040f80,0xc0f81c07,0x0,0x70,0x3e0303c0,0x3c3c0f83,0xe03c2107,0xe08810,0x18c31070,0x3c0703c0,
+      0x783e0842,0x22222208,0x83e04010,0x1008000,0x4000200,0x20001,0x2002,0x408008,0x0,0x0,0x100000,0x0,0x1008,0x2000000,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20080,0x38000880,0x8078140f,0x81c00000,0x3e000,0xc020180,0x60080001,0xe0000002,0xc00042,0x108e2010,
+      0xc0300c0,0x300c0303,0xf83c3e0f,0x83e0f81c,0x701c070,0x3c0c41c0,0x701c0701,0xc0001d08,0x42108421,0x8820088,0x4020120,0x58140480,
+      0x802,0x1205008,0x3014050,0xc058080,0x20120581,0x40000002,0x804814,0x2020050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,
+      0x281e2484,0x80200801,0x1c02000,0x10,0x22060220,0x880c0801,0x82208,0x80000001,0x20008,0x41030220,0x40220802,0x402102,0x209010,
+      0x18c31088,0x22088220,0x80080842,0x22222208,0x80204010,0x1014000,0x200,0x20001,0x2000,0x8008,0x0,0x0,0x100000,0x0,0x1008,
+      0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x40000500,0x80800010,0x40200000,0x41000,0x12020040,0x10000003,0xa0000006,
+      0x12000c4,0x31014000,0xc0300c0,0x300c0302,0x80402008,0x2008008,0x2008020,0x220c4220,0x88220882,0x20002208,0x42108421,0x8820088,
+      0x0,0x300,0x0,0x0,0x0,0x14000000,0x0,0x200200,0x0,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xfc282504,0x80001000,
+      0x82a02000,0x20,0x22020020,0x8140802,0x102208,0x80801006,0x18008,0x9c848220,0x80210802,0x802102,0x20a010,0x15429104,0x22104220,
+      0x80080842,0x22221405,0x404008,0x1022000,0x703c0,0x381e0701,0xc0783c02,0xc09008,0x1d83c070,0x3c078140,0x381c0882,0x21242208,
+      0x81e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,0x40220500,0x80800027,0x20e02800,0x9c800,0x12020040,
+      0x20000883,0xa0200002,0x120a044,0x11064010,0x12048120,0x48120484,0x80802008,0x2008008,0x2008020,0x210a4411,0x4411044,0x10884508,
+      0x42108421,0x503c0b0,0x1c0701c0,0x701c0707,0x70381c07,0x1c07008,0x2008020,0x20f01c0,0x701c0701,0xc0201c08,0x82208822,0x883c088,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x50281903,0x20001000,0x80802000,0x20,0x22020040,0x30240f03,0xc0101c08,0x80801018,
+      0x1fc06010,0xa48483c0,0x80210f03,0xe0803f02,0x20c010,0x15429104,0x22104220,0x70080841,0x41540805,0x804008,0x1041000,0x8220,
+      0x40220881,0x882202,0x40a008,0x12422088,0x22088180,0x40100882,0x21241408,0x80201008,0x2031000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x20280,0x401c0200,0x700028,0x21205000,0x92800,0xc1fc080,0x10000883,0xa0200002,0x1205049,0x12c19010,0x12048120,0x48120484,
+      0xf0803c0f,0x3c0f008,0x2008020,0x790a4411,0x4411044,0x10504908,0x42108421,0x5022088,0x2008020,0x8020080,0x88402208,0x82208808,
+      0x2008020,0x1e088220,0x88220882,0x20002608,0x82208822,0x8822088,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x501c0264,
+      0xa0001000,0x8001fc00,0x7000020,0x22020080,0x83e0082,0x20202207,0x80000020,0x1020,0xa4848220,0x80210802,0x9c2102,0x20c010,
+      0x12425104,0x3c1043c0,0x8080841,0x41540802,0x804008,0x1000000,0x78220,0x40220f81,0x882202,0x40c008,0x12422088,0x22088100,
+      0x60100881,0x41540805,0x406008,0x1849000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0xf0140200,0x880028,0x20e0a03f,0x709c800,
+      0x201c0,0x60000881,0xa0000007,0xc0284b,0x122eb020,0x12048120,0x48120487,0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,
+      0x10204908,0x42108421,0x2022088,0x1e0781e0,0x781e0787,0xf8403e0f,0x83e0f808,0x2008020,0x22088220,0x88220882,0x21fc2a08,0x82208822,
+      0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xf80a0294,0x40001000,0x80002000,0x20,0x22020100,0x8040082,0x20202200,
+      0x80000018,0x1fc06020,0xa48fc220,0x80210802,0x842102,0x20a010,0x12425104,0x20104240,0x8080841,0x41541402,0x1004008,0x1000000,
+      0x88220,0x40220801,0x882202,0x40a008,0x12422088,0x22088100,0x18100881,0x41540805,0x801008,0x2046000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x20280,0x401c0f80,0x80880028,0x20005001,0x94800,0x20000,0x880,0xa0000000,0x5015,0x4215040,0x3f0fc3f0,0xfc3f0fc8,
+      0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,0x10505108,0x42108421,0x203c088,0x22088220,0x88220888,0x80402008,0x2008008,
+      0x2008020,0x22088220,0x88220882,0x20002a08,0x82208822,0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa00a0494,0x60001000,
+      0x80002004,0x8020,0x22020200,0x88040882,0x20402201,0x801006,0x18000,0x9f084220,0x40220802,0x442102,0x209010,0x10423088,0x20088220,
+      0x8080840,0x80882202,0x2004008,0x1000000,0x88220,0x40220881,0x882202,0x409008,0x12422088,0x22088100,0x8100880,0x80881402,
+      0x1001008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0x40220200,0x80700027,0x20002801,0x92800,0x1fc000,0x980,
+      0xa0000000,0xa017,0x84417840,0x21084210,0x84210848,0x80402008,0x2008008,0x2008020,0x2208c220,0x88220882,0x20882208,0x42108421,
+      0x2020088,0x22088220,0x88220888,0xc8402208,0x82208808,0x2008020,0x22088220,0x88220882,0x20203208,0x82208822,0x2022020,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xa03c0463,0x90000801,0x2004,0x8040,0x1c0703e0,0x70040701,0xc0401c06,0x801001,0x20020,
+      0x400843c0,0x3c3c0f82,0x3c2107,0x1c0881e,0x10423070,0x20070210,0xf0080780,0x80882202,0x3e04004,0x1000000,0x783c0,0x381e0701,
+      0x782202,0x408808,0x12422070,0x3c078100,0x700c0780,0x80882202,0x1e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,
+      0xf8000200,0x80080010,0x40000001,0x41000,0x0,0xe80,0xa0000000,0x21,0x8e21038,0x21084210,0x84210848,0xf83c3e0f,0x83e0f81c,
+      0x701c070,0x3c08c1c0,0x701c0701,0xc0005c07,0x81e0781e,0x20200b0,0x1e0781e0,0x781e0787,0x30381c07,0x1c07008,0x2008020,0x1c0881c0,
+      0x701c0701,0xc0201c07,0x81e0781e,0x203c020,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x801,0x4,0x40,0x0,0x0,0x0,0x1000,
+      0x0,0x3c000000,0x0,0x0,0x0,0x0,0x10000,0x0,0x0,0x4004,0x1000000,0x0,0x0,0x80000,0x400000,0x0,0x20008000,0x0,0x4,0x1008,0x2000000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x8008000f,0x80000000,0x3e000,0x0,0x800,0xa0000400,0x0,0x0,0x0,0x0,0x80000,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100000,0x0,0x0,0x0,0x0,0x2000,0x0,0x4020040,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,
+      0x402,0x8,0x40,0x0,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x7004,0x70000fc,0x0,0x0,0x700000,0x800000,0x0,0x20008000,
+      0x0,0x4,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x80f00000,0x0,0x0,0x0,0x800,0xa0001800,0x0,0x0,0x0,0x0,
+      0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x4020040
+    };
+
+    // Definition of a 12x24 font (normal size).
+    const unsigned int font12x24[12*24*256/32] = {
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x19,0x80000000,0x198000,0x0,0x0,0x0,0x0,
+      0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc001806,0xc81980,0x60000000,0xc001806,0x1980c00,0x18060198,0xc80c,
+      0x180600,0xc8198000,0xc001,0x80601980,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x600300f,0x1301980,0x90000000,0x600300f,0x1980600,0x300f0198,0x13006,0x300f01,0x30198000,0x6003,
+      0xf01980,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x0,0x60000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7007,0x3c0000,0x3006019,
+      0x80000000,0x90000000,0x3006019,0x80000300,0x60198000,0x3,0x601980,0x0,0x3006,0x1980000,0x60000000,0x0,0x0,0xe0000000,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000000,
+      0x0,0x0,0x0,0x0,0x0,0xc800019,0x80000000,0x198000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x1001,0x420000,0x0,0x0,0x90000000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000c06,0xc80001,0x10000000,0x18000c06,0x1800,0xc060000,0xc818,0xc0600,0xc8000000,
+      0x18000,0xc0600000,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80660207,0x800f8060,0x300c004,0x0,0x6,
+      0xe00703f,0x3f00383,0xf80f07fc,0x1f01f000,0x0,0xf8,0x607f,0x7c7e07,0xfe7fe0f8,0x6063fc1f,0x86066007,0xe7060f0,0x7f80f07f,
+      0x81f8fff6,0x6606c03,0x70ee077f,0xe0786000,0xf0070000,0xc000060,0xc0,0x3e000,0x60006003,0x600fc00,0x0,0x0,0x0,0x0,0x0,0x3c0603,
+      0xc0000000,0x7800000,0xf0000,0x0,0xf00001f,0x80001fe0,0x7fe000,0x0,0x0,0x0,0x168fe609,0x0,0x90e07,0x6000,0x3c000e,0x70000f8,
+      0x1980001f,0x0,0x1f8,0xf00000f,0xf00180,0xfe000,0xe00e,0x1001,0x20060,0x6006006,0x600600,0x600fe07c,0x7fe7fe7f,0xe7fe3fc3,
+      0xfc3fc3fc,0x7e07060f,0xf00f00,0xf00f0000,0xf360660,0x6606606e,0x76001e0,0xc00180f,0x1681981,0x10000000,0xc00180f,0x1980c00,
+      0x180f0198,0x3801680c,0x180f01,0x68198000,0xc001,0x80f01980,0x18600198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,
+      0x8044020c,0xc01f8060,0x2004004,0x0,0xc,0x3f81f07f,0x87f80383,0xf81f87fc,0x3f83f800,0x0,0x1fc,0x780607f,0x81fe7f87,0xfe7fe1fc,
+      0x6063fc1f,0x860c6007,0xe7061f8,0x7fc1f87f,0xc3fcfff6,0x6606c03,0x30c6067f,0xe0783000,0xf00d8000,0x6000060,0xc0,0x7e000,0x60006003,
+      0x600fc00,0x0,0x0,0xc00,0x0,0x0,0x7c0603,0xe0000000,0xfc00000,0x1f0000,0x0,0x900003f,0xc0003fe0,0x7fe000,0x0,0x0,0x0,0x1302660f,
+      0x0,0xf0606,0x6004,0x7e0006,0x60601f8,0x19800001,0x80000000,0x1f8,0x19800010,0x81080300,0x3f2000,0x2011,0x1001,0x1c0060,0x6006006,
+      0x600600,0x601fe1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f87061f,0x81f81f81,0xf81f8000,0x3fa60660,0x66066066,0x66003f0,0x6003009,
+      0x1301981,0x10000000,0x6003009,0x1980600,0x30090198,0x1f013006,0x300901,0x30198000,0x6003,0x901980,0x30600198,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc0f8c,0xc0180060,0x6006044,0x40000000,0xc,0x3181b041,0xc41c0783,0x388018,
+      0x71c71800,0x0,0x106,0x18c0f061,0xc38261c6,0x600384,0x60606001,0x86186007,0xe78630c,0x60e30c60,0xe7040606,0x630cc03,0x39c30c00,
+      0xc0603000,0x3018c000,0x3000060,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,0x60000000,0x18400000,0x180000,
+      0x0,0x19800070,0x40003600,0xc000,0x0,0x0,0x0,0x25a06,0x0,0x6030c,0x4,0xe20007,0xe060180,0xf000,0x80000000,0xf0000,0x10800000,
+      0x80080600,0x7f2000,0x2020,0x80001001,0x20000,0xf00f00f,0xf00f00,0x601b0382,0x60060060,0x6000600,0x60060060,0x61c78630,0xc30c30c3,
+      0xc30c000,0x30e60660,0x66066063,0xc600738,0x3006019,0x80000000,0xe0000000,0x3006019,0x80000300,0x60198000,0x3e000003,0x601980,
+      0x0,0x3006,0x1980000,0x60600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc1fcc,0xc0180060,0x6006035,0x80000000,
+      0x18,0x71c03000,0xc00c0583,0x300018,0x60c60c00,0x0,0x6,0x3060f060,0xc30060c6,0x600300,0x60606001,0x86306007,0x9e78670e,0x60670e60,
+      0x66000606,0x630c606,0x19830c01,0xc0601800,0x30306000,0x60,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,
+      0x60000000,0x18000000,0x300000,0x0,0x78060,0x6600,0x1c000,0x300c,0x39819c0,0x0,0x25a00,0x0,0x30c,0x4,0xc00003,0xc060180,0x30c1f,
+      0x80000000,0x30c000,0x10800001,0x80700000,0x7f2000,0x2020,0x80001001,0x20060,0xf00f00f,0xf00f00,0xf01b0300,0x60060060,0x6000600,
+      0x60060060,0x60c78670,0xe70e70e7,0xe70e000,0x70c60660,0x66066063,0xc7f8618,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,
+      0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x87ff3a4c,0xc0180060,0x400600e,0x600000,0x18,0x60c03000,
+      0xc00c0d83,0x700018,0x60c60c00,0x20,0x400006,0x3060f060,0xc6006066,0x600600,0x60606001,0x86606006,0x966c6606,0x60660660,0x66000606,
+      0x630c666,0xf019801,0x80601800,0x30603000,0x1f06f,0xf01ec0,0xf03fe1ec,0x6703e01f,0x61c0c06,0xdc6701f0,0x6f01ec0c,0xe1f87fc6,
+      0xc60cc03,0x71c60c7f,0xc0600600,0x60000000,0x30000000,0x300000,0x40040,0x88060,0x6600,0x18000,0x300c,0x1981980,0x0,0x2421f,
+      0x80003ce0,0x7fc198,0x601f,0xc02021,0x980600c0,0x40230,0x80000000,0x402000,0x19806003,0x80006,0xc7f2000,0x2020,0x80001001,
+      0x420060,0xf00f00f,0xf00f00,0xf01b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x6606208,0x60e60660,0x66066061,
+      0x987fc670,0x1f01f01f,0x1f01f01,0xf039c0f0,0xf00f00f,0xf03e03,0xe03e03e0,0x1f06701f,0x1f01f01,0xf01f0060,0x1e660c60,0xc60c60c6,
+      0xc6f060c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x7ff3207,0x8c0c0000,0xc00300e,0x600000,0x30,0x60c03000,
+      0xc01c0983,0xf0600030,0x31860c06,0x6001e0,0x78000e,0x23e1f861,0xc6006066,0x600600,0x60606001,0x86c06006,0x966c6606,0x60660660,
+      0xe7000606,0x630c666,0xf01f803,0x600c00,0x30000000,0x3f87f,0x83f83fc3,0xf83fe3fc,0x7f83e01f,0x6380c07,0xfe7f83f8,0x7f83fc0d,
+      0xf3fc7fc6,0xc71cc03,0x3183187f,0xc0600600,0x60000000,0xff806000,0x300000,0x40040,0x88070,0x6600,0x60030060,0x6001818,0x1883180,
+      0x0,0x2423f,0xc0007ff0,0x607fc1f8,0x603f,0x80c01fc1,0xf80601e0,0x5f220,0x80420000,0x5f2000,0xf006006,0x80006,0xc7f2000,0x2020,
+      0x82107c07,0xc03c0060,0x1f81f81f,0x81f81f80,0xf03b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x660671c,0x61660660,
+      0x66066061,0xf860e6c0,0x3f83f83f,0x83f83f83,0xf87fe3f8,0x3f83f83f,0x83f83e03,0xe03e03e0,0x3f87f83f,0x83f83f83,0xf83f8060,
+      0x3fc60c60,0xc60c60c3,0x187f8318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x883200,0x300c0000,0xc003035,0x80600000,
+      0x30,0x66c03001,0xc0f81983,0xf86f0030,0x1f071c06,0x600787,0xfe1e001c,0x6261987f,0x86006067,0xfe7fc600,0x7fe06001,0x87c06006,
+      0xf6646606,0x60e6067f,0xc3e00606,0x61986f6,0x600f007,0x600c00,0x30000000,0x21c71,0x830831c3,0x1c06031c,0x71c06003,0x6700c06,
+      0x6671c318,0x71831c0f,0x16040c06,0xc318606,0x1b031803,0x80600600,0x60000000,0x30009000,0x300000,0x40040,0x7003e,0x67e0,0x90070090,
+      0x9001818,0x8c3100,0x0,0x60,0x4000e730,0x900380f0,0x6034,0x80c018c7,0xfe060338,0xb0121,0x80c60000,0x909000,0x6008,0x1080006,
+      0xc3f2000,0x2011,0x3180060,0x60060e0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0x60664660,0x66066066,
+      0x66063b8,0x62660660,0x66066060,0xf06066c0,0x21c21c21,0xc21c21c2,0x1c466308,0x31c31c31,0xc31c0600,0x60060060,0x31871c31,0x83183183,
+      0x18318000,0x71860c60,0xc60c60c3,0x18718318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1981a00,0xe03e0000,0xc003044,
+      0x40600000,0x60,0x66c03001,0x80f03182,0x1c7f8030,0x3f83fc06,0x601e07,0xfe078038,0x6661987f,0x86006067,0xfe7fc61e,0x7fe06001,
+      0x87e06006,0x66666606,0x7fc6067f,0x81f80606,0x61986f6,0x6006006,0x600600,0x30000000,0xc60,0xc60060c6,0xc06060c,0x60c06003,
+      0x6e00c06,0x6660c60c,0x60c60c0e,0x6000c06,0xc318666,0x1f031803,0x600600,0x603c2000,0x30016800,0x1fe0000,0x1f81f8,0x1c1f,0x804067e1,
+      0x68060168,0x16800810,0xc42300,0x0,0x60,0x20c331,0x68030060,0x6064,0x3fc1040,0xf006031c,0xa011e,0x818c7fe0,0x909000,0x7fe1f,
+      0x80f00006,0xc0f2060,0xf80e,0x18c0780,0x780781c0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0xfc666660,
+      0x66066066,0x66061f0,0x66660660,0x66066060,0x606066e0,0xc00c00,0xc00c00c0,0xc066600,0x60c60c60,0xc60c0600,0x60060060,0x60c60c60,
+      0xc60c60c6,0xc60c000,0x61c60c60,0xc60c60c3,0x1860c318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1980f81,0x80373000,
+      0xc003004,0x7fe0001,0xf0000060,0x60c03003,0x183180,0xc71c060,0x3181ec00,0x7000,0xe070,0x66619860,0xc6006066,0x60061e,0x60606001,
+      0x87606006,0x66626606,0x7f860661,0xc01c0606,0x6198696,0xf00600e,0x600600,0x30000000,0x1fc60,0xc60060c7,0xfc06060c,0x60c06003,
+      0x7c00c06,0x6660c60c,0x60c60c0c,0x7f00c06,0xc3b8666,0xe01b007,0x3c00600,0x3c7fe000,0xff03ec00,0x1fe0000,0x40040,0xe001,0xc0806603,
+      0xec0e03ec,0x3ec00010,0x0,0x60000000,0x7f,0x10c3f3,0xec070060,0x6064,0x3fc1040,0x6000030c,0xa0100,0x3187fe1,0xf09f1000,0x7fe00,
+      0x6,0xc012060,0x0,0xc63c03,0xc03c0380,0x19819819,0x81981981,0x98330600,0x60060060,0x6000600,0x60060060,0xfc662660,0x66066066,
+      0x66060e0,0x6c660660,0x66066060,0x6060e630,0x1fc1fc1f,0xc1fc1fc1,0xfc3fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
+      0xc60c7fe,0x62c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe02c6,0x3c633000,0xc003004,
+      0x7fe0001,0xf00000c0,0x60c03006,0xc6180,0xc60c060,0x60c00c00,0x7000,0xe060,0x66639c60,0x66006066,0x600606,0x60606001,0x86306006,
+      0x66636606,0x60060660,0xc0060606,0x61f8696,0xf00600c,0x600300,0x30000000,0x3fc60,0xc60060c7,0xfc06060c,0x60c06003,0x7c00c06,
+      0x6660c60c,0x60c60c0c,0x1f80c06,0xc1b0666,0xe01b00e,0x3c00600,0x3c43c000,0x3007de00,0x600000,0x40040,0x30000,0x61006607,0xde0c07de,
+      0x7de00000,0x0,0xf07fefff,0x1f,0x8008c3f7,0xde0e0060,0x6064,0xc01047,0xfe00018c,0xb013f,0x86300061,0xf0911000,0x6000,0x6,
+      0xc012060,0x3f,0x8063c0cc,0x3cc0c700,0x39c39c39,0xc39c39c1,0x98630600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,
+      0x66061f0,0x78660660,0x66066060,0x607fc618,0x3fc3fc3f,0xc3fc3fc3,0xfc7fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
+      0xc60c7fe,0x64c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe0260,0x6661b000,0xc003000,
+      0x600000,0xc0,0x60c0300c,0xc7fe0,0xc60c060,0x60c01c00,0x1e07,0xfe078060,0x6663fc60,0x66006066,0x600606,0x60606001,0x86386006,
+      0x6636606,0x60060660,0xe0060606,0x60f039c,0x1b806018,0x600300,0x30000000,0x70c60,0xc60060c6,0x6060c,0x60c06003,0x7600c06,
+      0x6660c60c,0x60c60c0c,0x1c0c06,0xc1b03fc,0xe01f01c,0xe00600,0x70000000,0x3007fc00,0x600000,0x40040,0x0,0x62006607,0xfc1807fc,
+      0x7fc00000,0x0,0xf0000000,0x1,0xc004c307,0xfc1c0060,0x6064,0xc018c0,0x600000d8,0x5f200,0x3180060,0x50a000,0x6000,0x6,0xc012000,
+      0x0,0xc601c0,0x4201c600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,0x66063b8,
+      0x70660660,0x66066060,0x607f860c,0x70c70c70,0xc70c70c7,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,
+      0x68c60c60,0xc60c60c1,0xf060c1f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3300260,0x6661e000,0xc003000,0x600000,
+      0x180,0x71c03018,0xc7fe0,0xc60c0c0,0x60c01800,0x787,0xfe1e0060,0x6663fc60,0x630060c6,0x600306,0x60606001,0x86186006,0x661e70e,
+      0x60070c60,0x60060606,0x60f039c,0x19806038,0x600180,0x30000000,0x60c60,0xc60060c6,0x6060c,0x60c06003,0x6700c06,0x6660c60c,
+      0x60c60c0c,0xc0c06,0xc1b039c,0x1f00e018,0x600600,0x60000000,0x1803f800,0x600000,0x40040,0x39e00,0x63006603,0xf83803f8,0x3f800000,
+      0x0,0x60000000,0x0,0xc00cc303,0xf8180060,0x6064,0xc01fc0,0x60060070,0x40200,0x18c0060,0x402000,0x6000,0x6,0xc012000,0x0,0x18c0140,
+      0x2014600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0300,0x60060060,0x6000600,0x60060060,0x60c61e70,0xe70e70e7,0xe70e71c,0x60e60660,0x66066060,
+      0x6060060c,0x60c60c60,0xc60c60c6,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,0x70c60c60,0xc60c60c0,
+      0xe060c0e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x33022e0,0x6670c000,0xc003000,0x600600,0x60180,0x31803030,
+      0x41c0184,0x1831c0c0,0x71c23806,0x6001e0,0x780000,0x62630c60,0xe38261c6,0x600386,0x60606043,0x860c6006,0x661e30c,0x60030c60,
+      0x740e0607,0xe0f039c,0x31c06030,0x600180,0x30000000,0x61c71,0x830831c3,0x406031c,0x60c06003,0x6300c06,0x6660c318,0x71831c0c,
+      0x41c0c07,0x1c0e039c,0x1b00e030,0x600600,0x60000000,0x1c41b00e,0x601cc0,0x401f8,0x45240,0xe1803601,0xb03001b0,0x1b000000,
+      0x0,0x0,0x41,0xc008e711,0xb0300060,0x6034,0x80c02020,0x60060030,0x30c00,0xc60000,0x30c000,0x0,0x7,0x1c012000,0x0,0x3180240,
+      0x6024608,0x30c30c30,0xc30c30c3,0xc630382,0x60060060,0x6000600,0x60060060,0x61c61e30,0xc30c30c3,0xc30c208,0x70c70e70,0xe70e70e0,
+      0x6060068c,0x61c61c61,0xc61c61c6,0x1cc62308,0x30430430,0x43040600,0x60060060,0x31860c31,0x83183183,0x18318060,0x31c71c71,
+      0xc71c71c0,0xe07180e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2203fc0,0x663f6000,0x6006000,0x600600,0x60300,
+      0x3f81fe7f,0xc7f80187,0xf83f80c0,0x3f83f006,0x600020,0x400060,0x33e6067f,0xc1fe7f87,0xfe6001fe,0x6063fc7f,0x60e7fe6,0x660e3f8,
+      0x6001f860,0x37fc0603,0xfc06030c,0x30c0607f,0xe06000c0,0x30000000,0x7fc7f,0x83f83fc3,0xfc0603fc,0x60c7fe03,0x61807c6,0x6660c3f8,
+      0x7f83fc0c,0x7f80fc3,0xfc0e039c,0x3180607f,0xc0600600,0x60000000,0xfc0e00c,0x601986,0x66040040,0x4527f,0xc0803fe0,0xe07fe0e0,
+      0xe000000,0x0,0x0,0x7f,0x80107ff0,0xe07fc060,0x603f,0x83fe0000,0x60060018,0xf000,0x420000,0xf0000,0x7fe00,0x7,0xfe012000,
+      0x0,0x2100640,0xc0643f8,0x60660660,0x66066067,0xec3e1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f860e3f,0x83f83f83,0xf83f8000,
+      0x5fc3fc3f,0xc3fc3fc0,0x606006fc,0x7fc7fc7f,0xc7fc7fc7,0xfcffe3f8,0x3fc3fc3f,0xc3fc7fe7,0xfe7fe7fe,0x3f860c3f,0x83f83f83,
+      0xf83f8060,0x7f83fc3f,0xc3fc3fc0,0x607f8060,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2201f80,0x3c1e7000,0x6006000,
+      0x600,0x60300,0xe01fe7f,0xc3f00183,0xe01f0180,0x1f01e006,0x600000,0x60,0x3006067f,0x807c7e07,0xfe6000f8,0x6063fc3e,0x6067fe6,
+      0x660e0f0,0x6000f060,0x3bf80601,0xf806030c,0x60e0607f,0xe06000c0,0x30000000,0x1ec6f,0xf01ec0,0xf80601ec,0x60c7fe03,0x61c03c6,
+      0x6660c1f0,0x6f01ec0c,0x3f007c1,0xcc0e030c,0x71c0c07f,0xc0600600,0x60000000,0x7804018,0xe01186,0x66040040,0x39e3f,0x80401fe0,
+      0x407fe040,0x4000000,0x0,0x0,0x3f,0x203ce0,0x407fc060,0x601f,0x3fe0000,0x60060018,0x0,0x0,0x0,0x7fe00,0x6,0xe6012000,0x0,
+      0x7e0,0x1807e1f0,0x60660660,0x66066066,0x6c3e07c,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7e060e0f,0xf00f00,0xf00f0000,0x8f01f81f,
+      0x81f81f80,0x60600670,0x1ec1ec1e,0xc1ec1ec1,0xec79c0f0,0xf80f80f,0x80f87fe7,0xfe7fe7fe,0x1f060c1f,0x1f01f01,0xf01f0000,0x4f01cc1c,
+      0xc1cc1cc0,0xc06f00c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x6006000,0x600,0x600,0x0,0x0,0x0,0x0,
+      0x600000,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x600060,0x30000000,0x0,0x0,0xc,0x3,0x0,0x0,0x60000c00,0x0,
+      0x0,0xc000,0x600600,0x60000000,0x18,0xc03100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f8,0x0,0x0,0x0,0x0,0x6,
+      0x12000,0x2000000,0x40,0x20004000,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0xc06000c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x2004000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,
+      0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0xc00,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x21c,0x3,0x0,0x0,0x60000c00,0x0,0x0,0xc000,
+      0x7c0603,0xe0000000,0x10,0xc02300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f0,0x0,0x0,0x0,0x0,0x6,0x12000,0x1000000,
+      0x40,0x7e004000,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc06000c0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x300c000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,0x0,0x7800000,0x0,
+      0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x3f8,0x3e,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x3c0603,0xc0000000,
+      0x10,0xfc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x60000,0x0,0x0,0x0,0x0,0x6,0x0,0x1000000,0x0,0x0,0x0,0x0,
+      0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,
+      0x0,0x1f0,0x3c,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x600,0x0,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x6,0x0,0xe000000,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+    // Definition of a 16x32 font (large size).
+    const unsigned int font16x32[16*32*256/32] = {
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70000e0,0x3c00730,0xe7001c0,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0x730,0x70000e0,0x3c00730,
+      0xe700000,0x700,0xe003c0,0xe7000e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x18001c0,0x6600ff0,0xe7003e0,0x0,0x18001c0,0x6600e70,0x18001c0,0x6600e70,0xff0,0x18001c0,0x6600ff0,0xe700000,0x180,
+      0x1c00660,0xe7001c0,0x0,0x0,0x0,0x380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,
+      0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00380,
+      0xc300ce0,0xe700630,0x0,0x1c00380,0xc300e70,0x1c00380,0xc300e70,0xce0,0x1c00380,0xc300ce0,0xe700000,0x1c0,0x3800c30,0xe700380,
+      0x0,0x0,0x0,0x7c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x700000,0x0,0x0,0x0,0x7c007c00,0x3e000000,
+      0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000070,0x1800000,0xc60,0x0,0xe000070,0x1800000,0xe000070,
+      0x1800000,0x0,0xe000070,0x1800000,0x0,0xe00,0x700180,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x800000,0x0,0x600600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x3f0,0xfc0,0x0,0x7000000,0x38000000,0x1c0000,0xfc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,
+      0x1801f00,0x0,0x0,0x1c,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7300000,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0xe700000,
+      0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0x0,0xc000c00,0x43800000,0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0xf80,0x70000e0,0x3c00730,0xe700c60,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0xe000730,0x70000e0,0x3c00730,0xe700000,0x700,
+      0xe003c0,0xe7000e0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300000,0x803c00,0x7c00180,
+      0xc00300,0x1000000,0x0,0x1c,0x3c007c0,0xfc007e0,0xe01ff8,0x3f03ffc,0x7e007c0,0x0,0x0,0x7c0,0x1c0,0x7f8003f0,0x7f007ff8,0x7ff803f0,
+      0x70381ffc,0xff0700e,0x7000783c,0x783807c0,0x7fc007c0,0x7fc00fc0,0x7fff7038,0x700ee007,0x780f780f,0x7ffc03f0,0x70000fc0,0x3c00000,
+      0x3000000,0x38000000,0x1c0000,0x1fc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x1801f80,0x0,0x1f80000,
+      0x7e,0x0,0x0,0x2400000,0xfc00000,0x7ff0000,0x7ffc0000,0x0,0x0,0x0,0x0,0xf30fb0c,0x2400000,0x0,0x240780f,0x1c0,0xfc,0x780f,
+      0x18003f0,0xe700000,0x7c00000,0x0,0xff0,0x3c00000,0x78007c0,0xc00000,0xff80000,0xf80,0x7c00000,0xc000c00,0x18001c0,0x1c001c0,
+      0x1c001c0,0x1c003e0,0x7fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007838,0x7c007c0,0x7c007c0,0x7c00000,0x7c67038,
+      0x70387038,0x7038780f,0x70001fe0,0x30000c0,0x2400f30,0xe700c60,0x0,0x30000c0,0x2400e70,0x30000c0,0x2400e70,0xf700f30,0x30000c0,
+      0x2400f30,0xe700000,0x300,0xc00240,0xe7000c0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,
+      0x630018c,0x807e00,0xfe00180,0xc00300,0x1000000,0x0,0x38,0xff01fc0,0x3ff01ff0,0x1e01ff8,0x7f83ffc,0x1ff80ff0,0x0,0x0,0xff0,
+      0x1f003e0,0x7fe00ff8,0x7fc07ff8,0x7ff80ff8,0x70381ffc,0xff0701c,0x7000783c,0x78381ff0,0x7fe01ff0,0x7fe01ff0,0x7fff7038,0x781ee007,
+      0x3c1e380e,0x7ffc0380,0x380001c0,0x3c00000,0x1800000,0x38000000,0x1c0000,0x3c00000,0x380001c0,0xe01c00,0x3800000,0x0,0x0,
+      0x0,0x7000000,0x0,0x0,0x1e0,0x18003c0,0x0,0x3fc0000,0x70,0x0,0x0,0x6600000,0x1ff00000,0x1fff0000,0x7ffc0000,0x0,0x0,0x0,0x0,
+      0xcf0239c,0x3c00000,0x0,0x3c0380e,0x1c0,0x2001fe,0x380e,0x18007f8,0xe700000,0x8600000,0x0,0xff0,0x7e00000,0x8c00870,0x1800000,
+      0x1ff80000,0x180,0xc600000,0xc000c00,0x38001c0,0x3e003e0,0x3e003e0,0x3e001c0,0x7fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,
+      0x7fc07838,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x1fec7038,0x70387038,0x7038380e,0x70003ce0,0x1800180,0x6600cf0,0xe7007c0,0x0,
+      0x1800180,0x6600e70,0x1800180,0x6600e70,0x7c00cf0,0x1800180,0x6600cf0,0xe700000,0x180,0x1800660,0xe700180,0x38000e70,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630030c,0x3f0e700,0x1e200180,0x1800180,0x21100000,0x0,
+      0x38,0x1e7819c0,0x38781038,0x1e01c00,0xf080038,0x1c381c38,0x0,0x0,0x1878,0x7fc03e0,0x70e01e18,0x70e07000,0x70001e18,0x703801c0,
+      0x707038,0x70007c7c,0x7c381c70,0x70701c70,0x70703830,0x1c07038,0x381ce007,0x1c1c3c1e,0x3c0380,0x380001c0,0x7e00000,0xc00000,
+      0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,0x70c0000,0xe0,
+      0x0,0x0,0xc300000,0x38300000,0x3c700000,0x3c0000,0x0,0x0,0x0,0x0,0xce022f4,0x1800000,0x0,0x1803c1e,0x1c0,0x2003c2,0x3c1e,
+      0x1800e08,0x7e0,0x300000,0x0,0x7e00000,0xe700000,0x600030,0x3000000,0x3f980000,0x180,0x18200000,0xc000c00,0x1e0001c0,0x3e003e0,
+      0x3e003e0,0x3e003e0,0xfe01e18,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70e07c38,0x1c701c70,0x1c701c70,0x1c700000,0x3c787038,
+      0x70387038,0x70383c1e,0x70003870,0xc00300,0xc300ce0,0x380,0x0,0xc00300,0xc300000,0xc00300,0xc300000,0xfc00ce0,0xc00300,0xc300ce0,
+      0x0,0xc0,0x3000c30,0x300,0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630031c,0xff8c300,
+      0x1c000180,0x1800180,0x39380000,0x0,0x70,0x1c3801c0,0x203c001c,0x3e01c00,0x1c000038,0x381c3838,0x0,0x0,0x1038,0xe0e03e0,0x70703c08,
+      0x70707000,0x70003808,0x703801c0,0x707070,0x70007c7c,0x7c383838,0x70383838,0x70387010,0x1c07038,0x381c700e,0x1e3c1c1c,0x780380,
+      0x1c0001c0,0xe700000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,
+      0x0,0xe000000,0xe0,0x0,0x1000100,0x3800,0x70100000,0x38700000,0x780000,0x1c0,0x7801ce0,0xe380000,0x0,0x2264,0x0,0x0,0x1c1c,
+      0x0,0x200780,0x1c1c,0x1800c00,0x1818,0x7f00000,0x0,0x18180000,0xc300000,0x600070,0x0,0x7f980000,0x180,0x18300000,0xc000c00,
+      0x3000000,0x3e003e0,0x3e003e0,0x3e003e0,0xee03c08,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,
+      0x38380000,0x38387038,0x70387038,0x70381c1c,0x7fc03870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xbc00000,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0xe88c300,0x1c000180,0x38001c0,
+      0xfe00180,0x0,0x70,0x1c3801c0,0x1c001c,0x6e01c00,0x1c000078,0x381c3818,0x0,0x40000,0x40000038,0x1c0607e0,0x70703800,0x70707000,
+      0x70003800,0x703801c0,0x7070e0,0x70007c7c,0x7c383838,0x70383838,0x70387000,0x1c07038,0x381c700e,0xf780e38,0x700380,0x1c0001c0,
+      0x1c380000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,
+      0xe000000,0xe0,0x0,0x1000100,0x4400,0x70000000,0x38700000,0x700000,0xe0,0x7001c70,0xe380000,0x0,0x2264,0x0,0x0,0xe38,0x0,
+      0x200700,0xe38,0x1800c00,0x300c,0xc300000,0x0,0x300c0000,0xc300180,0x6003c0,0x0,0x7f980000,0x180,0x18300000,0xc000c00,0x1800000,
+      0x7e007e0,0x7e007e0,0x7e003e0,0xee03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,0x38380000,
+      0x38387038,0x70387038,0x70380e38,0x7ff039f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x40000,0x0,0x0,0x38000000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0x1c80e700,0x1c000180,0x38001c0,0x3800180,
+      0x0,0xe0,0x381c01c0,0x1c001c,0x6e01c00,0x38000070,0x381c381c,0x0,0x3c0000,0x78000078,0x38030770,0x70707800,0x70387000,0x70007000,
+      0x703801c0,0x7071c0,0x7000745c,0x7638701c,0x7038701c,0x70387000,0x1c07038,0x1c38718e,0x7700f78,0xf00380,0xe0001c0,0x381c0000,
+      0x7e0,0x39e003e0,0x79c03f0,0x3ffc079c,0x39e01fc0,0xfe01c1e,0x3807778,0x39e007e0,0x39e0079c,0x73c07e0,0x7ff83838,0x701ce007,
+      0x783c701c,0x1ffc01c0,0x18001c0,0x0,0x1c000100,0xe0,0x0,0x1000100,0x4200,0x70000000,0x70700100,0xf00100,0x10000e0,0x7000c70,
+      0xc700000,0x0,0x2204,0x7e00000,0x1e380100,0x1ffc0f78,0x0,0xf80700,0xf78,0x1800e00,0x63e6,0x18300000,0x0,0x6fe60000,0xe700180,
+      0xc00060,0x3838,0x7f980000,0x180,0x18300000,0xc000c00,0x18001c0,0x7700770,0x7700770,0x77007f0,0xee07800,0x70007000,0x70007000,
+      0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1008,0x707c7038,0x70387038,0x70380f78,0x707039c0,0x7e007e0,0x7e007e0,
+      0x7e007e0,0x1f3c03e0,0x3f003f0,0x3f003f0,0x1fc01fc0,0x1fc01fc0,0x7f039e0,0x7e007e0,0x7e007e0,0x7e00380,0x7ce3838,0x38383838,
+      0x3838701c,0x39e0701c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6307fff,0x1c807e0c,0xe000180,
+      0x30000c0,0x3800180,0x0,0xe0,0x381c01c0,0x1c001c,0xce01fe0,0x38000070,0x381c381c,0x3800380,0xfc0000,0x7e0000f0,0x30030770,
+      0x70707000,0x70387000,0x70007000,0x703801c0,0x707380,0x700076dc,0x7638701c,0x7038701c,0x70387800,0x1c07038,0x1c3873ce,0x7f00770,
+      0xe00380,0xe0001c0,0x700e0000,0x1ff8,0x3ff00ff0,0xffc0ff8,0x3ffc0ffc,0x3bf01fc0,0xfe01c3c,0x3807f78,0x3bf00ff0,0x3ff00ffc,
+      0x77e0ff0,0x7ff83838,0x3838e007,0x3c783838,0x1ffc01c0,0x18001c0,0x0,0x7ff00380,0x1e0,0x0,0x1000100,0x4200,0x78000000,0x70700380,
+      0xe00380,0x3800060,0xe000e30,0x1c600000,0x0,0x2204,0xff00000,0x7f7c0380,0x1ffc0770,0x1c0,0x3fc0700,0x18040770,0x1800780,0x4e12,
+      0x18300104,0x0,0x4c320000,0x7e00180,0x1c00030,0x3838,0x7f980000,0x180,0x18302080,0xc000c00,0x18001c0,0x7700770,0x7700770,
+      0x7700770,0x1ee07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c381c,0x705c7038,0x70387038,
+      0x70380770,0x70383b80,0x1ff81ff8,0x1ff81ff8,0x1ff81ff8,0x3fbe0ff0,0xff80ff8,0xff80ff8,0x1fc01fc0,0x1fc01fc0,0xff83bf0,0xff00ff0,
+      0xff00ff0,0xff00380,0xffc3838,0x38383838,0x38383838,0x3ff03838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x1c0,0x7fff,0x1c803c38,0xf000000,0x70000e0,0xfe00180,0x0,0x1c0,0x381c01c0,0x3c0078,0xce01ff0,0x39e000f0,0x1c38381c,0x3800380,
+      0x3e07ffc,0xf8001f0,0x307b0770,0x70e07000,0x70387000,0x70007000,0x703801c0,0x707700,0x700076dc,0x7638701c,0x7038701c,0x70387e00,
+      0x1c07038,0x1c3873ce,0x3e007f0,0x1e00380,0x70001c0,0x0,0x1038,0x3c381e18,0x1c7c1e3c,0x3801e3c,0x3c7801c0,0xe01c78,0x380739c,
+      0x3c781c38,0x3c381c3c,0x7c21e10,0x7003838,0x3838700e,0x1ef03838,0x3c01c0,0x18001c0,0x0,0x7fe007c0,0x1c0,0x0,0x1000100,0x6400,
+      0x7e000000,0x707007c0,0x1e007c0,0x7c00070,0xe000638,0x18600000,0x0,0x0,0x1e100000,0x73ce07c0,0x3c07f0,0x1c0,0x7240700,0x1ddc3ffe,
+      0x1800de0,0x8c01,0x1870030c,0x0,0x8c310000,0x3c00180,0x3800030,0x3838,0x7f980000,0x180,0x183030c0,0xc000c00,0x430001c0,0x7700770,
+      0x7700770,0x7700770,0x1ce07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1c38,0x70dc7038,
+      0x70387038,0x703807f0,0x70383b80,0x10381038,0x10381038,0x10381038,0x21e71e18,0x1e3c1e3c,0x1e3c1e3c,0x1c001c0,0x1c001c0,0x1e383c78,
+      0x1c381c38,0x1c381c38,0x1c380380,0x1c383838,0x38383838,0x38383838,0x3c383838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0x1e8000e0,0x1f000000,0x70000e0,0x39380180,0x0,0x1c0,0x3b9c01c0,0x3c07f0,0x18e01078,0x3bf800e0,
+      0x7e0383c,0x3800380,0x1f807ffc,0x3f001c0,0x61ff0e38,0x7fc07000,0x70387ff0,0x7ff07000,0x7ff801c0,0x707f00,0x7000729c,0x7338701c,
+      0x7070701c,0x70703fc0,0x1c07038,0x1e7873ce,0x1c003e0,0x3c00380,0x70001c0,0x0,0x1c,0x3c381c00,0x1c3c1c1c,0x3801c3c,0x383801c0,
+      0xe01cf0,0x380739c,0x38381c38,0x3c381c3c,0x7801c00,0x7003838,0x3838700e,0xfe03c78,0x7801c0,0x18001c0,0x0,0x1c000c20,0xff8,
+      0x0,0x1ff01ff0,0x3818,0x3fc00100,0x707e0c20,0x3c00c20,0xc200030,0xc000618,0x18c00000,0x0,0x0,0x1c000080,0xe1ce0c20,0x7803e0,
+      0x1c0,0xe200700,0xff83ffe,0x1801878,0x9801,0x1cf0071c,0x7ffc0000,0x8c310000,0x7ffe,0x7000030,0x3838,0x3f980380,0x180,0xc6038e0,
+      0x7f9c7f9c,0x3e1c01c0,0xe380e38,0xe380e38,0xe380f78,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,0x1c001c0,0xfe387338,0x701c701c,
+      0x701c701c,0x701c0e70,0x719c7038,0x70387038,0x703803e0,0x70383b80,0x1c001c,0x1c001c,0x1c001c,0xe71c00,0x1c1c1c1c,0x1c1c1c1c,
+      0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380000,0x3c383838,0x38383838,0x38383c78,0x3c383c78,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0xf800380,0x3f830000,0x70000e0,0x31080180,0x0,0x380,0x3b9c01c0,
+      0x7807e0,0x38e00038,0x3c3800e0,0xff01c3c,0x3800380,0x7c000000,0x7c03c0,0x61870e38,0x7fc07000,0x70387ff0,0x7ff070fc,0x7ff801c0,
+      0x707f80,0x7000739c,0x7338701c,0x7ff0701c,0x7fe00ff0,0x1c07038,0xe7073ce,0x1c003e0,0x3800380,0x38001c0,0x0,0x1c,0x381c3800,
+      0x381c380e,0x380381c,0x383801c0,0xe01de0,0x380739c,0x3838381c,0x381c381c,0x7001e00,0x7003838,0x1c70718e,0x7e01c70,0xf00380,
+      0x18001e0,0x1e000000,0x1c001bb0,0xff8,0x0,0x1000100,0xe0,0xff00300,0x707e1bb0,0x3801bb0,0x1bb00010,0x8000308,0x30c00000,0x0,
+      0x0,0x1e0000c0,0xe1ce1bb0,0xf003e0,0x1c0,0x1c203ff8,0x63003e0,0x180181c,0x9801,0xfb00e38,0x7ffc0000,0x8fc10000,0x7ffe,0xe000860,
+      0x3838,0x1f980380,0x180,0x7c01c70,0x1f001f0,0x1f003c0,0xe380e38,0xe380e38,0xe380e38,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,
+      0x1c001c0,0xfe387338,0x701c701c,0x701c701c,0x701c07e0,0x731c7038,0x70387038,0x703803e0,0x70383980,0x1c001c,0x1c001c,0x1c001c,
+      0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x387c3838,0x38383838,0x38381c70,
+      0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc30,0x7f00e00,0x33c30000,0x70000e0,0x1007ffe,
+      0x0,0x380,0x3b9c01c0,0xf00078,0x30e0001c,0x3c1c01c0,0x1c381fdc,0x0,0x70000000,0x1c0380,0x63030e38,0x70707000,0x70387000,0x700070fc,
+      0x703801c0,0x707b80,0x7000739c,0x7338701c,0x7fc0701c,0x7fc001f0,0x1c07038,0xe703e5c,0x3e001c0,0x7800380,0x38001c0,0x0,0x7fc,
+      0x381c3800,0x381c380e,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7001fc0,0x7003838,0x1c70718e,0x7c01c70,
+      0xe01f00,0x180007c,0x7f8c0000,0x7fc03fb8,0x1c0,0x0,0x1000100,0x700,0x1f00600,0x70703fb8,0x7803fb8,0x3fb80000,0x8000000,0x180,
+      0x0,0x0,0x1fc00060,0xe1ce3fb8,0xe001c0,0x1c0,0x1c203ff8,0xc1801c0,0x180c,0x9801,0x1c70,0xc0000,0x8cc10000,0x180,0xfe007c0,
+      0x3838,0x7980380,0xff0,0xe38,0x3e003e00,0x3e000380,0xe380e38,0xe380e38,0xe380e38,0x38e07000,0x70007000,0x70007000,0x1c001c0,
+      0x1c001c0,0x70387338,0x701c701c,0x701c701c,0x701c03c0,0x731c7038,0x70387038,0x703801c0,0x703838e0,0x7fc07fc,0x7fc07fc,0x7fc07fc,
+      0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x38dc3838,0x38383838,0x38381c70,
+      0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc60,0xf83878,0x71e30000,0x70000e0,0x1007ffe,
+      0x7f0,0x380,0x381c01c0,0x1e0003c,0x60e0001c,0x381c01c0,0x381c079c,0x0,0x7c000000,0x7c0380,0x63031c1c,0x70307000,0x70387000,
+      0x7000701c,0x703801c0,0x7071c0,0x7000739c,0x71b8701c,0x7000701c,0x71e00078,0x1c07038,0xe703e7c,0x7e001c0,0xf000380,0x38001c0,
+      0x0,0x1ffc,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fc0,0x380739c,0x3838381c,0x381c381c,0x7000ff0,0x7003838,0x1ef03bdc,
+      0x3800ee0,0x1e01f00,0x180007c,0x61fc0000,0x7fc07f3c,0x1c0,0x0,0x1000100,0x1800,0x780c00,0x70707f3c,0xf007f3c,0x7f3c0000,0x0,
+      0x3c0,0x3ffcffff,0x0,0xff00030,0xe1fe7f3c,0x1e001c0,0x1c0,0x1c200700,0xc183ffe,0xe0c,0x9801,0x1ff038e0,0xc07f0,0x8c610000,
+      0x180,0x0,0x3838,0x1980380,0x0,0x1ff0071c,0xe000e000,0xe0000f80,0x1c1c1c1c,0x1c1c1c1c,0x1c1c1e38,0x38e07000,0x70007000,0x70007000,
+      0x1c001c0,0x1c001c0,0x703871b8,0x701c701c,0x701c701c,0x701c03c0,0x761c7038,0x70387038,0x703801c0,0x70703870,0x1ffc1ffc,0x1ffc1ffc,
+      0x1ffc1ffc,0xfff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x389c3838,0x38383838,
+      0x38380ee0,0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xfffc,0xbc60fc,0x70e30000,0x70000e0,
+      0x180,0x7f0,0x700,0x381c01c0,0x3e0001c,0x7ffc001c,0x381c03c0,0x381c001c,0x0,0x1f807ffc,0x3f00380,0x63031ffc,0x70387000,0x70387000,
+      0x7000701c,0x703801c0,0x7071e0,0x7000701c,0x71b8701c,0x7000701c,0x70f00038,0x1c07038,0x7e03e7c,0x77001c0,0xe000380,0x1c001c0,
+      0x0,0x3c1c,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x70003f8,0x7003838,0xee03bdc,
+      0x3c00ee0,0x3c00380,0x18000e0,0xf00000,0x1c007e7c,0x3c0,0x0,0x1000100,0x0,0x381800,0x70707e7c,0xe007e7c,0x7e7c0000,0x0,0x7c0,
+      0x0,0x0,0x3f80018,0xe1fe7e7c,0x3c001c0,0x1c0,0x1c200700,0xc183ffe,0xf0c,0x8c01,0x38e0,0xc07f0,0x8c710000,0x180,0x0,0x3838,
+      0x1980000,0x0,0x71c,0x7000f0,0x700f00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x3fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
+      0x703871b8,0x701c701c,0x701c701c,0x701c07e0,0x7c1c7038,0x70387038,0x703801c0,0x7ff03838,0x3c1c3c1c,0x3c1c3c1c,0x3c1c3c1c,
+      0x3fff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x391c3838,0x38383838,0x38380ee0,
+      0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffc,0x9c01ce,0x70f60000,0x70000e0,0x180,
+      0x0,0x700,0x381c01c0,0x780001c,0x7ffc001c,0x381c0380,0x381c003c,0x0,0x3e07ffc,0xf800380,0x63031ffc,0x70387000,0x70387000,
+      0x7000701c,0x703801c0,0x7070f0,0x7000701c,0x71b8701c,0x7000701c,0x70700038,0x1c07038,0x7e03e7c,0xf7801c0,0x1e000380,0x1c001c0,
+      0x0,0x381c,0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7000078,0x7003838,0xee03a5c,
+      0x7c00fe0,0x78001c0,0x18001c0,0x0,0x1c003ef8,0x380,0x0,0x1000100,0x810,0x383000,0x70703ef8,0x1e003ef8,0x3ef80000,0x0,0x7c0,
+      0x0,0x0,0x78000c,0xe1c03ef8,0x78001c0,0x1c0,0x1c200700,0x63001c0,0x18003f8,0x4e12,0x1c70,0xc0000,0x4c320000,0x180,0x0,0x3838,
+      0x1980000,0x0,0xe38,0x700118,0x701e00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x7fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
+      0x703871b8,0x701c701c,0x701c701c,0x701c0e70,0x7c1c7038,0x70387038,0x703801c0,0x7fc0381c,0x381c381c,0x381c381c,0x381c381c,
+      0x78e03800,0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x3b1c3838,0x38383838,0x38380fe0,
+      0x381c0fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1860,0x9c0186,0x707e0000,0x30000c0,0x180,
+      0x0,0xe00,0x183801c0,0xf00001c,0xe0001c,0x181c0380,0x381c0038,0x0,0xfc0000,0x7e000000,0x61873c1e,0x70383800,0x70707000,0x7000381c,
+      0x703801c0,0x707070,0x7000701c,0x70f83838,0x70003838,0x70780038,0x1c07038,0x7e03c3c,0xe3801c0,0x1c000380,0xe001c0,0x0,0x381c,
+      0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01ef0,0x380739c,0x3838381c,0x381c381c,0x7000038,0x7003838,0xfe03e7c,0xfe007c0,
+      0x70001c0,0x18001c0,0x0,0xe001ff0,0x380,0x0,0x1000100,0x162c,0x381800,0x30701ff0,0x1c001ff0,0x1ff00000,0x0,0x3c0,0x0,0x0,
+      0x380018,0xe1c01ff0,0x70001c0,0x1c0,0x1c200700,0xff801c0,0x18000f0,0x63e6,0xe38,0x0,0x6c3e0000,0x0,0x0,0x3838,0x1980000,0x0,
+      0x1c70,0xf0000c,0xf01c00,0x3c1e3c1e,0x3c1e3c1e,0x3c1e3c1c,0x70e03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x707070f8,
+      0x38383838,0x38383838,0x38381c38,0x38387038,0x70387038,0x703801c0,0x7000381c,0x381c381c,0x381c381c,0x381c381c,0x70e03800,
+      0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0380,0x3e1c3838,0x38383838,0x383807c0,0x381c07c0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18c0,0x9c0186,0x783c0000,0x38001c0,0x180,0x3800000,
+      0x3800e00,0x1c3801c0,0x1e00003c,0xe00038,0x1c1c0780,0x381c0038,0x3800380,0x3c0000,0x78000000,0x61ff380e,0x70383808,0x70707000,
+      0x7000381c,0x703801c0,0x40707078,0x7000701c,0x70f83838,0x70003838,0x70384038,0x1c07038,0x7e03c3c,0x1e3c01c0,0x3c000380,0xe001c0,
+      0x0,0x383c,0x3c381c00,0x1c3c1c00,0x3801c3c,0x383801c0,0xe01c78,0x380739c,0x38381c38,0x3c381c3c,0x7000038,0x7003878,0x7c01e78,
+      0x1ef007c0,0xf0001c0,0x18001c0,0x0,0xe000ee0,0x7800380,0xe380000,0x1001ff0,0x2242,0x40380c00,0x38700ee0,0x3c000ee0,0xee00000,
+      0x0,0x0,0x0,0x0,0x380030,0xe1c00ee0,0xf0001c0,0x1c0,0xe200700,0xdd801c0,0x1800038,0x300c,0x71c,0x0,0x300c0000,0x0,0x0,0x3838,
+      0x1980000,0x0,0x38e0,0xb0000c,0xb01c08,0x380e380e,0x380e380e,0x380e380e,0x70e03808,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
+      0x707070f8,0x38383838,0x38383838,0x3838381c,0x38387038,0x70387038,0x703801c0,0x7000381c,0x383c383c,0x383c383c,0x383c383c,
+      0x70e01c00,0x1c001c00,0x1c001c00,0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383878,0x38783878,0x387807c0,
+      0x3c3807c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x18c0,0x10b801ce,0x3c3e0000,0x38001c0,0x180,
+      0x3800000,0x3801c00,0x1e7801c0,0x3c002078,0xe02078,0x1c380700,0x1c3810f0,0x3800380,0x40000,0x40000380,0x307b380e,0x70701e18,
+      0x70e07000,0x70001c1c,0x703801c0,0x60e0703c,0x7000701c,0x70f83c78,0x70003c70,0x703c70f0,0x1c03870,0x3c01c3c,0x3c1c01c0,0x78000380,
+      0x7001c0,0x0,0x3c7c,0x3c381e18,0x1c7c1e0c,0x3801c3c,0x383801c0,0xe01c38,0x3c0739c,0x38381c38,0x3c381c3c,0x7001078,0x7803c78,
+      0x7c01c38,0x1c780380,0x1e0001c0,0x18001c0,0x0,0x70c06c0,0x7000380,0xe300000,0x1000100,0x2142,0x70f00600,0x3c7006c0,0x780006c0,
+      0x6c00000,0x0,0x0,0x0,0x0,0x10780060,0x73e206c0,0x1e0001c0,0x1c0,0x7240700,0x180c01c0,0x1800018,0x1818,0x30c,0x0,0x18180000,
+      0x0,0x0,0x3c78,0x1980000,0x0,0x30c0,0x130000c,0x1301c18,0x380e380e,0x380e380e,0x380e380e,0x70e01e18,0x70007000,0x70007000,
+      0x1c001c0,0x1c001c0,0x70e070f8,0x3c783c78,0x3c783c78,0x3c781008,0x7c783870,0x38703870,0x387001c0,0x70003a3c,0x3c7c3c7c,0x3c7c3c7c,
+      0x3c7c3c7c,0x79f11e18,0x1e0c1e0c,0x1e0c1e0c,0x1c001c0,0x1c001c0,0x1c783838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383c78,0x3c783c78,
+      0x3c780380,0x3c380380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x38c0,0x1ff800fc,0x1fee0000,
+      0x1800180,0x180,0x3800000,0x3801c00,0xff01ffc,0x3ffc3ff0,0xe03ff0,0xff00700,0x1ff81fe0,0x3800380,0x0,0x380,0x3000780f,0x7ff00ff8,
+      0x7fc07ff8,0x70000ffc,0x70381ffc,0x7fe0701c,0x7ff8701c,0x70781ff0,0x70001ff0,0x701c7ff0,0x1c01fe0,0x3c01c38,0x380e01c0,0x7ffc0380,
+      0x7001c0,0x0,0x1fdc,0x3ff00ff0,0xffc0ffc,0x3800fdc,0x38383ffe,0xe01c3c,0x1fc739c,0x38380ff0,0x3ff00ffc,0x7001ff0,0x3f81fb8,
+      0x7c01c38,0x3c3c0380,0x1ffc01c0,0x18001c0,0x0,0x3fc0380,0x7000380,0xc70718c,0x1000100,0x2244,0x7ff00200,0x1fff0380,0x7ffc0380,
+      0x3800000,0x0,0x0,0x0,0x0,0x1ff000c0,0x7f7e0380,0x1ffc01c0,0x1c0,0x3fc3ffe,0x1c0,0x1800018,0x7e0,0x104,0x0,0x7e00000,0x7ffe,
+      0x0,0x3fde,0x1980000,0x0,0x2080,0x3300018,0x3300ff0,0x780f780f,0x780f780f,0x780f780e,0xf0fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,
+      0x1ffc1ffc,0x7fc07078,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x7ff01fe0,0x1fe01fe0,0x1fe001c0,0x70003bf8,0x1fdc1fdc,0x1fdc1fdc,
+      0x1fdc1fdc,0x3fbf0ff0,0xffc0ffc,0xffc0ffc,0x3ffe3ffe,0x3ffe3ffe,0xff03838,0xff00ff0,0xff00ff0,0xff00000,0x3ff01fb8,0x1fb81fb8,
+      0x1fb80380,0x3ff00380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x31c0,0x7e00078,0x7cf0000,0x1800180,
+      0x0,0x3800000,0x3803800,0x3c01ffc,0x3ffc0fe0,0xe01fc0,0x3e00e00,0x7e00f80,0x3800380,0x0,0x380,0x18007007,0x7fc003f0,0x7f007ff8,
+      0x700003f0,0x70381ffc,0x3f80701e,0x7ff8701c,0x707807c0,0x700007c0,0x701e1fc0,0x1c00fc0,0x3c01818,0x780f01c0,0x7ffc0380,0x3801c0,
+      0x0,0xf9c,0x39e003e0,0x79c03f0,0x380079c,0x38383ffe,0xe01c1e,0x7c739c,0x383807e0,0x39e0079c,0x7000fc0,0x1f80f38,0x3801c38,
+      0x781e0380,0x1ffc01c0,0x18001c0,0x0,0x1f80100,0xe000700,0x1c60718c,0x1000100,0x1e3c,0x1fc00100,0x7ff0100,0x7ffc0100,0x1000000,
+      0x0,0x0,0x0,0x0,0xfc00080,0x3e3c0100,0x1ffc01c0,0x1c0,0xf83ffe,0x1c0,0x1800838,0x0,0x0,0x0,0x0,0x7ffe,0x0,0x3b9e,0x1980000,
+      0x0,0x0,0x2300038,0x23003e0,0x70077007,0x70077007,0x70077007,0xe0fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007078,
+      0x7c007c0,0x7c007c0,0x7c00000,0xc7c00fc0,0xfc00fc0,0xfc001c0,0x700039f0,0xf9c0f9c,0xf9c0f9c,0xf9c0f9c,0x1f1e03e0,0x3f003f0,
+      0x3f003f0,0x3ffe3ffe,0x3ffe3ffe,0x7e03838,0x7e007e0,0x7e007e0,0x7e00000,0x63e00f38,0xf380f38,0xf380380,0x39e00380,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x3000000,0x3800,0x0,0x0,0x0,0x0,
+      0x0,0x300,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0,0x0,0x0,0x0,0x0,0x380,0x3801c0,0x0,0x0,0x0,0x0,0x1c,0x0,0xe00000,
+      0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1c0,0x18001c0,0x0,0x0,0xe000700,0x18600000,0x1000100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800ff0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0x1800000,0x0,0x6300070,0x6300000,0x0,
+      0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000000,
+      0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x7000000,
+      0x7000,0x0,0x0,0x0,0x0,0x0,0x700,0x0,0x0,0xf040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x3f0,0x1c0fc0,0x0,0x0,
+      0x0,0x0,0x1c,0x0,0xe00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1e0,0x18003c0,0x0,0x0,0xc000700,0x18c00000,0x1000000,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x18007e0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
+      0x0,0x7f800e0,0x7f80000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,
+      0x0,0x600600,0x0,0x6000000,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,
+      0x3f0,0xfc0,0x0,0x0,0x0,0x0,0x838,0x0,0x1e00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0xf00,0xfc,0x1801f80,0x0,0x0,0x8008e00,0x30c00000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
+      0x0,0x3001c0,0x300000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0xf00,0x38000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0xff0,0x0,0x1fc00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3e00,0x7c,0x1801f00,0x0,0x0,0x800fe00,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7c00000,0x0,0x3001fc,0x300000,
+      0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x3e00,0x38003e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x7e0,0x0,0x1f000000,
+      0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3c00,0x0,0x1800000,0x0,0x0,0x7800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00,0x38003c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+    // Definition of a 29x57 font (extra large size).
+    const unsigned int font29x57[29*57*256/32] = {
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x781e00,0x0,0x0,0x7,0x81e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0000,0xf8000,0x7e00000,0x0,0x7,
+      0xc0000000,0x0,0x7c00,0xf80,0x7e000,0x0,0x7c00000,0xf80000,0x7e000000,0x0,0x0,0x1f00,0x3e0,0x1f800,0x0,0x0,0x0,0x3,0xe0000000,
+      0x7c00003f,0x0,0xf8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x3c3c00,0x0,0x0,0x3,0xc3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0000,
+      0x1f0000,0x7e00000,0xf838001f,0xf80001f,0xf0000000,0x0,0x3e00,0x1f00,0x7e000,0x3e1f000,0x3e00000,0x1f00000,0x7e00003e,0x1f000000,
+      0x3e0,0xe0000f80,0x7c0,0x1f800,0x3e0e00,0x7c3e000,0x0,0x1,0xf0000000,0xf800003f,0x1f0f,0x800001f0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e7800,0x0,0x0,
+      0x1,0xe7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x1e0000,0xff00001,0xfe38001f,0xf80003f,
+      0xf8000000,0x0,0x1e00,0x1e00,0xff000,0x3e1f000,0x1e00000,0x1e00000,0xff00003e,0x1f000000,0x7f8,0xe0000780,0x780,0x3fc00,0x7f8e00,
+      0x7c3e000,0x0,0x0,0xf0000000,0xf000007f,0x80001f0f,0x800001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xef000,0x0,0x0,0x0,0xef000000,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000,0x3c0000,0x1e780003,0xfff8001f,0xf80003c,0x78000000,0x0,0xf00,0x3c00,0x1e7800,
+      0x3e1f000,0xf00000,0x3c00001,0xe780003e,0x1f000000,0xfff,0xe00003c0,0xf00,0x79e00,0xfffe00,0x7c3e000,0x0,0x0,0x78000001,0xe00000f3,
+      0xc0001f0f,0x800003c0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x78000,0x780000,0x3c3c0003,0x8ff0001f,0xf800078,0x3c000000,0x0,0x780,0x7800,0x3c3c00,0x3e1f000,0x780000,0x7800003,0xc3c0003e,
+      0x1f000000,0xe3f,0xc00001e0,0x1e00,0xf0f00,0xe3fc00,0x7c3e000,0x0,0x0,0x3c000003,0xc00001e1,0xe0001f0f,0x80000780,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x1f,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc00,0x7e000,0xfe000,0x0,0x3c000,0xf00000,0x781e0003,
+      0x83e0001f,0xf800070,0x1c000000,0x0,0x3c0,0xf000,0x781e00,0x3e1f000,0x3c0000,0xf000007,0x81e0003e,0x1f000000,0xe0f,0x800000f0,
+      0x3c00,0x1e0780,0xe0f800,0x7c3e000,0x0,0x0,0x1e000007,0x800003c0,0xf0001f0f,0x80000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf8000000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ff800,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x78,0xf000000,0x0,0x0,0x780f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ffc00,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x3e000,0x3e00000,0x0,0x78,0x3c000000,0x0,0x1f000,0x3e0,
+      0x3e000,0x0,0x1f000000,0x3e0000,0x3e000000,0x0,0x0,0x7c00,0xf8,0xf800,0x0,0x0,0x0,0xf,0x80000000,0x1f00001f,0x0,0x3e,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x781c0000,0x38,0xe000000,0x0,0x0,0x380e0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x39c00,0x1ce000,0x303e00,
+      0x0,0x0,0x0,0x0,0x0,0x78,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,
+      0x0,0x0,0xf80000,0x7c000,0x3e00000,0xf0380000,0x70,0x1c000000,0x0,0xf800,0x7c0,0x3e000,0x0,0xf800000,0x7c0000,0x3e000000,
+      0x0,0x3c0,0xe0003e00,0x1f0,0xf800,0x3c0e00,0x0,0x0,0x7,0xc0000000,0x3e00001f,0x0,0x7c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0xff,0x0,
+      0xf8,0xf8000,0x1c000,0x0,0x0,0x0,0x0,0x1f,0xc0000000,0x1ff8,0xff00,0x0,0x0,0x3fe000,0x0,0x1fc00001,0xfe000000,0x0,0x0,0x0,
+      0x0,0x7f800,0x0,0x0,0x0,0xff00000,0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf8000000,0xfe,0x0,0x7f80,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x780000,0x1,0xe0000000,0x0,0x780000,0x3,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x3fc00,0x0,0x0,0x1fc000,0x0,0x0,0x0,0x1fc0,
+      0x0,0xff000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe1c0000,0x1c,0x1c000000,0x0,0x0,0x1c1c0,0x0,0x0,0x0,0x0,0x1fe0000,
+      0x0,0x0,0x1ff,0x1f0f8,0x0,0xff000,0x0,0x0,0x0,0x3f,0xff00000f,0x80000000,0xfe0,0x3f80,0xf00,0x0,0x0,0x0,0x1,0xf8000003,0xe0000000,
+      0x1c00,0xe000,0xe00,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,
+      0x7f0000,0x0,0x1fc07000,0x0,0x0,0x0,0x0,0x0,0x3f800,0x780000,0x78000,0x7f00001,0xfc38001f,0xf800070,0x1c000000,0x0,0x7800,
+      0x780,0x7f000,0x3e1f000,0x7800000,0x780000,0x7f00003e,0x1f0003f0,0x7f0,0xe0001e00,0x1e0,0x1fc00,0x7f0e00,0x7c3e000,0x0,0x3,
+      0xc0000000,0x3c00003f,0x80001f0f,0x80000078,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x1e078000,0x30000000,0x3ff,0xc00001e0,0xf0,
+      0x78000,0x1c000,0x0,0x0,0x0,0x0,0x1e0007f,0xf000007e,0x1ffff,0x7ffe0,0x1f80,0x3ffff80,0xfff803,0xfffff800,0xfff80007,0xff800000,
+      0x0,0x0,0x0,0x0,0x1ffe00,0x0,0xfe0003,0xfff80000,0x3ffe01ff,0xe00003ff,0xffe01fff,0xff0003ff,0xe01e0007,0x803ffff0,0xfff80,
+      0x3c000fc0,0x7800001f,0x8003f07e,0x1e000f,0xfe0007ff,0xf00003ff,0x8007ffe0,0x1fff8,0x7fffffe,0xf0003c1,0xe000079e,0xf1f,0x1f3e0,
+      0x1f01ff,0xfff8003f,0xf003c000,0x7fe0,0x3f00,0x0,0x3c0000,0x1,0xe0000000,0x0,0x780000,0xf,0xfe000000,0x78000,0x3c00,0xf000,
+      0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xfc0000f0,0x3fe00,0x0,0x0,0xfff00,0x0,0x0,0x3fe000,
+      0x0,0x0,0x0,0x1dc0,0x0,0x3fff00,0x0,0x3ffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff1c07ff,0x3c0f001e,0x3c000000,
+      0x0,0x0,0x1e3c0,0xf80007c,0x0,0x780000,0x0,0xfff8000,0x3e00,0x1f00000,0x7ff,0xc001f0f8,0x0,0x3ffc00,0x0,0x0,0x0,0x3f,0xff00003f,
+      0xe0000000,0x3ff8,0xffe0,0x1e00,0x0,0xfffc00,0x0,0x7,0xf800000f,0xf8000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,
+      0x3f800001,0xfc00003f,0xf80000ff,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,
+      0xfc00,0x3c001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x0,0x7ff8f0f0,0x3c0780,0x1e03c00,0xf01e000,0x783e0001,0xf01e0000,0xffe00,
+      0x3c0000,0xf0000,0x7700001,0xfe38001f,0xf800070,0x1c000000,0x0,0x3c00,0xf00,0x77000,0x3e1f000,0x3c00000,0xf00000,0x7700003e,
+      0x1f0000f8,0xc0007f8,0xe0000f00,0x3c0,0x1dc00,0x7f8e00,0x7c3e000,0x0,0x1,0xe0000000,0x7800003b,0x80001f0f,0x800000f0,0x1e0000,
+      0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x780000,0x3c1e0000,0x1e070000,0x300001f0,0x7ff,0xc00001e0,0x1e0,0x7c000,0x1c000,0x0,0x0,0x0,0x0,0x3c000ff,0xf80007fe,
+      0x3ffff,0x801ffff8,0x1f80,0x3ffff80,0x3fff803,0xfffff801,0xfffc000f,0xffc00000,0x0,0x0,0x0,0x0,0x7fff80,0x0,0xfe0003,0xffff0000,
+      0xffff01ff,0xfc0003ff,0xffe01fff,0xff000fff,0xf01e0007,0x803ffff0,0xfff80,0x3c001f80,0x7800001f,0xc007f07e,0x1e001f,0xff0007ff,
+      0xfc0007ff,0xc007fffc,0x3fffc,0x7fffffe,0xf0003c1,0xf0000f9e,0xf0f,0x8003e1e0,0x1e01ff,0xfff8003f,0xf001e000,0x7fe0,0x3f00,
+      0x0,0x1e0000,0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x1fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x3de0,0x0,0x7fff80,0x0,0xfffff80,
+      0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe7bc07ff,0x3e1f000f,0x78000000,0x0,0x0,0xf780,0x7800078,0x0,0x780000,0x180000,
+      0x1fff8000,0x1e00,0x1e0003c,0xfff,0xc001f0f8,0x0,0x7ffe00,0x0,0x0,0x0,0x3f,0xff00007f,0xf0000000,0x3ffc,0xfff0,0x3c00,0x0,
+      0x7fffc00,0x0,0x7,0xf800003f,0xfe000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xe00001ff,
+      0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000fc00,0x3c003ffe,0x1fff0,
+      0xfff80,0x7ffc00,0x3ffe000,0x0,0xfffce0f0,0x3c0780,0x1e03c00,0xf01e000,0x781e0001,0xe01e0000,0x3fff00,0x1e0000,0x1e0000,0xf780003,
+      0xcf78001f,0xf800078,0x3c000000,0x0,0x1e00,0x1e00,0xf7800,0x3e1f000,0x1e00000,0x1e00000,0xf780003e,0x1f0000fc,0x7c000f3d,
+      0xe0000780,0x780,0x3de00,0xf3de00,0x7c3e000,0x0,0x0,0xf0000000,0xf000007b,0xc0001f0f,0x800001e0,0x1e0000,0x3e1f00,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
+      0x3c1e0000,0x1e0f0000,0x300007fc,0xfff,0xc00001e0,0x1e0,0x3c000,0x1c000,0x0,0x0,0x0,0x0,0x3c001ff,0xfc001ffe,0x3ffff,0xc01ffffc,
+      0x3f80,0x3ffff80,0x7fff803,0xfffff803,0xfffe001f,0xffe00000,0x0,0x0,0x0,0x0,0xffff80,0x7f800,0xfe0003,0xffff8001,0xffff01ff,
+      0xff0003ff,0xffe01fff,0xff001fff,0xf01e0007,0x803ffff0,0xfff80,0x3c003f00,0x7800001f,0xc007f07f,0x1e003f,0xff8007ff,0xff000fff,
+      0xe007ffff,0x7fffc,0x7fffffe,0xf0003c0,0xf0000f1e,0xf07,0x8003c1f0,0x3e01ff,0xfff8003f,0xf001e000,0x7fe0,0x7f80,0x0,0xe0000,
+      0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,
+      0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x3fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x78f0,0x0,0xffff80,0x0,0x3fffff80,0x1f,
+      0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc7f80070,0x3e1f0007,0x70000000,0x0,0x0,0x7700,0x7c000f8,0x0,0x780000,0x180000,
+      0x3fff8000,0x1f00,0x3e0003c,0x1f03,0xc001f0f8,0x0,0x703f00,0x0,0x0,0x0,0x3f,0xff0000f0,0xf8000000,0x303e,0xc0f8,0x7800,0x0,
+      0xffffc00,0x0,0x7,0x3800003e,0x3e000000,0x1c00,0xe000,0x3c00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00000f,0xe00001ff,
+      0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000fe00,0x3c007fff,0x3fff8,
+      0x1fffc0,0xfffe00,0x7fff000,0x1,0xffffc0f0,0x3c0780,0x1e03c00,0xf01e000,0x781f0003,0xe01e0000,0x3fff80,0xe0000,0x3c0000,0x1e3c0003,
+      0x8ff0001f,0xf80003c,0x78000000,0x0,0xe00,0x3c00,0x1e3c00,0x3e1f000,0xe00000,0x3c00001,0xe3c0003e,0x1f00007f,0xf8000e3f,0xc0000380,
+      0xf00,0x78f00,0xe3fc00,0x7c3e000,0x0,0x0,0x70000001,0xe00000f1,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0000,
+      0x30000ffe,0xf80,0xc00001e0,0x3c0,0x1e000,0x101c040,0x0,0x0,0x0,0x0,0x78003f0,0x7e001ffe,0x3f807,0xe01f00fe,0x3f80,0x3ffff80,
+      0x7e01803,0xfffff007,0xe03f003f,0x3f00000,0x0,0x0,0x0,0x0,0xfc0fc0,0x3ffe00,0xfe0003,0xffffc003,0xf81f01ff,0xff8003ff,0xffe01fff,
+      0xff003f01,0xf01e0007,0x803ffff0,0xfff80,0x3c007e00,0x7800001f,0xc007f07f,0x1e007e,0xfc007ff,0xff801f83,0xf007ffff,0x800fc07c,
+      0x7fffffe,0xf0003c0,0xf0000f0f,0x1e07,0xc007c0f8,0x7c01ff,0xfff8003c,0xf000,0x1e0,0xffc0,0x0,0xf0000,0x1,0xe0000000,0x0,0x780000,
+      0x3e,0x0,0x78000,0x3c00,0xf000,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0x800000f0,0x1f80,
+      0x0,0x0,0x7e0780,0x0,0x0,0x1f82000,0x0,0x0,0x0,0x7070,0x0,0x1f80f80,0x0,0x7fffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x1,0xc3f80070,0x3f3f0007,0xf0000000,0x0,0x0,0x7f00,0x3e001f0,0x0,0x780000,0x180000,0x7f018000,0xf80,0x7c0003c,0x3e00,
+      0x4001f0f8,0xfe00,0x400f00,0x0,0x0,0x0,0x7f000000,0xe0,0x38000000,0x1e,0x38,0x7800,0x0,0x1ffe1c00,0x0,0x0,0x38000078,0xf000000,
+      0x1c00,0xe000,0x7f800,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xf00001ff,0xffc03f81,0xf007ffff,0xc03ffffe,
+      0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf800fe00,0x3c00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,
+      0x3,0xf07fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x780f8007,0xc01e0000,0x7e0fc0,0xf0000,0x3c0000,0x1c1c0003,0x87f0001f,0xf80003f,
+      0xf8000000,0x0,0xf00,0x3c00,0x1c1c00,0x3e1f000,0xf00000,0x3c00001,0xc1c0003e,0x1f00003f,0xc0000e1f,0xc00003c0,0xf00,0x70700,
+      0xe1fc00,0x7c3e000,0x0,0x0,0x78000001,0xe00000e0,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0001,0xff801e0f,
+      0x1f00,0x1e0,0x3c0,0x1e000,0x3c1c1e0,0x0,0x0,0x0,0x0,0x78007c0,0x1f001f9e,0x3c001,0xf010003e,0x7780,0x3c00000,0xf800000,0xf007,
+      0xc01f007c,0x1f80000,0x0,0x0,0x0,0x0,0xe003e0,0x7fff00,0x1ef0003,0xc007e007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x301e0007,
+      0x80007800,0x780,0x3c00fc00,0x7800001f,0xe00ff07f,0x1e00f8,0x3e00780,0x1fc03e00,0xf807801f,0xc01f001c,0xf000,0xf0003c0,0xf0000f0f,
+      0x1e03,0xc00f8078,0x780000,0xf0003c,0xf000,0x1e0,0x1f3e0,0x0,0x78000,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,
+      0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0xf0,0xf80,0x0,0x0,0xf80180,0x0,0x0,0x1e00000,
+      0x0,0x0,0x0,0xe038,0x0,0x3e00380,0x0,0xfe0f0000,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc0f00070,0x3b370003,0xe0000000,
+      0x0,0x0,0x3e00,0x1e001e0,0x0,0x780000,0x180000,0x7c000000,0x780,0x780003c,0x3c00,0x0,0x7ffc0,0x780,0x0,0x0,0x3,0xffe00000,
+      0x1c0,0x3c000000,0xe,0x38,0xf000,0x0,0x3ffe1c00,0x0,0x0,0x38000078,0xf000000,0x1c00,0xe000,0x7f000,0xf000,0x3de000,0x1ef0000,
+      0xf780000,0x7bc00003,0xde00001e,0xf00003e7,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+      0xe0001e03,0xfc00fe00,0x3c01f007,0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x7,0xc01f80f0,0x3c0780,0x1e03c00,0xf01e000,0x78078007,
+      0x801e0000,0x7803c0,0x78000,0x780000,0x380e0003,0x81e00000,0x1f,0xf0000000,0x0,0x780,0x7800,0x380e00,0x0,0x780000,0x7800003,
+      0x80e00000,0x1ff,0x80000e07,0x800001e0,0x1e00,0xe0380,0xe07800,0x0,0x0,0x0,0x3c000003,0xc00001c0,0x70000000,0x780,0x1e0000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x780000,0x3c1e0000,0x3c0e0007,0xfff01c07,0x1e00,0x1e0,0x780,0xf000,0x3e1c3e0,0x0,0x0,0x0,0x0,0xf0007c0,0x1f00181e,0x20000,
+      0xf000001f,0xf780,0x3c00000,0x1f000000,0x1f00f,0x800f8078,0xf80000,0x0,0x0,0x0,0x0,0x8003e0,0x1fc0f80,0x1ef0003,0xc001e007,
+      0x800101e0,0x7e003c0,0x1e00,0x7800,0x101e0007,0x80007800,0x780,0x3c00f800,0x7800001e,0xe00ef07f,0x801e00f0,0x1e00780,0x7c03c00,
+      0x78078007,0xc01e0004,0xf000,0xf0003c0,0x78001e0f,0x1e03,0xe00f807c,0xf80000,0x1f0003c,0x7800,0x1e0,0x3e1f0,0x0,0x3c000,0x1,
+      0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,
+      0x1e,0xf0,0x780,0x0,0x0,0x1f00080,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x1e03c,0x0,0x3c00080,0x0,0xf80f0000,0x0,0x1f0000,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x3bf70003,0xe0000000,0x0,0x0,0x3e00,0x1f003e0,0x0,0x780000,0x180000,0x78000000,0x7c0,0xf80003c,
+      0x3c00,0x0,0x1f01f0,0x780,0x0,0x0,0xf,0x80f80000,0x1c0,0x1c000000,0xe,0x38,0x1e000,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,0x7800000,
+      0x1c00,0xe000,0x7fc00,0xf000,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x80007800,0x10078000,0x3c0000,
+      0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00ff00,0x3c01e003,0xc00f001e,0x7800f0,0x3c00780,0x1e003c00,
+      0x7,0x800f00f0,0x3c0780,0x1e03c00,0xf01e000,0x7807c00f,0x801e0000,0xf803c0,0x3c000,0xf00000,0x780f0000,0x0,0x7,0xc0000000,
+      0x0,0x3c0,0xf000,0x780f00,0x0,0x3c0000,0xf000007,0x80f00000,0x7ff,0xc0000000,0xf0,0x3c00,0x1e03c0,0x0,0x0,0x0,0x0,0x1e000007,
+      0x800003c0,0x78000000,0xf00,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c1e001f,0xfff03803,0x80001e00,0x1e0,0x780,0xf000,0xf9cf80,
+      0x0,0x0,0x0,0x0,0xf000780,0xf00001e,0x0,0xf800000f,0xe780,0x3c00000,0x1e000000,0x1e00f,0x78078,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,
+      0x3f003c0,0x1ef0003,0xc000f00f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,0x3c01f000,0x7800001e,0xe00ef07f,
+      0x801e01f0,0x1e00780,0x3c07c00,0x78078003,0xc03e0000,0xf000,0xf0003c0,0x78001e0f,0x1e01,0xf01f003c,0xf00000,0x3e0003c,0x7800,
+      0x1e0,0x7c0f8,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,
+      0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x8,0x40,0x0,0x7e0000,0x7c00000,0x1,0xf00f0000,
+      0x0,0x3e0000,0x0,0x3f,0xfc0,0xfc3f0,0xfc3f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,0xf003c0,0x0,0x0,0x180000,0xf8000000,
+      0x3c0,0xf00003c,0x3c00,0x0,0x3c0078,0x7ff80,0x0,0x0,0x1e,0x3c0000,0x1c0,0x1c000000,0xe,0xf0,0x0,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,
+      0x7800000,0x1c00,0xe000,0x3c00,0x0,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x8000f800,0x78000,0x3c0000,
+      0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00ff00,0x3c03e003,0xc01f001e,0xf800f0,0x7c00780,0x3e003c00,
+      0xf,0x800f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803c00f,0x1fffc0,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x307,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x781e003f,0xfff03803,
+      0x80001e00,0x1e0,0xf80,0xf000,0x3dde00,0x0,0x0,0x0,0x0,0xf000f00,0x780001e,0x0,0x7800000f,0x1e780,0x3c00000,0x3e000000,0x3e00f,
+      0x780f0,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,0x7c001e0,0x3ef8003,0xc000f00f,0x1e0,0xf003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,
+      0x3c03e000,0x7800001e,0xf01ef07b,0xc01e01e0,0xf00780,0x3e07800,0x3c078003,0xe03c0000,0xf000,0xf0003c0,0x78001e0f,0x1e00,0xf01e003e,
+      0x1f00000,0x3c0003c,0x7800,0x1e0,0x78078,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,
+      0xe70000,0x7800000,0x1,0xe00f0000,0x0,0x3c0000,0x0,0x3f,0xfc0,0xfc1f0,0x1f83f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,
+      0xf807c0,0x0,0x0,0x180000,0xf0000000,0x3e0,0x1f00003c,0x3e00,0x0,0x70001c,0x3fff80,0x0,0x0,0x38,0xe0000,0x1c0,0x1c000078,
+      0x1c,0x1fe0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x7df000,0x3ef8000,0x1f7c0000,0xfbe00007,
+      0xdf00003c,0x780003c7,0x8000f000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f780,
+      0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0xf80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803e01f,0x1ffff8,0xf001e0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x0,0x0,0x1e0000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x780000,0x3c1e0000,0x781e003e,0x30703803,0x80001e00,0x1e0,0xf00,0x7800,0xff800,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,
+      0x0,0x7800000f,0x3c780,0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x2000000,0x800000,0x1e0,0x78000e0,0x3c78003,
+      0xc000f01e,0x1e0,0xf803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x701cf07b,0xc01e01e0,0xf00780,0x1e07800,
+      0x3c078001,0xe03c0000,0xf000,0xf0003c0,0x7c003e0f,0x1e00,0xf83e001e,0x1e00000,0x7c0003c,0x3c00,0x1e0,0xf807c,0x0,0x0,0x1fe0001,
+      0xe1fc0000,0x7f00003,0xf8780007,0xf000003c,0x7f0,0x783f0,0x0,0x0,0x7800000,0x1e00000,0x3e0f8000,0xfc00007,0xf8000007,0xf00001fc,
+      0xf,0xc0003fc0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,0x1818000,
+      0x7800000,0x1,0xe00f0000,0x0,0x7c0000,0x0,0x1f,0x80001f80,0x7c1f8,0x1f83e0,0x0,0x0,0x0,0x70,0x38c70007,0xf8000000,0x7f03,
+      0xf0000000,0x0,0x780780,0x0,0x0,0xfe0000,0xf0000000,0x1e0,0x1e00003c,0x3f00,0x0,0xe07f0e,0x7fff80,0x0,0x0,0x70,0x70000,0x1c0,
+      0x1c000078,0x3c,0x1fc0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x78f000,0x3c78000,0x1e3c0000,
+      0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
+      0xf80f780,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0x1f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801e01e,0x1ffffc,
+      0xf007e0,0x3fc000,0x1fe0000,0xff00000,0x7f800003,0xfc00001f,0xe0000fc0,0xfc00007f,0xfe0,0x7f00,0x3f800,0x1fc000,0x0,0x0,0x0,
+      0x1,0xf000001f,0x80000ff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x1f80000,0x1fc1e000,0x0,0x0,0x0,0x0,0x1e1fc0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,
+      0x781c007c,0x30003803,0x80001f00,0x1e0,0xf00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,0x0,0x7800000f,0x3c780,
+      0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x1e000000,0xf00000,0x3e0,0xf0000e0,0x3c78003,0xc000f01e,0x1e0,0x7803c0,
+      0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c0f8000,0x7800001e,0x701cf079,0xe01e01e0,0xf00780,0x1e07800,0x3c078001,0xe03c0000,
+      0xf000,0xf0003c0,0x3c003c0f,0x3e00,0x787c001f,0x3e00000,0xf80003c,0x3c00,0x1e0,0x1f003e,0x0,0x0,0x1fffc001,0xe7ff0000,0x3ffe000f,
+      0xfe78003f,0xfc001fff,0xfe001ffc,0xf0078ffc,0x1ffc00,0x7ff000,0x7800f80,0x1e0000f,0x7f1fc01e,0x3ff0001f,0xfe00079f,0xfc0007ff,
+      0x3c003c7f,0xf001fff8,0x1fffff0,0x3c003c0,0xf0000f1e,0xf1f,0x7c1f0,0x1f00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3c00000,0x100000,
+      0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7800000,0x1,0xe00f0000,0x1000000,0xf80000,0x40000002,0xf,0x80001f00,0x7e0f8,0x1f07c0,
+      0x0,0x0,0x0,0x70,0x38c7003f,0xff000000,0xff8f,0xf8000100,0xffffe,0x7c0f80,0x0,0x0,0x3ffc000,0xf0000020,0x1001f0,0x3c00003c,
+      0x1f80,0x0,0x1c3ffc7,0x7c0780,0x0,0x0,0xe3,0xff038000,0xe0,0x38000078,0x78,0x1ff0,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,
+      0x7800000,0x1c00,0xe000,0xe00,0xf000,0x78f000,0x3c78000,0x1e3c0000,0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,
+      0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,
+      0x4000200f,0x3f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801f03e,0x1ffffe,0xf01fe0,0x3fff800,0x1fffc000,0xfffe0007,0xfff0003f,
+      0xff8001ff,0xfc003ff3,0xfe0003ff,0xe0007ff8,0x3ffc0,0x1ffe00,0xfff000,0x3ff80001,0xffc0000f,0xfe00007f,0xf000003f,0xf8003c7f,
+      0xe0003ffc,0x1ffe0,0xfff00,0x7ff800,0x3ffc000,0x1f80000,0xfff1c03c,0x3c01e0,0x1e00f00,0xf007800,0x781f0001,0xf01e7ff0,0x7c0007c,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
+      0x3c1e003f,0xfffff078,0x30003803,0x80000f00,0x1e0,0x1f00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x3c000f00,0x780001e,0x0,0x7800000f,
+      0x78780,0x3c00000,0x3c000000,0x7c00f,0x780f0,0x3c0007,0xe000003f,0x0,0xfe000000,0xfe0000,0x3c0,0x1f000070,0x7c7c003,0xc000f01e,
+      0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c1f0000,0x7800001e,0x783cf079,0xe01e03c0,0xf00780,0x1e0f000,0x3c078001,
+      0xe03c0000,0xf000,0xf0003c0,0x3c003c07,0x81f03c00,0x7c7c000f,0x87c00000,0xf00003c,0x1e00,0x1e0,0x3e001f,0x0,0x0,0x3fffe001,
+      0xefff8000,0x7fff001f,0xff78007f,0xfe001fff,0xfe003ffe,0xf0079ffe,0x1ffc00,0x7ff000,0x7801f00,0x1e0000f,0xffbfe01e,0x7ff8003f,
+      0xff0007bf,0xfe000fff,0xbc003cff,0xf803fffc,0x1fffff0,0x3c003c0,0x78001e1e,0xf0f,0x800f80f0,0x1e00ff,0xffe0001e,0xf0,0x780,
+      0x0,0x0,0x3c00000,0x380000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1008000,0x7800000,0x3,0xe00f0000,0x3800000,0xf00000,0xe0000007,
+      0xf,0x80001f00,0x3e0f8,0x1e07c0,0x0,0x0,0x0,0x70,0x3807007f,0xff800000,0x1ffdf,0xfc000380,0xffffe,0x3e1f00,0x0,0x0,0xfffe000,
+      0xf0000030,0x3800f8,0x7c00003c,0xfc0,0x0,0x18780c3,0xf00780,0x80100,0x0,0xc3,0xffc18000,0xf0,0x78000078,0xf0,0xf0,0x0,0x3c003c0,
+      0xfffe1c00,0x0,0x0,0x380000f0,0x7800801,0x1c00,0xe000,0x1e00,0xf000,0xf8f800,0x7c7c000,0x3e3e0001,0xf1f0000f,0x8f80007c,0x7c000787,
+      0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078001,0xe03c000f,
+      0x1e00078,0xf0003c0,0x78001e00,0xe000701f,0x3fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x7800f87c,0x1e007f,0xf07e00,0x7fffc00,0x3fffe001,
+      0xffff000f,0xfff8007f,0xffc003ff,0xfe007ff7,0xff0007ff,0xf000fffc,0x7ffe0,0x3fff00,0x1fff800,0x3ff80001,0xffc0000f,0xfe00007f,
+      0xf00000ff,0xf8003cff,0xf0007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x1f80001,0xfffb803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,
+      0xe01efff8,0x3c00078,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e003f,0xfffff078,0x30001c07,0xf80,0x1e0,0x1e00,0x3c00,0xff800,0x1e0000,0x0,0x0,0x0,0x3c001e00,
+      0x3c0001e,0x0,0x7800001e,0x70780,0x3c00000,0x78000000,0x78007,0x800f00f0,0x3e0007,0xe000003f,0x3,0xfe000000,0xff8000,0x7c0,
+      0x1e000070,0x783c003,0xc001f01e,0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c3e0000,0x7800001e,0x3838f079,
+      0xe01e03c0,0x780780,0x1e0f000,0x1e078001,0xe03c0000,0xf000,0xf0003c0,0x3c007c07,0x81f03c00,0x3ef80007,0x87800000,0x1f00003c,
+      0x1e00,0x1e0,0x7c000f,0x80000000,0x0,0x3ffff001,0xffffc000,0xffff003f,0xff7800ff,0xff001fff,0xfe007ffe,0xf007bffe,0x1ffc00,
+      0x7ff000,0x7803e00,0x1e0000f,0xffffe01e,0xfff8007f,0xff8007ff,0xff001fff,0xbc003dff,0xf807fffc,0x1fffff0,0x3c003c0,0x78001e0f,
+      0x1e07,0xc01f00f0,0x1e00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7c00000,0x7c0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1018000,0x7800000,
+      0x3,0xc00f0000,0x7c00000,0x1f00001,0xf000000f,0x80000007,0xc0003e00,0x1e07c,0x3e0780,0x0,0x0,0x0,0x70,0x380700ff,0xff800000,
+      0x3ffff,0xfe0007c0,0xffffe,0x1e1e00,0x0,0x780000,0x1fffe000,0xf0000078,0x7c0078,0x7800003c,0xff0,0x0,0x38e0003,0x80f00780,
+      0x180300,0x0,0x1c3,0x81e1c000,0x7f,0xf0000078,0x1e0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800c01,0x80001c00,
+      0xe000,0x603e00,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x7800078,0x3c000f87,0x8001e000,0x78000,0x3c0000,0x1e00000,
+      0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f01,0xf000f81e,
+      0x7bc0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007878,0x1e001f,0xf0f800,0x7fffe00,0x3ffff001,0xffff800f,0xfffc007f,0xffe003ff,
+      0xff007fff,0xff800fff,0xf001fffe,0xffff0,0x7fff80,0x3fffc00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00001ff,0xfc003dff,0xf000ffff,
+      0x7fff8,0x3fffc0,0x1fffe00,0xffff000,0x1f80003,0xffff803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,0xe01ffffc,0x3c00078,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
+      0x3c1e003f,0xfffff078,0x30001e0f,0x300780,0x1e0,0x1e00,0x3c00,0x3dde00,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf800003e,
+      0xf0780,0x3dfc000,0x783f8000,0xf8007,0xc01f00f0,0x3e0007,0xe000003f,0x1f,0xfc000000,0x7ff000,0xf80,0x3e007c70,0x783c003,0xc001e03c,
+      0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,0x80007800,0x780,0x3c7c0000,0x7800001e,0x3878f078,0xf01e03c0,0x780780,0x1e0f000,0x1e078001,
+      0xe03e0000,0xf000,0xf0003c0,0x1e007807,0x83f03c00,0x3ef00007,0xcf800000,0x3e00003c,0xf00,0x1e0,0xf80007,0xc0000000,0x0,0x3e01f801,
+      0xfe07e001,0xf80f007e,0x7f801f8,0x1f801fff,0xfe00fc0f,0xf007f83f,0x1ffc00,0x7ff000,0x7807c00,0x1e0000f,0x87e1e01f,0xe0fc00fc,
+      0xfc007f8,0x1f803f03,0xfc003df0,0x3807e03c,0x1fffff0,0x3c003c0,0x78003e0f,0x1e03,0xe03e00f8,0x3e00ff,0xffe0001e,0xf0,0x780,
+      0x0,0x0,0x7800000,0xfe0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7c00000,0x3,0xc00f0000,0xfe00000,0x3e00003,0xf800001f,
+      0xc0000007,0xc0003e00,0x1e03c,0x3c0f80,0x0,0x0,0x0,0x70,0x380700fc,0x7800000,0x7c1fe,0x3e000fe0,0xffffe,0x1f3e00,0x0,0x780000,
+      0x3f98e000,0xf000003c,0xfcf8007c,0xf800003c,0x3ffc,0x0,0x31c0001,0x80f00f80,0x380700,0x0,0x183,0x80e0c000,0x3f,0xe0000078,
+      0x3c0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x38000078,0xf000e01,0xc003ffe0,0x1fff00,0x7ffc00,0xf000,0xf07800,0x783c000,0x3c1e0001,
+      0xe0f0000f,0x7800078,0x3c000f07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
+      0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf801f01e,0xf3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007cf8,
+      0x1e000f,0x80f0f000,0x7c03f00,0x3e01f801,0xf00fc00f,0x807e007c,0x3f003e0,0x1f80707f,0x8f801f80,0xf003f03f,0x1f81f8,0xfc0fc0,
+      0x7e07e00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00003ff,0xfc003fc1,0xf801f81f,0x800fc0fc,0x7e07e0,0x3f03f00,0x1f81f800,0x1f80007,
+      0xe07f003c,0x3c01e0,0x1e00f00,0xf007800,0x780f8003,0xe01fe07e,0x3e000f8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3f,0xfffff078,0x30000ffe,0x1f007c0,0x0,0x1e00,
+      0x3c00,0xf9cf80,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf00000fc,0x1e0780,0x3fff800,0x78ffe000,0xf0003,0xe03e00f0,
+      0x3e0007,0xe000003f,0x7f,0xe01fffff,0xf00ffc00,0x1f80,0x3c01ff70,0x783c003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,
+      0x80007800,0x780,0x3cfc0000,0x7800001e,0x3c78f078,0xf01e03c0,0x780780,0x3e0f000,0x1e078003,0xc01f0000,0xf000,0xf0003c0,0x1e007807,
+      0x83f83c00,0x1ff00003,0xcf000000,0x3e00003c,0xf00,0x1e0,0x0,0x0,0x0,0x20007801,0xfc03e003,0xe003007c,0x3f803e0,0x7c0003c,
+      0xf807,0xf007e00f,0x3c00,0xf000,0x780f800,0x1e0000f,0x87e1f01f,0x803c00f8,0x7c007f0,0xf803e01,0xfc003f80,0x80f8004,0x3c000,
+      0x3c003c0,0x3c003c0f,0x1e03,0xe03e0078,0x3c0000,0x7c0001e,0xf0,0x780,0x0,0x0,0x3ffff800,0x1ff0000,0x0,0x7800000,0x0,0x18,
+      0xc0,0x0,0x1818000,0x3e00000,0x3,0xc00f0000,0x1ff00000,0x3e00007,0xfc00003f,0xe0000003,0xc0003c00,0xf03c,0x3c0f00,0x0,0x0,
+      0x0,0x70,0x380701f0,0x800000,0x780fc,0x1e001ff0,0x7c,0xf3c00,0x0,0x780000,0x7e182000,0xf000001f,0xfff00ffc,0xffc0003c,0x3cfe,
+      0x0,0x31c0001,0x80f01f80,0x780f00,0x0,0x183,0x80e0c000,0xf,0x80000078,0x780,0x38,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x38000078,
+      0xf000f01,0xe003ffe0,0x1fff00,0x7ff800,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x78000f8,0x3e000f07,0x8003c000,0x78000,
+      0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
+      0x78000f00,0x7c03e01e,0x1e3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78003cf0,0x1e0007,0x80f1e000,0x4000f00,0x20007801,0x3c008,
+      0x1e0040,0xf00200,0x780403f,0x7803e00,0x3007c00f,0x803e007c,0x1f003e0,0xf801f00,0x780000,0x3c00000,0x1e000000,0xf00007f0,
+      0x3e003f00,0x7801f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e003c,0x3c01e0,0x1e00f00,0xf007800,0x78078003,
+      0xc01fc03e,0x1e000f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xf078007c,0x300007fc,0x7e00fe0,0x0,0x1e00,0x3c00,0x3e1c3e0,0x1e0000,0x0,0x0,0x0,0xf0001e00,
+      0x3c0001e,0x1,0xf000fff8,0x1e0780,0x3fffe00,0x79fff000,0x1f0001,0xfffc00f0,0x7e0007,0xe000003f,0x3ff,0x801fffff,0xf003ff80,
+      0x3f00,0x3c03fff0,0xf01e003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3df80000,0x7800001e,
+      0x1c70f078,0x781e03c0,0x780780,0x3c0f000,0x1e078007,0xc01f8000,0xf000,0xf0003c0,0x1e007807,0x83f83c00,0xfe00003,0xff000000,
+      0x7c00003c,0x780,0x1e0,0x0,0x0,0x0,0x7c01,0xf801f007,0xc00100f8,0x1f803c0,0x3c0003c,0x1f003,0xf007c00f,0x80003c00,0xf000,
+      0x783f000,0x1e0000f,0x3c0f01f,0x3e01f0,0x3e007e0,0x7c07c00,0xfc003f00,0xf0000,0x3c000,0x3c003c0,0x3c003c0f,0x1e01,0xf07c007c,
+      0x7c0000,0xfc0001e,0xf0,0x780,0x0,0x0,0x3ffff000,0x3838000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0xff0000,0x3f00000,0x3,0xc00fff00,
+      0x38380000,0x7c0000e,0xe000070,0x70000001,0xe0003c00,0xf01e,0x780e00,0x0,0x0,0x0,0x0,0x1e0,0x0,0x780f8,0xf003838,0xfc,0xffc00,
+      0x0,0x780000,0x7c180000,0xf000000f,0xffe00fff,0xffc0003c,0x783f,0x80000000,0x6380000,0xc0f83f80,0xf81f00,0x0,0x303,0x80e06000,
+      0x0,0x78,0xf00,0x78,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x3800003c,0x3e000f81,0xf003ffe0,0x1fff00,0x1fc000,0xf000,0x1e03c00,
+      0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e000f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,
+      0x3c000001,0xe0001e00,0x3c0f0f0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3e07c01e,0x1e3c0f0,0x3c0780,0x1e03c00,
+      0xf01e000,0x78003ff0,0x1e0007,0x80f1e000,0xf80,0x7c00,0x3e000,0x1f0000,0xf80000,0x7c0001e,0x3c07c00,0x10078007,0x803c003c,
+      0x1e001e0,0xf000f00,0x780000,0x3c00000,0x1e000000,0xf00007c0,0x1e003e00,0x7c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,
+      0xf,0x801f003c,0x3c01e0,0x1e00f00,0xf007800,0x7807c007,0xc01f801f,0x1f001f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xe078003c,0x300001f0,0x3f801ff0,0x0,
+      0x3c00,0x1e00,0x3c1c1e0,0x1e0000,0x0,0x0,0x0,0xf0001e0f,0x3c0001e,0x3,0xe000fff0,0x3c0780,0x3ffff00,0x7bfff800,0x1e0000,0x7ff00078,
+      0x7e0007,0xe000003f,0x1ffc,0x1fffff,0xf0007ff0,0x7e00,0x3c07c3f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,
+      0x1fffff,0x80007800,0x780,0x3ffc0000,0x7800001e,0x1ef0f078,0x781e03c0,0x780780,0x7c0f000,0x1e07801f,0x800ff000,0xf000,0xf0003c0,
+      0xf00f807,0x83b83c00,0xfc00001,0xfe000000,0xf800003c,0x780,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0xc00000f0,0xf80780,0x3c0003c,
+      0x1e001,0xf007c007,0x80003c00,0xf000,0x787e000,0x1e0000f,0x3c0f01f,0x1e01e0,0x1e007c0,0x3c07800,0x7c003f00,0xf0000,0x3c000,
+      0x3c003c0,0x3e007c07,0x80003c00,0xf8f8003c,0x780000,0xf80001e,0xf0,0x780,0x0,0x0,0x7ffff000,0x601c000,0x3,0xffff0000,0x0,
+      0xfff,0xf8007fff,0xc0000000,0x7e003c,0x1fe0000,0xc0003,0xc00fff00,0x601c0000,0xf800018,0x70000c0,0x38000001,0xe0007800,0x701e,
+      0x701e00,0x0,0x0,0x0,0x0,0x1e0,0x6,0x700f8,0xf00601c,0xf8,0x7f800,0x0,0x780000,0xf8180000,0xf000000f,0x87c00fff,0xffc0003c,
+      0xf01f,0xc0000000,0x6380000,0xc07ff780,0x1f03e03,0xfffffe00,0x303,0x81c06000,0x0,0x1ffff,0xfe001e00,0x180f8,0x0,0x3c003c0,
+      0x3ffe1c00,0x3f00000,0x0,0x3800003f,0xfe0007c0,0xf8000000,0x18000000,0xc0000006,0x1f000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,
+      0x3c000f0,0x1e001f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f0f0,
+      0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f0f801e,0x3c3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,
+      0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07c00,0xf0007,0x8078003c,0x3c001e0,0x1e000f00,0x780000,0x3c00000,
+      0x1e000000,0xf0000f80,0x1f003e00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0xf,0x3f003c,0x3c01e0,0x1e00f00,0xf007800,
+      0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe078003f,0xb0000000,0xfc003cf0,0x0,0x3c00,0x1e00,0x101c040,0x1e0000,0x0,0x0,0x1,
+      0xe0001e1f,0x83c0001e,0x7,0xe000fff0,0x3c0780,0x3c03f80,0x7fc0fc00,0x1e0000,0xfff80078,0xfe0007,0xe000003f,0x7fe0,0x1fffff,
+      0xf0000ffc,0xfc00,0x780f81f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3ffc0000,
+      0x7800001e,0x1ef0f078,0x3c1e03c0,0x780780,0x1fc0f000,0x1e07ffff,0x7ff00,0xf000,0xf0003c0,0xf00f007,0xc3b87c00,0x7c00001,0xfe000000,
+      0xf800003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0x800000f0,0xf80780,0x1e0003c,0x1e001,0xf0078007,0x80003c00,0xf000,0x78fc000,
+      0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,0x3c07800,0x7c003e00,0xf0000,0x3c000,0x3c003c0,0x1e007807,0x80003c00,0x7df0003c,0x780000,
+      0x1f00001e,0xf0,0x780,0x0,0x0,0x7800000,0xe7ce000,0x3,0xffff0000,0x0,0xfff,0xf8007fff,0xc0000000,0x1f0,0xffe000,0x1c0003,
+      0xc00fff00,0xe7ce0000,0xf800039,0xf38001cf,0x9c000000,0xe0007800,0x780e,0x701c00,0x0,0x0,0x0,0x0,0x1e0,0x7,0xf0078,0xf00e7ce,
+      0x1f0,0x7f800,0x0,0x780000,0xf0180000,0xf000000e,0x1c0001f,0xe000003c,0xf007,0xe0000000,0x6380000,0xc03fe780,0x3e07c03,0xfffffe00,
+      0x303,0xffc06000,0x0,0x1ffff,0xfe003ffe,0x1fff0,0x0,0x3c003c0,0x1ffe1c00,0x3f00000,0x7,0xffc0001f,0xfc0003e0,0x7c000001,0xfc00000f,
+      0xe000007f,0x1e000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,
+      0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,
+      0x783c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07800,
+      0xf0003,0xc078001e,0x3c000f0,0x1e000780,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,
+      0x7800780,0x3c003c00,0xf,0x7f003c,0x3c01e0,0x1e00f00,0xf007800,0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe070001f,0xf8000007,
+      0xf0007cf8,0x7800000,0x3c00,0x1e00,0x1c000,0x1e0000,0x0,0x0,0x1,0xe0001e1f,0x83c0001e,0xf,0xc000fff8,0x780780,0x2000f80,0x7f803e00,
+      0x3e0003,0xfffe007c,0x1fe0000,0x0,0x3ff00,0x0,0x1ff,0x8001f000,0x780f00f0,0x1f00f003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,
+      0xfe03c00f,0xf81fffff,0x80007800,0x780,0x3ffe0000,0x7800001e,0xee0f078,0x3c1e03c0,0x7807ff,0xff80f000,0x1e07fffe,0x3ffe0,
+      0xf000,0xf0003c0,0xf00f003,0xc7bc7800,0xfc00000,0xfc000001,0xf000003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xe000f80f,0x800001e0,
+      0xf80f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x79f8000,0x1e0000f,0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003e00,
+      0xf0000,0x3c000,0x3c003c0,0x1e007807,0x81e03c00,0x7df0003e,0xf80000,0x3e00003e,0xf0,0x7c0,0xfc000,0x80000000,0x7800000,0x1e7cf000,
+      0x3,0xffff0000,0x0,0x18,0xc0,0x0,0xf80,0x7ffc00,0x380003,0xc00fff01,0xe7cf0000,0x1f000079,0xf3c003cf,0x9e000000,0xe0007000,
+      0x380e,0xe01c00,0x0,0x0,0x0,0x0,0x1e0,0x3,0x800f0078,0xf01e7cf,0x3e0,0x3f000,0x0,0x780000,0xf018001f,0xfff8001e,0x1e0000f,
+      0xc000003c,0xf003,0xe0000000,0x6380000,0xc00fc780,0x7c0f803,0xfffffe00,0x303,0xfe006000,0x0,0x1ffff,0xfe003ffe,0x1ffe0,0x0,
+      0x3c003c0,0xffe1c00,0x3f00000,0x7,0xffc00007,0xf00001f0,0x3e00001f,0xfc0000ff,0xe00007ff,0x3e000,0x3e01e00,0x1f00f000,0xf8078007,
+      0xc03c003e,0x1e001e0,0xf001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,
+      0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000fc0,
+      0x1e0007,0x80f1f000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c0f800,0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,
+      0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1e,0xf7803c,0x3c01e0,0x1e00f00,
+      0xf007800,0x7803e00f,0x801e000f,0x80f803e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe0f0000f,0xff00001f,0x8000f87c,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,
+      0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x1f,0x800000fe,0xf00780,0x7c0,0x7f001e00,0x3c0007,0xe03f003f,0x3fe0000,0x0,0x3fc00,0x0,
+      0x7f,0x8001e000,0x781f00f0,0x1e00f003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3f9f0000,0x7800001e,
+      0xfe0f078,0x3c1e03c0,0x7807ff,0xff00f000,0x1e07fff8,0xfff8,0xf000,0xf0003c0,0xf81f003,0xc7bc7800,0xfe00000,0x78000003,0xe000003c,
+      0x1e0,0x1e0,0x0,0x0,0x0,0x1fffc01,0xe000780f,0x1e0,0x780f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7bf0000,0x1e0000f,
+      0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xf8000,0x3c000,0x3c003c0,0x1f00f807,0x81f03c00,0x3fe0001e,0xf00000,0x7c00007c,
+      0xf0,0x3e0,0x3ff801,0x80000000,0x7800000,0x3cfcf800,0x3,0xffff0000,0x0,0x18,0xc0,0x0,0x7c00,0x1fff00,0x700003,0xc00f0003,
+      0xcfcf8000,0x3e0000f3,0xf3e0079f,0x9f000000,0xf000,0x1000,0x0,0x0,0x0,0x0,0x0,0x1f0,0x1,0xc00f0078,0xf03cfcf,0x800007c0,0x1e000,
+      0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x8000003c,0xf001,0xf0000000,0x6380000,0xc0000000,0xf81f003,0xfffffe00,0x303,
+      0x87006000,0x0,0x1ffff,0xfe003ffe,0x7f00,0x0,0x3c003c0,0x3fe1c00,0x3f00000,0x7,0xffc00000,0xf8,0x1f0001ff,0xf0000fff,0x80007ffc,
+      0xfc000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf001e07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,
+      0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3fc001e,0x1e03c0f0,0x3c0780,
+      0x1e03c00,0xf01e000,0x78000780,0x1e0007,0x80f0fc00,0x3fff80,0x1fffc00,0xfffe000,0x7fff0003,0xfff8001f,0xffc0001e,0x3c0f000,
+      0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,0x3c00000,0x1e000000,0xf0001e00,0xf803c00,0x3c078001,0xe03c000f,0x1e00078,
+      0xf0003c0,0x78001e07,0xfffffe1e,0x1e7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801e00f,0x1e0007,0x807803c0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00007,
+      0xffc0007e,0xf03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x3f,0x3e,0xf00780,0x3c0,0x7e001e00,
+      0x7c000f,0x800f001f,0xffde0000,0x0,0x3e000,0x0,0xf,0x8003e000,0x781e0070,0x1e00f003,0xc001f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,
+      0xf81e0007,0x80007800,0x780,0x3f1f0000,0x7800001e,0x7c0f078,0x1e1e03c0,0x7807ff,0xfc00f000,0x1e07fffe,0xffc,0xf000,0xf0003c0,
+      0x781e003,0xc71c7800,0x1ff00000,0x78000003,0xe000003c,0x1e0,0x1e0,0x0,0x0,0x0,0xffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,
+      0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7f000,0x3c000,
+      0x3c003c0,0xf00f007,0xc1f07c00,0x1fc0001f,0x1f00000,0xfc000ff8,0xf0,0x1ff,0xfffe07,0x80000000,0x7800000,0x7ffcfc00,0x0,0xf000000,
+      0x0,0x18,0xc0,0x0,0x3e000,0x1ff80,0xe00003,0xc00f0007,0xffcfc000,0x3e0001ff,0xf3f00fff,0x9f800000,0x6000,0x0,0x0,0x7c000,
+      0x0,0x0,0x0,0xfe,0x0,0xe00f007f,0xff07ffcf,0xc0000fc0,0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x80000000,0xf800,
+      0xf0000000,0x6380000,0xc0000000,0x1f03c000,0x1e00,0x303,0x83806000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xfe1c00,0x3f00000,0x0,
+      0x0,0x3c,0xf801fff,0xfff8,0x7ffc0,0x1f8000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf003c07,0x8003c000,0x78000,
+      0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
+      0x78000f00,0x1f8001e,0x1e03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e000f,0x80f0ff00,0x1ffff80,0xffffc00,0x7fffe003,
+      0xffff001f,0xfff800ff,0xffc007ff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,
+      0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x3c7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801f01f,
+      0x1e0007,0x807c07c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00000,0xfff003f0,0x1f00f03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x7ff80000,0x3,
+      0xc0001e0f,0x3c0001e,0x7e,0x1f,0x1e00780,0x3e0,0x7e000f00,0x78000f,0x7800f,0xff9e0000,0x0,0x3fc00,0x0,0x7f,0x8003c000,0x781e0070,
+      0x3e00f803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3e0f8000,0x7800001e,0x7c0f078,0x1e1e03c0,
+      0x7807ff,0xf000f000,0x1e07807f,0xfe,0xf000,0xf0003c0,0x781e003,0xc71c7800,0x3ef00000,0x78000007,0xc000003c,0x1e0,0x1e0,0x0,
+      0x0,0x0,0x1ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,
+      0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7ff80,0x3c000,0x3c003c0,0xf00f003,0xc1f07800,0x1fc0000f,0x1e00000,0xf8000ff0,0xf0,
+      0xff,0xffffff,0x80000000,0x3fffc000,0xfff9fe00,0x0,0xf000000,0x0,0x18,0xc0,0x0,0x1f0000,0x1fc0,0x1c00003,0xc00f000f,0xff9fe000,
+      0x7c0003ff,0xe7f81fff,0x3fc00000,0x0,0x0,0x0,0xfe000,0x1ffffc0f,0xfffffc00,0x0,0xff,0xf0000000,0x700f007f,0xff0fff9f,0xe0000f80,
+      0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00fff,0xffc00000,0xf800,0xf0000000,0x6380000,0xc0ffff80,0x3e078000,0x1e00,0x7ff80303,
+      0x83c06000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,0x0,0x7f,0xff00001e,0x7c1fff0,0xfff80,0x7ffc00,0x3f0000,0x7c01f00,
+      0x3e00f801,0xf007c00f,0x803e007c,0x1f003e0,0xf803c07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+      0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f8001e,0x3c03c0f0,0x3c0780,0x1e03c00,0xf01e000,
+      0x78000780,0x1e001f,0xf07f80,0x3ffff80,0x1ffffc00,0xffffe007,0xffff003f,0xfff801ff,0xffc03fff,0xffc0f000,0x1fffff,0xc0fffffe,
+      0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,
+      0xfffffe1e,0x787803c,0x3c01e0,0x1e00f00,0xf007800,0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x3ff80fc0,0x7fc1e01f,
+      0x7800000,0x3c00,0x1e00,0x0,0x7fffff80,0x0,0x7ff80000,0x7,0x80001e00,0x3c0001e,0xfc,0xf,0x1e00780,0x1e0,0x7c000f00,0x78000f,
+      0x78007,0xff1e0000,0x0,0x3ff00,0x0,0x1ff,0x8003c000,0x781e0070,0x3c007803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,
+      0x80007800,0x780,0x3c07c000,0x7800001e,0x7c0f078,0xf1e03c0,0x780780,0xf000,0x1e07801f,0x3e,0xf000,0xf0003c0,0x781e003,0xcf1c7800,
+      0x3cf80000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,0x0,0x0,0x3ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,
+      0x80003c00,0xf000,0x7ff8000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3fff0,0x3c000,0x3c003c0,0xf81f003,
+      0xc3b87800,0xf80000f,0x1e00001,0xf0000ff0,0xf0,0xff,0xf03fff,0x80000000,0x3fff8001,0xfff1ff00,0x0,0xf000000,0x0,0x18,0xc0,
+      0x0,0x380000,0x7c0,0x3c00003,0xc00f001f,0xff1ff000,0xf80007ff,0xc7fc3ffe,0x3fe00000,0x0,0x0,0x0,0x1ff000,0x7ffffe1f,0xffffff00,
+      0x0,0x7f,0xfe000000,0x780f007f,0xff1fff1f,0xf0001f00,0x1e000,0x0,0x780001,0xe0180000,0xf000001c,0xe00fff,0xffc00000,0x7c00,
+      0xf0000000,0x31c0001,0x80ffff80,0x3e078000,0x1e00,0x7ff80183,0x81c0c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,
+      0x0,0x7f,0xff00001e,0x7c7ff03,0xc03ff8fe,0x1ffc0f0,0x7e0000,0x7800f00,0x3c007801,0xe003c00f,0x1e0078,0xf003c0,0x7803c07,0x8003c000,
+      0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,
+      0xf0001e0,0x78000f00,0x3fc001e,0x7803c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e007f,0xf03fe0,0x7ffff80,0x3ffffc01,
+      0xffffe00f,0xffff007f,0xfff803ff,0xffc07fff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,
+      0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x707803c,0x3c01e0,0x1e00f00,0xf007800,
+      0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x30f81f00,0xffe1e00f,0x87800000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,
+      0x7,0x80001e00,0x3c0001e,0x1f8,0x7,0x83c00780,0x1e0,0x7c000f00,0xf8001e,0x3c001,0xfc1e0000,0x0,0x7fe0,0x0,0xffc,0x3c000,0x781e0070,
+      0x3ffff803,0xc000783c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x380f078,0xf1e03c0,
+      0x780780,0xf000,0x1e07800f,0x8000001e,0xf000,0xf0003c0,0x3c3c003,0xcf1e7800,0x7c780000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,
+      0x0,0x0,0x7f003c01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7f7c000,0x1e0000f,0x3c0f01e,
+      0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfff8,0x3c000,0x3c003c0,0x781e003,0xc3b87800,0x1fc00007,0x83e00003,0xe0000ff8,0xf0,
+      0x1ff,0xc007fe,0x0,0x7fff8001,0xffe3ff00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x3c0,0x7800003,0xc00f001f,0xfe3ff000,0xf80007ff,
+      0x8ffc3ffc,0x7fe00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x1f,0xff000000,0x3c0f007f,0xff1ffe3f,0xf0003e00,0x1e000,0x0,0x780001,
+      0xe0180000,0xf000001e,0x1e00fff,0xffc00000,0x3f00,0xf0000000,0x31c0001,0x80ffff80,0x1f03c000,0x1e00,0x7ff80183,0x81c0c000,
+      0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x7f,0xff00003c,0xf87f007,0xc03f83ff,0x81fc01f0,0x7c0000,0x7ffff00,0x3ffff801,
+      0xffffc00f,0xfffe007f,0xfff003ff,0xff807fff,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+      0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf003c0f0,0x3c0780,0x1e03c00,0xf01e000,
+      0x78000780,0x1ffffe,0xf00ff0,0xfe00780,0x7f003c03,0xf801e01f,0xc00f00fe,0x7807f0,0x3c0ffff,0xffc0f000,0x1fffff,0xc0fffffe,
+      0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
+      0x1e,0xf07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783e,0x1e0007,0x801e0f80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x307c0801,0xe1f1e00f,0x87000000,
+      0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,0xf,0x1e00,0x3c0001e,0x3f0,0x7,0x83fffffc,0x1e0,0x7c000f00,0xf0001e,0x3c000,0x3e0000,
+      0x0,0x1ffc,0x1fffff,0xf0007ff0,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x3c000,0x781e0007,0x80007800,
+      0x780,0x3c03e000,0x7800001e,0xf078,0x79e03c0,0x780780,0xf000,0x1e078007,0x8000000f,0xf000,0xf0003c0,0x3c3c001,0xee0ef000,
+      0xf87c0000,0x7800001f,0x3c,0x78,0x1e0,0x0,0x0,0x0,0x7c003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
+      0xf000,0x7e3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x1ffc,0x3c000,0x3c003c0,0x781e003,0xe3b8f800,
+      0x1fc00007,0x83c00007,0xc00000fc,0xf0,0x3e0,0x8001f8,0x0,0x7800000,0xffc7fe00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,
+      0xf000003,0xc00f000f,0xfc7fe001,0xf00003ff,0x1ff81ff8,0xffc00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x3,0xff800000,0x1e0f0078,
+      0xffc7f,0xe0007c00,0x1e000,0x0,0x780001,0xe0180000,0xf000000e,0x1c00007,0x80000000,0x1f81,0xe0000000,0x38e0003,0x80000000,
+      0xf81f000,0x1e00,0x7ff801c3,0x80e1c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf8,0x1f070007,0xc03803ff,0xc1c001f0,
+      0xf80000,0xfffff00,0x7ffff803,0xffffc01f,0xfffe00ff,0xfff007ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,
+      0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f00f,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,0xf003c0f0,
+      0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1ffffc,0xf003f8,0xf800780,0x7c003c03,0xe001e01f,0xf00f8,0x7807c0,0x3c0fc1e,0xf000,
+      0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,
+      0xf0003c0,0x78001e00,0x1e,0x1e07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783c,0x1e0007,0x801e0f00,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xffff8000,0x303c0001,
+      0xc071e007,0xcf000000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0xf,0xf00,0x780001e,0x7e0,0x7,0x83fffffc,0x1e0,0x7c000f00,0x1f0001e,
+      0x3c000,0x3c0000,0x0,0x3ff,0x801fffff,0xf003ff80,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,
+      0x80007800,0x780,0x3c01f000,0x7800001e,0xf078,0x79e03c0,0xf00780,0xf000,0x3e078007,0xc000000f,0xf000,0xf0003c0,0x3c3c001,
+      0xee0ef000,0xf03e0000,0x7800003e,0x3c,0x78,0x1e0,0x0,0x0,0x0,0xf8003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,
+      0x80003c00,0xf000,0x7c3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfc,0x3c000,0x3c003c0,0x3c3e001,0xe7b8f000,
+      0x3fe00007,0xc7c0000f,0xc000003e,0xf0,0x7c0,0x0,0x0,0x7c00000,0x7fcffc00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,0x1e000003,
+      0xc00f0007,0xfcffc003,0xe00001ff,0x3ff00ff9,0xff800000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x1f800000,0xf0f0078,0x7fcff,
+      0xc000fc00,0x1e000,0x0,0x780001,0xe0180000,0xf000000f,0x87c00007,0x80000000,0xfe3,0xe0000000,0x18780c3,0x0,0x7c0f800,0x1e00,
+      0xc3,0x80e18000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x1f0,0x3e00000f,0xc0000303,0xe00003f0,0xf00000,0xfffff80,
+      0x7ffffc03,0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,
+      0x3c000001,0xe0001e00,0x780f00f,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1f0f801f,0xe00780f0,0x3c0780,0x1e03c00,
+      0xf01e000,0x78000780,0x1ffff8,0xf000f8,0x1f000780,0xf8003c07,0xc001e03e,0xf01f0,0x780f80,0x3c1f01e,0xf000,0x1e0000,0xf00000,
+      0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
+      0x1e,0x3c07803c,0x3c01e0,0x1e00f00,0xf007800,0x78007c7c,0x1e0007,0x801f1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c00000,0x303c0003,0x8039e003,0xef000000,
+      0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0xfc0,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,
+      0x0,0x7f,0xe01fffff,0xf00ffc00,0x3c000,0x781f00f0,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,0x80007800,
+      0x780,0x3c01f000,0x7800001e,0xf078,0x7de01e0,0xf00780,0x7800,0x3c078003,0xc000000f,0xf000,0xf0003c0,0x3e7c001,0xee0ef001,
+      0xf01e0000,0x7800003e,0x3c,0x3c,0x1e0,0x0,0x0,0x0,0xf0003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
+      0xf000,0x781f000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0x7df00003,
+      0xc780000f,0x8000003e,0xf0,0x780,0x0,0x0,0x3c00000,0x3fcff800,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x1f00fc,0x1e0,0x1e000001,
+      0xe00f0003,0xfcff8003,0xe00000ff,0x3fe007f9,0xff000000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x7c00000,0xf0f0078,0x3fcff,0x8000f800,
+      0x1e000,0x0,0x780001,0xe0180000,0xf000001f,0xffe00007,0x8000003c,0x7ff,0xc0000000,0x1c3ffc7,0x0,0x3e07c00,0x1e00,0xe3,0x80738000,
+      0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x3e0,0x7c00001d,0xc0000001,0xe0000770,0x1f00000,0xfffff80,0x7ffffc03,
+      0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
+      0xe0001e00,0x780f00f,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0x3e07c01f,0xc00780f0,0x3c0780,0x1e03c00,0xf01e000,
+      0x78000780,0x1fffc0,0xf0007c,0x1e000780,0xf0003c07,0x8001e03c,0xf01e0,0x780f00,0x3c1e01e,0xf000,0x1e0000,0xf00000,0x7800000,
+      0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1e,0x7807803c,
+      0x3c01e0,0x1e00f00,0xf007800,0x78003c78,0x1e0007,0x800f1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x83c00000,0x303c0003,0x8039e001,0xee000000,0x1e00,0x3c00,
+      0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0x1f80,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,0x0,0x1f,0xfc1fffff,
+      0xf07ff000,0x0,0x780f00f0,0x78003c03,0xc000781e,0x1e0,0xf803c0,0x1e00,0x1e000,0x781e0007,0x80007800,0x780,0x3c00f800,0x7800001e,
+      0xf078,0x3de01e0,0xf00780,0x7800,0x3c078003,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfe0ff003,0xe01f0000,0x7800007c,0x3c,0x3c,
+      0x1e0,0x0,0x0,0x0,0xf0007c01,0xe000f80f,0x800001e0,0xf80f00,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x780f800,0x1e0000f,
+      0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003c00,0x1e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0xf8f80003,0xe780001f,0x1e,
+      0xf0,0x780,0x0,0x0,0x3c00000,0x1ffff000,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x3bc1de,0x1e0,0xf000001,0xe00f0001,0xffff0007,0xc000007f,
+      0xffc003ff,0xfe000000,0x0,0x0,0x0,0xfe000,0x0,0x0,0x0,0x0,0x3c00000,0x1e0f0078,0x1ffff,0x1f000,0x1e000,0x0,0x780000,0xf0180000,
+      0xf000001f,0xfff00007,0x8000003c,0x1ff,0x80000000,0xe0ff0e,0x0,0x1f03e00,0x1e00,0x70,0x70000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,
+      0xe1c00,0x0,0x0,0x0,0x7c0,0xf8000019,0xc0000000,0xe0000670,0x1e00000,0xf000780,0x78003c03,0xc001e01e,0xf00f0,0x780780,0x3c0f807,
+      0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf80f007,0xbc03c001,0xe01e000f,
+      0xf00078,0x78003c0,0x3c001e00,0x7c03e00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,
+      0xf0007c07,0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0xf800,0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,
+      0xf0001e00,0x7803c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1f8001f,0xf00f803c,0x3c01e0,0x1e00f00,0xf007800,
+      0x78003e78,0x1e000f,0x800f9e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x3c00000,0x303c0003,0x8039f001,0xfe000000,0x1e00,0x3c00,0x0,0x1e0000,0x0,0x0,0x3c,0xf00,
+      0x780001e,0x3f00,0x7,0x80000780,0x3e0,0x3e000f00,0x3c0001e,0x3c000,0x7c0000,0x0,0x3,0xfe000000,0xff8000,0x0,0x3c0f81f0,0xf0001e03,
+      0xc000780f,0x1e0,0xf003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x780,0x3c007c00,0x7800001e,0xf078,0x3de01e0,0xf00780,0x7800,
+      0x3c078001,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfc07f003,0xe00f0000,0x78000078,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
+      0xf000f007,0x800000f0,0xf80780,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
+      0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78001,0xe71df000,0xf8f80001,0xef80003e,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,
+      0xfffe000,0x0,0x3e000000,0x0,0x18,0x7fff,0xc0000000,0x60c306,0x1e0,0x7800001,0xe00f0000,0xfffe0007,0x8000003f,0xff8001ff,
+      0xfc000000,0x0,0x0,0x0,0x7c000,0x0,0x0,0x0,0x0,0x3c00000,0x3c0f0078,0xfffe,0x3e000,0x1e000,0x0,0x780000,0xf0180000,0xf000003c,
+      0xfcf80007,0x8000003c,0x7f,0x0,0x70001c,0x0,0xf81f00,0x0,0x38,0xe0000,0x0,0x0,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf81,
+      0xf0000039,0xc0000000,0xe0000e70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,0x8000f000,0x78000,
+      0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f007,0xbc03c001,0xe01e000f,0xf00078,0x78003c0,
+      0x3c001e00,0xf801f00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,0x8003e03c,
+      0x1f01e0,0xf80f00,0x7c1e01e,0x7800,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,
+      0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xe00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef8,0x1f000f,
+      0x7be00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0xf,0x3c00000,0x307c0003,0x8038f000,0xfc000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e00003c,0x780,0xf00001e,
+      0x7e00,0xf,0x80000780,0x3c0,0x3e001e00,0x3c0001f,0x7c000,0x780007,0xe000003f,0x0,0xfe000000,0xfe0000,0x0,0x3c07c3f0,0xf0001e03,
+      0xc000f80f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x4000f80,0x3c003c00,0x7800001e,0xf078,0x1fe01f0,0x1f00780,
+      0x7c00,0x7c078001,0xf000001f,0xf000,0xf0003c0,0x1e78001,0xfc07f007,0xc00f8000,0x780000f8,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
+      0xf000f007,0xc00000f0,0xf80780,0x3c,0x1f003,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
+      0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78000,0xfe0fe001,0xf07c0001,0xef00007c,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,
+      0x7cfc000,0xfc00000,0x3c00000f,0xc3f00000,0x18,0x7fff,0xc0000000,0x406303,0x3e0,0x3c00001,0xf00f0000,0x7cfc000f,0x8000001f,
+      0x3f0000f9,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x780700f8,0x7cfc,0x7c000,0x1e000,0x0,0x780000,0xf8180000,
+      0xf0000070,0x3c0007,0x8000003c,0x3f,0x80000000,0x3c0078,0x0,0x780f00,0x0,0x1e,0x3c0000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,0xe1c00,
+      0x0,0x0,0x0,0xf01,0xe0000071,0xc0000000,0xe0001c70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
+      0x8000f800,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00f003,0xfc03e003,0xe01f001f,
+      0xf800f8,0x7c007c0,0x3e003e01,0xf000f80f,0xf00f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,
+      0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0x7c00,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,
+      0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xc00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef0,
+      0x1f000f,0x7bc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x780000,0xf,0x3800040,0x30780003,0x8038f800,0x78000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
+      0x780,0x1f00001e,0xfc00,0x20001f,0x780,0x80007c0,0x1f001e00,0x7c0000f,0x78000,0xf80007,0xe000003f,0x0,0x1e000000,0xf00000,
+      0x3c000,0x3c03fff0,0xf0001e03,0xc001f007,0x800101e0,0x7e003c0,0x1e00,0x7800,0x781e0007,0x80007800,0x6000f00,0x3c003e00,0x7800001e,
+      0xf078,0x1fe00f0,0x1e00780,0x3c00,0x78078000,0xf020001e,0xf000,0x7800780,0xff0001,0xfc07f00f,0x8007c000,0x780001f0,0x3c,0xf,
+      0x1e0,0x0,0x0,0x0,0xf800fc01,0xf801f007,0xc00100f8,0x1f807c0,0x40003c,0xf807,0xf0078007,0x80003c00,0xf000,0x7803e00,0x1f0000f,
+      0x3c0f01e,0x1e01f0,0x3e007e0,0x7c07c00,0xfc003c00,0x1e,0x3e000,0x3e007c0,0x1ff8000,0xfe0fe003,0xe03e0001,0xff0000fc,0x1e,
+      0xf0,0x780,0x0,0x0,0x1f00080,0x3cf8000,0xfc00000,0x3c00001f,0x83f00000,0x18,0xc0,0x0,0xc06203,0x40003c0,0x1c00000,0xf80f0000,
+      0x3cf8001f,0xf,0x3e000079,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x700780fc,0x3cf8,0xfc000,0x1e000,0x0,0x780000,
+      0x7c180000,0xf0000020,0x100007,0x8000003c,0xf,0x80000000,0x1f01f0,0x0,0x380700,0x0,0xf,0x80f80000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,
+      0xe1c00,0x0,0x0,0x0,0xe01,0xc0000071,0xc0000001,0xc0001c70,0x1e00040,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
+      0x80007800,0x10078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00f003,0xfc01e003,0xc00f001e,
+      0x7800f0,0x3c00780,0x1e003c00,0xe000700f,0x800f0078,0x7803c0,0x3c01e00,0x1e00f000,0xf0000780,0x1e0000,0xf0003c,0x1f001f80,
+      0xf800fc07,0xc007e03e,0x3f01f0,0x1f80f80,0xfc1e01f,0x7c00,0x100f8000,0x807c0004,0x3e00020,0x1f000100,0x780000,0x3c00000,0x1e000000,
+      0xf0000f80,0x1f003c00,0x3c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,0x1f8000f,0x801f003e,0x7c01f0,0x3e00f80,0x1f007c00,
+      0xf8001ff0,0x1f801f,0x7fc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0xf,0x7800078,0x31f80001,0xc070fc00,0xfc000000,0x1e00,0x7c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
+      0x7c0,0x1f00001e,0x1f000,0x38003f,0x780,0xe000f80,0x1f803e00,0x780000f,0x800f8000,0x1f00007,0xe000003f,0x0,0x2000000,0x800000,
+      0x3c000,0x3e01ff71,0xf0001f03,0xc007f007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x781e0007,0x80007800,0x7801f00,0x3c001f00,0x7800001e,
+      0xf078,0xfe00f8,0x3e00780,0x3e00,0xf8078000,0xf838003e,0xf000,0x7c00f80,0xff0000,0xfc07e00f,0x8003c000,0x780001e0,0x3c,0xf,
+      0x1e0,0x0,0x0,0x0,0xf801fc01,0xfc03e003,0xe003007c,0x3f803e0,0x1c0003c,0xfc0f,0xf0078007,0x80003c00,0xf000,0x7801f00,0xf8000f,
+      0x3c0f01e,0x1e00f8,0x7c007f0,0xf803e01,0xfc003c00,0x8003e,0x1f000,0x1e00fc0,0xff0000,0xfe0fe007,0xc01f0000,0xfe0000f8,0x1e,
+      0xf0,0x780,0x0,0x0,0xf80180,0x1cf0000,0x1f800000,0x3c00001f,0x83e00000,0x18,0xc0,0x0,0xc06203,0x70007c0,0xe00000,0x7e0f0000,
+      0x1cf0001e,0x7,0x3c000039,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100,0x7c00000,0xe00780fc,0x2001cf0,0xf8000,0x1e000,0x0,
+      0x780000,0x7e182000,0xf0000000,0x7,0x8000003c,0x7,0xc0000000,0x7ffc0,0x0,0x180300,0x0,0x3,0xffe00000,0x0,0x0,0x0,0x0,0x0,
+      0x3f00fc0,0xe1c00,0x0,0x0,0x0,0xc01,0x800000e1,0xc0000003,0xc0003870,0x1f001c0,0x3e0003e1,0xf0001f0f,0x8000f87c,0x7c3e0,0x3e1f00,
+      0x1f1e007,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e03,0xfc00f001,0xfc01f007,
+      0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x4000201f,0xc01f007c,0xf803e0,0x7c01f00,0x3e00f801,0xf0000780,0x1e0000,0xf0007c,
+      0x1f003f80,0xf801fc07,0xc00fe03e,0x7f01f0,0x3f80f80,0x1fc1f03f,0x803e00,0x3007c003,0x803e001c,0x1f000e0,0xf800700,0x780000,
+      0x3c00000,0x1e000000,0xf00007c0,0x3e003c00,0x3c01f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e001e,0xfc00f0,
+      0x7e00780,0x3f003c01,0xf8000fe0,0x1fc03e,0x3f800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,0xfff00001,0xe0f07f03,0xfe000000,0xf00,0x7800,0x0,
+      0x1e0000,0xfc0000,0x0,0x7e0000f0,0x3f0,0x7e000fff,0xfc03ffff,0xf83f00fe,0x780,0xfc03f80,0xfc0fc00,0xf800007,0xe03f0018,0x7e00007,
+      0xe000003f,0x0,0x0,0x0,0x3c000,0x1e007c71,0xe0000f03,0xffffe003,0xf01f01ff,0xff8003ff,0xffe01e00,0x3f01,0xf81e0007,0x803ffff0,
+      0x7e03f00,0x3c000f00,0x7ffffe1e,0xf078,0xfe007e,0xfc00780,0x1f83,0xf0078000,0x783f00fe,0xf000,0x3f03f00,0xff0000,0xfc07e01f,
+      0x3e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7e07fc01,0xfe07e001,0xf80f007e,0x7f801f8,0xfc0003c,0x7ffe,0xf0078007,
+      0x807ffffe,0xf000,0x7801f00,0xfff00f,0x3c0f01e,0x1e00fc,0xfc007f8,0x1f803f03,0xfc003c00,0xf80fc,0x1fff0,0x1f83fc0,0xff0000,
+      0xfc07e007,0xc01f0000,0xfe0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfe0780,0xfe0000,0x1f000000,0x3c00001f,0x7c00e03,0x81c00018,
+      0xc0,0x0,0x406203,0x7e01fc0,0x700000,0x7fffff80,0xfe0003f,0xffffc003,0xf800001f,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f0,
+      0x1f800001,0xc007c1fe,0x6000fe0,0x1ffffe,0x1e000,0x0,0x780000,0x3f98e03f,0xffff8000,0x7,0x8000003c,0x7,0xc0000000,0xfe00,
+      0x0,0x80100,0x0,0x0,0x7f000000,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3f83fe8,0xe1c00,0x0,0x0,0x0,0x801,0xc1,0xc0000007,0x80003070,
+      0xfc0fc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc03f01,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,
+      0xffff001f,0xfff800ff,0xffc01fff,0xf800f001,0xfc00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,0x1f,0xf07e003f,0x3f001f8,
+      0x1f800fc0,0xfc007e07,0xe0000780,0x1e0000,0xf301f8,0xfc0ff80,0x7e07fc03,0xf03fe01f,0x81ff00fc,0xff807e0,0x7fc0f87f,0x81801f80,
+      0xf003f01f,0x801f80fc,0xfc07e0,0x7e03f00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff807e0,0x7e003c00,0x3c01f81f,0x800fc0fc,0x7e07e0,
+      0x3f03f00,0x1f81f800,0x1f8000f,0xe07e001f,0x83fc00fc,0x1fe007e0,0xff003f07,0xf8000fe0,0x1fe07e,0x3f800,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,
+      0xffe00000,0xffe03fff,0xdf000000,0xf00,0x7800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0x1ff,0xfc000fff,0xfc03ffff,0xf83ffffc,0x780,
+      0xfffff00,0x7fff800,0xf000007,0xffff001f,0xffe00007,0xe000003f,0x0,0x0,0x0,0x3c000,0x1e000001,0xe0000f03,0xffffc001,0xffff01ff,
+      0xff0003ff,0xffe01e00,0x1fff,0xf81e0007,0x803ffff0,0x7fffe00,0x3c000f80,0x7ffffe1e,0xf078,0xfe003f,0xff800780,0xfff,0xf0078000,
+      0x7c3ffffc,0xf000,0x3ffff00,0xff0000,0xf803e01e,0x1e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7fffbc01,0xffffc000,
+      0xffff003f,0xfff800ff,0xffc0003c,0x3ffe,0xf0078007,0x807ffffe,0xf000,0x7800f80,0x7ff00f,0x3c0f01e,0x1e007f,0xff8007ff,0xff001fff,
+      0xbc003c00,0xffffc,0x1fff0,0x1fffbc0,0xff0000,0x7c07c00f,0x800f8000,0x7e0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7fff80,0x7c0000,
+      0x1f000000,0x3c00001e,0x7c00f07,0xc1e00018,0xc0,0x0,0x60e303,0x7ffff80,0x380000,0x3fffff80,0x7c0003f,0xffffc001,0xf000000f,
+      0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff800003,0x8003ffff,0xfe0007c0,0x1ffffe,0x1e000,0x0,0x780000,0x1fffe03f,0xffff8000,
+      0x7,0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3fffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x1c1,
+      0xc000000f,0x7070,0x7fffc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,
+      0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000f001,0xfc007fff,0x3fff8,0x1fffc0,0xfffe00,0x7fff000,0x3b,0xfffc003f,
+      0xfff001ff,0xff800fff,0xfc007fff,0xe0000780,0x1e0000,0xf3fff8,0xffff780,0x7fffbc03,0xfffde01f,0xffef00ff,0xff7807ff,0xfbc0ffff,
+      0xff800fff,0xf001ffff,0x800ffffc,0x7fffe0,0x3ffff00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff803ff,0xfc003c00,0x3c00ffff,0x7fff8,
+      0x3fffc0,0x1fffe00,0xffff000,0x1f,0xfffc001f,0xffbc00ff,0xfde007ff,0xef003fff,0x780007e0,0x1ffffc,0x1f800,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x700003f,
+      0xffc00000,0x7fc01fff,0x9f800000,0xf80,0xf800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0xff,0xf8000fff,0xfc03ffff,0xf83ffff8,0x780,
+      0xffffe00,0x7fff000,0xf000003,0xfffe001f,0xffc00007,0xe000003f,0x0,0x0,0x0,0x3c000,0xf000003,0xe0000f83,0xffff0000,0xffff01ff,
+      0xfc0003ff,0xffe01e00,0xfff,0xf01e0007,0x803ffff0,0x7fffc00,0x3c0007c0,0x7ffffe1e,0xf078,0x7e003f,0xff000780,0x7ff,0xe0078000,
+      0x3c3ffff8,0xf000,0x1fffe00,0x7e0000,0xf803e03e,0x1f000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x3fff3c01,0xefff8000,
+      0x7ffe001f,0xff78007f,0xff80003c,0x1ffc,0xf0078007,0x807ffffe,0xf000,0x78007c0,0x3ff00f,0x3c0f01e,0x1e003f,0xff0007bf,0xfe000fff,
+      0xbc003c00,0xffff8,0xfff0,0xfff3c0,0x7e0000,0x7c07c01f,0x7c000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3fff80,0x380000,
+      0x3e000000,0x7c00003e,0x7801f07,0xc1e00018,0xc0,0x0,0x39c1ce,0x7ffff00,0x1c0000,0xfffff80,0x380003f,0xffffc000,0xe0000007,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff000007,0x1ffcf,0xfe000380,0x1ffffe,0x1e000,0x0,0x780000,0xfffe03f,0xffff8000,0x7,
+      0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x381,
+      0xc000001e,0xe070,0x7fff80,0x7c0001f3,0xe0000f9f,0x7cf8,0x3e7c0,0x1f3e00,0xfbe007,0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,
+      0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000f000,0xfc007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x79,0xfff8001f,
+      0xffe000ff,0xff0007ff,0xf8003fff,0xc0000780,0x1e0000,0xf3fff0,0x7ffe780,0x3fff3c01,0xfff9e00f,0xffcf007f,0xfe7803ff,0xf3c07ff3,
+      0xff8007ff,0xe000ffff,0x7fff8,0x3fffc0,0x1fffe00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff801ff,0xf8003c00,0x3c007ffe,0x3fff0,
+      0x1fff80,0xfffc00,0x7ffe000,0x1d,0xfff8000f,0xff3c007f,0xf9e003ff,0xcf001ffe,0x780007c0,0x1efff8,0x1f000,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0xf000003,
+      0xfe000000,0x1f000fff,0xfc00000,0x780,0xf000,0x0,0x0,0xf80000,0x0,0x7e0001e0,0x7f,0xf0000fff,0xfc03ffff,0xf81ffff0,0x780,
+      0x7fff800,0x1ffe000,0x1f000000,0xfff8001f,0xff000007,0xe000003e,0x0,0x0,0x0,0x3c000,0xf800003,0xc0000783,0xfff80000,0x3ffe01ff,
+      0xe00003ff,0xffe01e00,0x7ff,0xc01e0007,0x803ffff0,0x3fff800,0x3c0003c0,0x7ffffe1e,0xf078,0x7e000f,0xfe000780,0x3ff,0xc0078000,
+      0x3e1fffe0,0xf000,0x7ff800,0x7e0000,0xf803e07c,0xf800,0x780003ff,0xfffc003c,0x3,0xc00001e0,0x0,0x0,0x0,0xffe3c01,0xe7ff0000,
+      0x3ffc000f,0xfe78003f,0xfe00003c,0x7f0,0xf0078007,0x807ffffe,0xf000,0x78003e0,0xff00f,0x3c0f01e,0x1e001f,0xfe00079f,0xfc0007ff,
+      0x3c003c00,0x7ffe0,0x1ff0,0x7fe3c0,0x7e0000,0x7c07c03e,0x3e000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfff00,0x100000,
+      0x3e000000,0x7800003c,0xf800f07,0xc1e00018,0xc0,0x0,0x1f80fc,0x3fffc00,0xc0000,0x3ffff80,0x100003f,0xffffc000,0x40000002,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xfc000006,0xff87,0xfc000100,0x1ffffe,0x1e000,0x0,0x780000,0x3ffc03f,0xffff8000,0x7,
+      0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dff9f8,0xe1c00,0x0,0x0,0x0,0x0,0x3ff,
+      0xf800003c,0xfffe,0x1ffe00,0x780000f3,0xc000079e,0x3cf0,0x1e780,0xf3c00,0x7bc007,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,
+      0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,0xf000,0xfc001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x70,0xfff00007,
+      0xff80003f,0xfc0001ff,0xe0000fff,0x780,0x1e0000,0xf3ffe0,0x1ffc780,0xffe3c00,0x7ff1e003,0xff8f001f,0xfc7800ff,0xe3c03fe1,
+      0xff0003ff,0xc0007ffc,0x3ffe0,0x1fff00,0xfff800,0xfffffc07,0xffffe03f,0xffff01ff,0xfff800ff,0xf0003c00,0x3c003ffc,0x1ffe0,
+      0xfff00,0x7ff800,0x3ffc000,0x38,0xfff00007,0xfe3c003f,0xf1e001ff,0x8f000ffc,0x780007c0,0x1e7ff0,0x1f000,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
+      0x1fc,0x0,0x780,0xf000,0x0,0x0,0x1f80000,0x0,0x1e0,0x1f,0xc0000000,0x0,0x1ff80,0x0,0xffc000,0x7f8000,0x0,0x3fe00007,0xfc000000,
+      0x7e,0x0,0x0,0x0,0x0,0x7c00000,0x0,0x0,0xff00000,0x0,0x0,0xfe,0x0,0x0,0x3fc000,0x0,0x0,0x0,0x3,0xf8000000,0xff,0xc0000000,
+      0x1ff00,0x0,0x1fe000,0x0,0x0,0x0,0x0,0x3c,0x3,0xc00001e0,0x0,0x0,0x0,0x3f80000,0x1fc0000,0x7f00003,0xf8000007,0xf0000000,
+      0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x7,0xf8000787,0xf00001fc,0x3c000000,0x7f80,0x0,0x1f8000,0x0,0x0,0x0,0x7c000000,0x1e,
+      0xf0,0x780,0x0,0x0,0x3fc00,0x0,0x3c000000,0x7800003c,0xf000601,0xc00018,0xc0,0x0,0x0,0x3fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xf0000000,0x7e03,0xf0000000,0x0,0x0,0x0,0x0,0xfe0000,0x0,0x0,0x3c,0x2007,0x80000000,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c7e0f0,0xe1c00,0x0,0x3800000,0x0,0x0,0x3ff,0xf8000078,0xfffe,0x7f800,0x0,0x0,0x0,0x0,
+      0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,0x7f0000,0x70,0x3fc00001,0xfe00000f,0xf000007f,
+      0x800003fc,0x0,0x0,0xff00,0x7f0000,0x3f80000,0x1fc00000,0xfe000007,0xf000003f,0x80001f80,0xfc00007f,0xfe0,0x7f00,0x3f800,
+      0x1fc000,0x0,0x0,0x0,0x3f,0xc0000000,0xff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x78,0x3fc00001,0xf800000f,0xc000007e,0x3f0,0x7c0,
+      0x1e1fc0,0x1f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xe0000000,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,0x1e,0xf0,0x780,0x0,0x0,0x0,0x0,0x3c000000,0x78000078,0xf000000,0x18,0xc0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3c0f,0x80000000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0x1800000,0x0,0x0,0x3ff,0xf80000f0,0xfffe,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x780,0x1e0000,0x1e000,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
+      0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x1f80000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf8000000,
+      0x1f,0xf0,0xf80,0x0,0x0,0x0,0x0,0x78000000,0xf8000078,0x1e000000,0x8,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3fff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x3c00000,0xe1c00,0x0,0x1c00000,0x0,0x0,0x1,0xc00001e0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x1e0000,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x3c000,0x0,0x0,0x1f00000,
+      0x0,0x780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0xfe0100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0xf0007fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,
+      0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x1f,0x800000f0,0x1f80,0x0,0x0,0x0,0x0,
+      0x78000000,0xf0000070,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3ffe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,
+      0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0xf00,0x1e0000,0x3c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x7c000,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x7fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4003,0xe0000000,0x0,0x1f000,0x0,0x0,
+      0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x1,0xf0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0x70000001,0xf00000e0,
+      0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,
+      0x0,0x0,0x3c,0xff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,0x0,0x0,0x1,0xc00003ff,
+      0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00,0x1e0000,
+      0x7c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0xf0,0x78000,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf8,0x0,
+      0x0,0x0,0x0,0x1fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,
+      0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780f,0xc0000000,0x0,0x3e000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
+      0x0,0x0,0x0,0x0,0x3,0xe0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0xf0000103,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x21e00000,0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e00,0x1e0000,0xf8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,
+      0xf8,0xf8000,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x1fe00,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x7fff,0xc0000000,0x0,0x3ffe000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0xe0000000,0x7,0xfc0000f0,
+      0x3fe00,0x0,0x0,0x0,0x0,0x600001ff,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,
+      0x3fe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x7fe00,0x1e0000,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff,0x80000000,0x0,0x3ffc000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
+      0x0,0x0,0x0,0x0,0x7f,0xc0000000,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x0,0x0,0x1ff,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3fc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fc00,0x1e0000,0x1ff0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffe,0x0,0x0,0x3ff8000,0x0,0x0,0x0,
+      0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0x80000000,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x80000000,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f800,0x1e0000,0x1fe0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8,0x0,0x0,0x3fe0000,
+      0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7e,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x1e0000,0x1f80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+      0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
+
+    // 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};
+
+    //! Set/get output stream for CImg library messages.
+    inline std::FILE* output(std::FILE *file) {
+      static std::FILE *res = stderr;
+      if (file) res = file;
+      return res;
+    }
+
+    //! Display a warning message.
+    /**
+       \param format is a C-string describing the format of the message, as in <tt>std::printf()</tt>.
+    **/
+    inline void warn(const char *const format, ...) {
+      if (cimg::exception_mode()>=1) {
+        char message[16384] = { 0 };
+        std::va_list ap;
+        va_start(ap,format);
+        cimg_vsnprintf(message,sizeof(message),format,ap);
+        va_end(ap);
+#ifdef cimg_strict_warnings
+        throw CImgWarningException(message);
+#else
+        std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s",cimg::t_red,cimg::t_normal,message);
+#endif
+      }
+    }
+
+    // Execute an external system command.
+    /**
+       \note This function is similar to <tt>std::system()</tt>
+       and is here because using the <tt>std::</tt> version on
+       Windows may open undesired consoles.
+    **/
+    inline int system(const char *const command, const char *const module_name=0) {
+#if cimg_OS==2
+      PROCESS_INFORMATION pi;
+      STARTUPINFO si;
+      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);
+        return 0;
+      } else
+#endif
+        return std::system(command);
+      return module_name?0:1;
+    }
+
+    //! Return a reference to a temporary variable of type T.
+    template<typename T>
+    inline T& temporary(const T&) {
+      static T temp;
+      return temp;
+    }
+
+    //! Exchange values of variables \p a and \p b.
+    template<typename T>
+    inline void swap(T& a, T& b) { T t = a; a = b; b = t; }
+
+    //! Exchange values of variables (\p a1,\p a2) and (\p b1,\p b2).
+    template<typename T1, typename T2>
+    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) {
+      cimg::swap(a1,b1); cimg::swap(a2,b2);
+    }
+
+    //! Exchange values of variables (\p a1,\p a2,\p a3) and (\p b1,\p b2,\p b3).
+    template<typename T1, typename T2, typename T3>
+    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) {
+      cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3);
+    }
+
+    //! Exchange values of variables (\p a1,\p a2,...,\p a4) and (\p b1,\p b2,...,\p b4).
+    template<typename T1, typename T2, typename T3, typename T4>
+    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) {
+      cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4);
+    }
+
+    //! Exchange values of variables (\p a1,\p a2,...,\p a5) and (\p b1,\p b2,...,\p b5).
+    template<typename T1, typename T2, typename T3, typename T4, typename T5>
+    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) {
+      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5);
+    }
+
+    //! Exchange values of variables (\p a1,\p a2,...,\p a6) and (\p b1,\p b2,...,\p b6).
+    template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
+    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) {
+      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6);
+    }
+
+    //! Exchange values of variables (\p a1,\p a2,...,\p a7) and (\p b1,\p b2,...,\p b7).
+    template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
+    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
+                     T7& a7, T7& b7) {
+      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7);
+    }
+
+    //! Exchange values of variables (\p a1,\p a2,...,\p a8) and (\p b1,\p b2,...,\p b8).
+    template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
+    inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
+                     T7& a7, T7& b7, T8& a8, T8& b8) {
+      cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8);
+    }
+
+    //! Return the current endianness of the CPU.
+    /**
+       \return \c false for "Little Endian", \c true for "Big Endian".
+    **/
+    inline bool endianness() {
+      const int x = 1;
+      return ((unsigned char*)&x)[0]?false:true;
+    }
+
+    //! Invert endianness of a memory buffer.
+    template<typename T>
+    inline void invert_endianness(T* const buffer, const unsigned int size) {
+      if (size) switch (sizeof(T)) {
+      case 1 : break;
+      case 2 : { for (unsigned short *ptr = (unsigned short*)buffer+size; ptr>(unsigned short*)buffer; ) {
+        const unsigned short val = *(--ptr);
+        *ptr = (unsigned short)((val>>8)|((val<<8)));
+      }
+      } break;
+      case 4 : { for (unsigned int *ptr = (unsigned int*)buffer+size; ptr>(unsigned int*)buffer; ) {
+        const unsigned int val = *(--ptr);
+        *ptr = (val>>24)|((val>>8)&0xff00)|((val<<8)&0xff0000)|(val<<24);
+      }
+      } break;
+      default : { for (T* ptr = buffer+size; ptr>buffer; ) {
+        unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T);
+        for (int i = 0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe));
+      }
+      }
+      }
+    }
+
+    //! Invert endianness of a single variable.
+    template<typename T>
+    inline T& invert_endianness(T& a) {
+      invert_endianness(&a,1);
+      return a;
+    }
+
+    //! Get the value of a system timer with a millisecond precision.
+    inline unsigned long time() {
+#if cimg_OS==1
+      struct timeval st_time;
+      gettimeofday(&st_time,0);
+      return (unsigned long)(st_time.tv_usec/1000 + st_time.tv_sec*1000);
+#elif cimg_OS==2
+      static SYSTEMTIME st_time;
+      GetSystemTime(&st_time);
+      return (unsigned long)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour)));
+#else
+      return 0;
+#endif
+    }
+
+    // Implement a tic/tac mechanism to display elapsed time of algorithms.
+    inline unsigned long tictac(const bool is_tic) {
+      static unsigned long t0 = 0;
+      const unsigned long t = cimg::time();
+      if (is_tic) return (t0 = t);
+      const unsigned long dt = t>=t0?(t - t0):cimg::type<unsigned long>::max();
+      const unsigned int
+        edays = (unsigned int)(dt/86400000.0),
+        ehours = (unsigned int)((dt - edays*86400000.0)/3600000.0),
+        emin = (unsigned int)((dt - edays*86400000.0 - ehours*3600000.0)/60000.0),
+        esec = (unsigned int)((dt - edays*86400000.0 - ehours*3600000.0 - emin*60000.0)/1000.0),
+        ems = (unsigned int)(dt - edays*86400000.0 - ehours*3600000.0 - emin*60000.0 - esec*1000.0);
+      if (!edays && !ehours && !emin && !esec)
+        std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u ms%s\n",cimg::t_red,ems,cimg::t_normal);
+      else {
+        if (!edays && !ehours && !emin)
+          std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u sec %u ms%s\n",cimg::t_red,esec,ems,cimg::t_normal);
+        else {
+          if (!edays && !ehours)
+            std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u min %u sec %u ms%s\n",cimg::t_red,emin,esec,ems,cimg::t_normal);
+          else{
+            if (!edays)
+              std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u hours %u min %u sec %u ms%s\n",cimg::t_red,ehours,emin,esec,ems,cimg::t_normal);
+            else{
+              std::fprintf(cimg::output(),"%s[CImg] Elapsed time : %u days %u hours %u min %u sec %u ms%s\n",cimg::t_red,edays,ehours,emin,esec,ems,cimg::t_normal);
+            }
+          }
+        }
+      }
+      return t;
+    }
+
+    inline unsigned long tic() {
+      return cimg::tictac(true);
+    }
+
+    inline unsigned long tac() {
+      return cimg::tictac(false);
+    }
+
+    //! Sleep for a certain numbers of milliseconds.
+    /**
+       This function frees the CPU ressources during the sleeping time.
+       It may be used to temporize your program properly, without wasting CPU time.
+    **/
+    inline void sleep(const unsigned int milliseconds) {
+#if cimg_OS==1
+      struct timespec tv;
+      tv.tv_sec = milliseconds/1000;
+      tv.tv_nsec = (milliseconds%1000)*1000000;
+      nanosleep(&tv,0);
+#elif cimg_OS==2
+      Sleep(milliseconds);
+#endif
+    }
+
+    inline unsigned int _sleep(const unsigned int milliseconds, unsigned long& timer) {
+      if (!timer) timer = cimg::time();
+      const unsigned long current_time = cimg::time();
+      if (current_time>=timer+milliseconds) { timer = current_time; return 0; }
+      const unsigned long time_diff = timer + milliseconds - current_time;
+      timer = current_time + time_diff;
+      cimg::sleep(time_diff);
+      return (unsigned int)time_diff;
+    }
+
+    //! Wait for a certain number of milliseconds since the last call.
+    /**
+       This function is equivalent to sleep() but the waiting time is computed with regard to the last call
+       of wait(). It may be used to temporize your program properly.
+    **/
+    inline unsigned int wait(const unsigned int milliseconds) {
+      static unsigned long timer = 0;
+      if (!timer) timer = cimg::time();
+      return _sleep(milliseconds,timer);
+    }
+
+    // Use a specific srand initialization to avoid multi-threads to have to the
+    // same series of random numbers (executed only once for a single program).
+    inline void srand() {
+      static bool first_time = true;
+      if (first_time) {
+        std::srand(cimg::time());
+        unsigned char *const rand_ptr = new unsigned char[1+std::rand()%2048];
+        std::srand((unsigned int)std::rand() + *(unsigned int*)(void*)rand_ptr);
+        delete[] rand_ptr;
+        first_time = false;
+      }
+    }
+
+    //! Return a left bitwise-rotated number.
+    template<typename T>
+    inline T rol(const T a, const unsigned int n=1) {
+      return n?(T)((a<<n)|(a>>((sizeof(T)<<3)-n))):a;
+    }
+
+    inline float rol(const float a, const unsigned int n=1) {
+      return (float)rol((int)a,n);
+    }
+
+    inline double rol(const double a, const unsigned int n=1) {
+      return (double)rol((long)a,n);
+    }
+
+    //! Return a right bitwise-rotated number.
+    template<typename T>
+    inline T ror(const T a, const unsigned int n=1) {
+      return n?(T)((a>>n)|(a<<((sizeof(T)<<3)-n))):a;
+    }
+
+    inline float ror(const float a, const unsigned int n=1) {
+      return (float)ror((int)a,n);
+    }
+
+    inline double ror(const double a, const unsigned int n=1) {
+      return (double)ror((long)a,n);
+    }
+
+    //! Return the absolute value of a number.
+    /**
+       \note This function is different from <tt>std::abs()</tt> or <tt>std::fabs()</tt>
+       because it is able to consider a variable of any type, without cast needed.
+    **/
+    template<typename T>
+    inline T abs(const T a) {
+      return a>=0?a:-a;
+    }
+    inline bool abs(const bool a) {
+      return a;
+    }
+    inline unsigned char abs(const unsigned char a) {
+      return a;
+    }
+    inline unsigned short abs(const unsigned short a) {
+      return a;
+    }
+    inline unsigned int abs(const unsigned int a) {
+      return a;
+    }
+    inline unsigned long abs(const unsigned long a) {
+      return a;
+    }
+    inline double abs(const double a) {
+      return std::fabs(a);
+    }
+    inline float abs(const float a) {
+      return (float)std::fabs((double)a);
+    }
+    inline int abs(const int a) {
+      return std::abs(a);
+    }
+
+    //! Return the square of a number.
+    template<typename T>
+    inline T sqr(const T val) {
+      return val*val;
+    }
+
+    //! Return 1 + log_10(x).
+    inline int xln(const int x) {
+      return x>0?(int)(1+std::log10((double)x)):1;
+    }
+
+    //! Return the minimum value between two numbers.
+    template<typename t1, typename t2>
+    inline typename cimg::superset<t1,t2>::type min(const t1& a, const t2& b) {
+      typedef typename cimg::superset<t1,t2>::type t1t2;
+      return (t1t2)(a<=b?a:b);
+    }
+
+    //! Return the minimum value between three numbers.
+    template<typename t1, typename t2, typename t3>
+    inline typename cimg::superset2<t1,t2,t3>::type min(const t1& a, const t2& b, const t3& c) {
+      typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
+      return (t1t2t3)cimg::min(cimg::min(a,b),c);
+    }
+
+    //! Return the minimum value between four numbers.
+    template<typename t1, typename t2, typename t3, typename t4>
+    inline typename cimg::superset3<t1,t2,t3,t4>::type min(const t1& a, const t2& b, const t3& c, const t4& d) {
+      typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
+      return (t1t2t3t4)cimg::min(cimg::min(a,b,c),d);
+    }
+
+    //! Return the maximum value between two numbers.
+    template<typename t1, typename t2>
+    inline typename cimg::superset<t1,t2>::type max(const t1& a, const t2& b) {
+      typedef typename cimg::superset<t1,t2>::type t1t2;
+      return (t1t2)(a>=b?a:b);
+    }
+
+    //! Return the maximum value between three numbers.
+    template<typename t1, typename t2, typename t3>
+    inline typename cimg::superset2<t1,t2,t3>::type max(const t1& a, const t2& b, const t3& c) {
+      typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
+      return (t1t2t3)cimg::max(cimg::max(a,b),c);
+    }
+
+    //! Return the maximum value between four numbers.
+    template<typename t1, typename t2, typename t3, typename t4>
+    inline typename cimg::superset3<t1,t2,t3,t4>::type max(const t1& a, const t2& b, const t3& c, const t4& d) {
+      typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
+      return (t1t2t3t4)cimg::max(cimg::max(a,b,c),d);
+    }
+
+    //! Return the sign of a number.
+    template<typename T>
+    inline T sign(const T x) {
+      return (x<0)?(T)(-1):(x==0?(T)0:(T)1);
+    }
+
+    //! Return the nearest power of 2 higher than a given number.
+    template<typename T>
+    inline unsigned int nearest_pow2(const T x) {
+      unsigned int i = 1;
+      while (x>i) i<<=1;
+      return i;
+    }
+
+    //! Return the sinc() of a given number.
+    inline double sinc(const double x) {
+      return x?std::sin(x)/x:1;
+    }
+
+    //! Return the modulo of a number.
+    /**
+       \note This modulo function accepts negative and floating-points modulo numbers, as well as
+       variable of any type.
+    **/
+    template<typename T>
+    inline T mod(const T& x, const T& m) {
+      const double dx = (double)x, dm = (double)m;
+      if (x<0) { return (T)(dm+dx+dm*std::floor(-dx/dm)); }
+      return (T)(dx-dm*std::floor(dx/dm));
+    }
+    inline int mod(const bool x, const bool m) {
+      return m?(x?1:0):0;
+    }
+    inline int mod(const char x, const char m) {
+      return x>=0?x%m:(x%m?m+x%m:0);
+    }
+    inline int mod(const short x, const short m) {
+      return x>=0?x%m:(x%m?m+x%m:0);
+    }
+    inline int mod(const int x, const int m) {
+      return x>=0?x%m:(x%m?m+x%m:0);
+    }
+    inline int mod(const long x, const long m) {
+      return x>=0?x%m:(x%m?m+x%m:0);
+    }
+    inline int mod(const unsigned char x, const unsigned char m) {
+      return x%m;
+    }
+    inline int mod(const unsigned short x, const unsigned short m) {
+      return x%m;
+    }
+    inline int mod(const unsigned int x, const unsigned int m) {
+      return x%m;
+    }
+    inline int mod(const unsigned long x, const unsigned long m) {
+      return x%m;
+    }
+
+    //! Return the minmod of two numbers.
+    /**
+       <i>minmod(\p a,\p b)</i> is defined to be :
+       - <i>minmod(\p a,\p b) = min(\p a,\p b)</i>, if \p a and \p b have the same sign.
+       - <i>minmod(\p a,\p b) = 0</i>, if \p a and \p b have different signs.
+    **/
+    template<typename T>
+    inline T minmod(const T a, const T b) {
+      return a*b<=0?0:(a>0?(a<b?a:b):(a<b?b:a));
+    }
+
+    //! Return a random variable between [0,1] with respect to an uniform distribution.
+    inline double rand() {
+      static bool first_time = true;
+      if (first_time) { cimg::srand(); first_time = false; }
+      return (double)std::rand()/RAND_MAX;
+    }
+
+    //! Return a random variable between [-1,1] with respect to an uniform distribution.
+    inline double crand() {
+      return 1-2*cimg::rand();
+    }
+
+    //! Return a random variable following a gaussian distribution and a standard deviation of 1.
+    inline double grand() {
+      double x1, w;
+      do {
+        const double x2 = 2*cimg::rand() - 1.0;
+        x1 = 2*cimg::rand()-1.0;
+        w = x1*x1 + x2*x2;
+      } while (w<=0 || w>=1.0);
+      return x1*std::sqrt((-2*std::log(w))/w);
+    }
+
+    //! Return a random variable following a Poisson distribution of parameter z.
+    inline unsigned int prand(const double z) {
+      if (z<=1.0e-10) return 0;
+      if (z>100.0) return (unsigned int)((std::sqrt(z) * cimg::grand()) + z);
+      unsigned int k = 0;
+      const double y = std::exp(-z);
+      for (double s = 1.0; s>=y; ++k) s*=cimg::rand();
+      return k-1;
+    }
+
+    //! Return a rounded number.
+    /**
+       \param x is the number to be rounded.
+       \param y is the rounding precision.
+       \param rounding_type defines the type of rounding (0=nearest, -1=backward, 1=forward).
+    **/
+    inline double round(const double x, const double y, const int rounding_type=0) {
+      if (y<=0) return x;
+      const double delta = cimg::mod(x,y);
+      if (delta==0.0) return x;
+      if (delta==0.5*y) return x>=0?x+delta:x-delta;
+      const double
+        backward = x - delta,
+        forward = backward + y;
+      return rounding_type<0?backward:(rounding_type>0?forward:(2*delta<y?backward:forward));
+    }
+
+    inline double _pythagore(double a, double b) {
+      const double absa = cimg::abs(a), absb = cimg::abs(b);
+      if (absa>absb) { const double tmp = absb/absa; return absa*std::sqrt(1.0+tmp*tmp); }
+      else { const double tmp = absa/absb; return (absb==0?0:absb*std::sqrt(1.0+tmp*tmp)); }
+    }
+
+    //! Remove the 'case' of an ASCII character.
+    inline char uncase(const char x) {
+      return (char)((x<'A'||x>'Z')?x:x-'A'+'a');
+    }
+
+    //! Remove the 'case' of a C string.
+    /**
+       Acts in-place.
+    **/
+    inline void uncase(char *const string) {
+      if (string) for (char *ptr = string; *ptr; ++ptr) *ptr = uncase(*ptr);
+    }
+
+    //! Read a double number from a C-string.
+    /**
+       \note This function is quite similar to <tt>std::atof()</tt>,
+       but that it allows the retrieval of fractions as in "1/2".
+    **/
+    inline double atof(const char *const str) {
+      double x = 0, y = 1;
+      if (!str) return 0; else { std::sscanf(str,"%lf/%lf",&x,&y); return x/y; }
+    }
+
+    //! Compare the first \p n characters of two C-strings, ignoring the case.
+    /**
+       \note This function is defined since it is not provided by all compilers
+       (not an ANSI function).
+    **/
+    inline int strncasecmp(const char *const s1, const char *const s2, const int l) {
+      if (!l) return 0;
+      if (!s1) return s2?-1:0;
+      const char *ns1 = s1, *ns2 = s2;
+      int k, diff = 0; for (k = 0; k<l && !(diff = uncase(*ns1)-uncase(*ns2)); ++k) { ++ns1; ++ns2; }
+      return k!=l?diff:0;
+    }
+
+    //! Compare two C-strings, ignoring the case.
+    /**
+       \note This function is defined since it is not provided by all compilers
+       (not an ANSI function).
+    **/
+    inline int strcasecmp(const char *const s1, const char *const s2) {
+      if (!s1) return s2?-1:0;
+      const unsigned int l1 = std::strlen(s1), l2 = std::strlen(s2);
+      return cimg::strncasecmp(s1,s2,1+(l1<l2?l1:l2));
+    }
+
+    //! Remove useless delimiters on the borders of a C-string
+    inline bool strpare(char *const s, const char delimiter=' ', const bool symmetric=false, const bool is_iterative=false) {
+      if (!s) return false;
+      const int l = (int)std::strlen(s);
+      int p, q;
+      if (symmetric) for (p = 0, q = l-1; p<q && s[p]==delimiter && s[q]==delimiter; ) { --q; ++p; if (!is_iterative) break; }
+      else {
+        for (p = 0; p<l && s[p]==delimiter; ) { ++p; if (!is_iterative) break; }
+        for (q = l-1; q>p && s[q]==delimiter; ) { --q; if (!is_iterative) break; }
+      }
+      const int n = q - p + 1;
+      if (n!=l) { std::memmove(s,s+p,n); s[n] = 0; return true; }
+      return false;
+    }
+
+    //! Replace explicit escape sequences '\x' in C-strings.
+    inline void strescape(char *const s) {
+#define cimg_strescape(ci,co) case ci: *nd = co; ++ns; break;
+      static unsigned int val = 0;
+      for (char *ns = s, *nd = s; *ns || (bool)(*nd=0); ++nd) if (*ns=='\\') switch (*(++ns)) {
+            cimg_strescape('n','\n');
+            cimg_strescape('t','\t');
+            cimg_strescape('v','\v');
+            cimg_strescape('b','\b');
+            cimg_strescape('r','\r');
+            cimg_strescape('f','\f');
+            cimg_strescape('a','\a');
+            cimg_strescape('\\','\\');
+            cimg_strescape('\?','\?');
+            cimg_strescape('\'','\'');
+            cimg_strescape('\"','\"');
+          case 0 : *nd = 0; break;
+          case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' :
+            std::sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns;
+            *nd = val; break;
+          case 'x':
+            std::sscanf(++ns,"%x",&val); while ((*ns>='0' && *ns<='7') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns;
+            *nd = val; break;
+          default : *nd = *(ns++);
+          } else *nd = *(ns++);
+    }
+
+    // Return a temporary string describing the size of a buffer.
+    inline const char *strbuffersize(const unsigned long size) {
+      static char res[256] = { 0 };
+      if (size<1024LU) cimg_snprintf(res,sizeof(res),"%lu byte%s",size,size>1?"s":"");
+      else if (size<1024*1024LU) { const float nsize = size/1024.0f; cimg_snprintf(res,sizeof(res),"%.1f Kb",nsize); }
+      else if (size<1024*1024*1024LU) { const float nsize = size/(1024*1024.0f); cimg_snprintf(res,sizeof(res),"%.1f Mb",nsize); }
+      else { const float nsize = size/(1024*1024*1024.0f); cimg_snprintf(res,sizeof(res),"%.1f Gb",nsize); }
+      return res;
+    }
+
+    //! Compute the basename of a filename.
+    inline const char* basename(const char *const s)  {
+      const char *p = 0;
+      for (const char *np = s; np>=s && (p=np); np = std::strchr(np,cimg_file_separator)+1) {}
+      return p;
+    }
+
+    // Generate a random filename.
+    inline const char* filenamerand() {
+      static char randomid[9] = { 0,0,0,0,0,0,0,0,0 };
+      cimg::srand();
+      for (unsigned int k = 0; k<8; ++k) {
+        const int v = (int)std::rand()%3;
+        randomid[k] = (char)(v==0?('0'+(std::rand()%10)):(v==1?('a'+(std::rand()%26)):('A'+(std::rand()%26))));
+      }
+      return randomid;
+    }
+
+    // Convert filename into a Windows-style filename.
+    inline void winformat_string(char *const s) {
+      if (s && *s) {
+#if cimg_OS==2
+        char *const ns = new char[MAX_PATH];
+        if (GetShortPathNameA(s,ns,MAX_PATH)) std::strcpy(s,ns);
+#endif
+      }
+    }
+
+    //! Return or set path to store temporary files.
+    inline const char* temporary_path(const char *const user_path=0, const bool reinit_path=false) {
+#define _cimg_test_temporary_path(p) \
+      if (!path_found) { \
+        cimg_snprintf(st_path,1024,"%s",p); \
+        cimg_snprintf(tmp,sizeof(tmp),"%s%c%s",st_path,cimg_file_separator,filetmp); \
+        if ((file=std::fopen(tmp,"wb"))!=0) { std::fclose(file); std::remove(tmp); path_found = true; } \
+      }
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        char tmp[1024] = { 0 }, filetmp[512] = { 0 };
+        std::FILE *file = 0;
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s.tmp",cimg::filenamerand());
+        char *tmpPath = std::getenv("TMP");
+        if (!tmpPath) { tmpPath = std::getenv("TEMP"); winformat_string(tmpPath); }
+        if (tmpPath) _cimg_test_temporary_path(tmpPath);
+#if cimg_OS==2
+        _cimg_test_temporary_path("C:\\WINNT\\Temp");
+        _cimg_test_temporary_path("C:\\WINDOWS\\Temp");
+        _cimg_test_temporary_path("C:\\Temp");
+        _cimg_test_temporary_path("C:");
+        _cimg_test_temporary_path("D:\\WINNT\\Temp");
+        _cimg_test_temporary_path("D:\\WINDOWS\\Temp");
+        _cimg_test_temporary_path("D:\\Temp");
+        _cimg_test_temporary_path("D:");
+#else
+        _cimg_test_temporary_path("/tmp");
+        _cimg_test_temporary_path("/var/tmp");
+#endif
+        if (!path_found) {
+          *st_path = 0;
+          std::strncpy(tmp,filetmp,sizeof(tmp)-1);
+          if ((file=std::fopen(tmp,"wb"))!=0) { std::fclose(file); std::remove(tmp); path_found = true; }
+        }
+        if (!path_found)
+          throw CImgIOException("cimg::temporary_path() : Failed to locate path for writing temporary files.\n");
+      }
+      return st_path;
+    }
+
+    // Return or set path to the "Program files/" directory (windows only).
+#if cimg_OS==2
+    inline const char* programfiles_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[MAX_PATH];
+        std::memset(st_path,0,MAX_PATH);
+        // Note : in the following line, 0x26 = CSIDL_PROGRAM_FILES (not defined on every compiler).
+#if !defined(__INTEL_COMPILER)
+        if (!SHGetSpecialFolderPathA(0,st_path,0x0026,false)) {
+          const char *const pfPath = std::getenv("PROGRAMFILES");
+          if (pfPath) std::strncpy(st_path,pfPath,MAX_PATH-1);
+          else std::strcpy(st_path,"C:\\PROGRA~1");
+        }
+#else
+        std::strcpy(st_path,"C:\\PROGRA~1");
+#endif
+      }
+      return st_path;
+    }
+#endif
+
+    //! Return or set path to the ImageMagick's \c convert tool.
+    inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        const char *const pf_path = programfiles_path();
+        if (!path_found) {
+          std::strcpy(st_path,".\\convert.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%.2d-\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d-Q\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%.2d-\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d-Q\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"convert.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./convert");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"convert");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Return path of the GraphicsMagick's \c gm tool.
+    inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        const char *const pf_path = programfiles_path();
+        if (!path_found) {
+          std::strcpy(st_path,".\\gm.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%.2d-\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d-Q\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%.2d-\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d-Q\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=10 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 9; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        for (int k = 32; k>=0 && !path_found; --k) {
+          cimg_snprintf(st_path,sizeof(st_path),"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gm.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./gm");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gm");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Return or set path of the \c XMedcon tool.
+    inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        const char *const pf_path = programfiles_path();
+        if (!path_found) {
+          std::strcpy(st_path,".\\medcon.bat");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) {
+          std::strcpy(st_path,".\\medcon.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\XMedCon\\bin\\medcon.bat",pf_path);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) {
+          cimg_snprintf(st_path,sizeof(st_path),"%s\\XMedCon\\bin\\medcon.exe",pf_path);
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"medcon.bat");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./medcon");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"medcon");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Return or set path to the 'ffmpeg' command.
+    inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\ffmpeg.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"ffmpeg.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./ffmpeg");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"ffmpeg");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Return or set path to the 'gzip' command.
+    inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\gzip.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gzip.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./gzip");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gzip");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Return or set path to the 'gunzip' command.
+    inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\gunzip.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gunzip.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./gunzip");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"gunzip");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Return or set path to the 'dcraw' command.
+    inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false) {
+      static char *st_path = 0;
+      if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
+      if (user_path) {
+        if (!st_path) st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        std::strncpy(st_path,user_path,1023);
+      } else if (!st_path) {
+        st_path = new char[1024];
+        std::memset(st_path,0,1024);
+        bool path_found = false;
+        std::FILE *file = 0;
+#if cimg_OS==2
+        if (!path_found) {
+          std::strcpy(st_path,".\\dcraw.exe");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"dcraw.exe");
+#else
+        if (!path_found) {
+          std::strcpy(st_path,"./dcraw");
+          if ((file=std::fopen(st_path,"r"))!=0) { std::fclose(file); path_found = true; }
+        }
+        if (!path_found) std::strcpy(st_path,"dcraw");
+#endif
+        winformat_string(st_path);
+      }
+      return st_path;
+    }
+
+    //! Split a filename into two strings 'body' and 'extension'.
+    inline const char *split_filename(const char *const filename, char *const body=0) {
+      if (!filename) { if (body) *body = 0; return 0; }
+      const char *p = 0; for (const char *np = filename; np>=filename && (p=np); np = std::strchr(np,'.')+1) {}
+      if (p==filename) {
+        if (body) std::strcpy(body,filename);
+        return filename + std::strlen(filename);
+      }
+      const unsigned int l = p - filename - 1;
+      if (body) { std::memcpy(body,filename,l); body[l] = 0; }
+      return p;
+    }
+
+    //! Create a numbered version of a filename.
+    inline char* number_filename(const char *const filename, const int number, const unsigned int n, char *const string) {
+      if (!filename) { if (string) *string = 0; return 0; }
+      char format[1024] = { 0 }, body[1024] = { 0 };
+      const char *const ext = cimg::split_filename(filename,body);
+      if (n>0) cimg_snprintf(format,sizeof(format),"%s_%%.%ud.%s",body,n,ext);
+      else cimg_snprintf(format,sizeof(format),"%s_%%d.%s",body,ext);
+      std::sprintf(string,format,number);
+      return string;
+    }
+
+    //! Open a file, and check for possible errors.
+    inline std::FILE *fopen(const char *const path, const char *const mode) {
+      if (!path)
+        throw CImgArgumentException("cimg::fopen() : Specified file path is (null).");
+      if (!mode)
+        throw CImgArgumentException("cimg::fopen() : File '%s', specified mode is (null).",
+                                    path);
+
+      if (*path=='-' && path[1]=='.') return (*mode=='r')?stdin:stdout;
+      std::FILE *res = std::fopen(path,mode);
+      if (!res)
+        throw CImgIOException("cimg::fopen() : Failed to open file '%s' with mode '%s'.",
+                              path,mode);
+      return res;
+    }
+
+    //! Close a file, and check for possible errors.
+    inline int fclose(std::FILE *file) {
+      if (!file) warn("cimg::fclose() : Specified file is (null).");
+      if (!file || file==stdin || file==stdout) return 0;
+      const int errn = std::fclose(file);
+      if (errn!=0) warn("cimg::fclose() : Error code %d returned during file closing.",
+                        errn);
+      return errn;
+    }
+
+    //! Try to guess the image format of a filename, using the magic numbers in its header.
+    inline const char *file_type(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException("cimg::file_type() : Specified filename is (null).");
+      static const char
+        *const _pnm = "pnm",
+        *const _pfm = "pfm",
+        *const _bmp = "bmp",
+        *const _gif = "gif",
+        *const _jpg = "jpg",
+        *const _off = "off",
+        *const _pan = "pan",
+        *const _png = "png",
+        *const _tif = "tif",
+        *const _inr = "inr",
+        *const _dcm = "dcm";
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      const char *f_type = 0, *head;
+      char header[2048] = { 0 }, item[1024] = { 0 };
+      const unsigned char *const uheader = (unsigned char*)header;
+      int err; char cerr;
+      const unsigned int siz = (unsigned int)std::fread(header,2048,1,nfile);   // Read first 2048 bytes.
+      if (!file) cimg::fclose(nfile);
+
+      if (!std::strncmp(header,"OFF\n",4)) f_type = _off; // Check for OFF format.
+      else if (!std::strncmp(header,"#INRIMAGE",9)) f_type = _inr; // Check for INRIMAGE format.
+      else if (!std::strncmp(header,"PANDORE",7)) f_type = _pan; // Check for PANDORE format.
+      else if (!std::strncmp(header+128,"DICM",4)) f_type = _dcm; // Check for DICOM format.
+      else if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) f_type = _jpg;  // Check for JPEG format.
+      else if (header[0]=='B' && header[1]=='M') f_type = _bmp;  // Check for BMP format.
+      else if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' && // Check for GIF format.
+               (header[4]=='7' || header[4]=='9')) f_type = _gif;
+      else if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 &&  // Check for PNG format.
+               uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) f_type = _png;
+      else if ((uheader[0]==0x49 && uheader[1]==0x49) || (uheader[0]==0x4D && uheader[1]==0x4D)) f_type = _tif; // Check for TIFF format.
+      else { // Check for PNM or PFM format.
+        head = header;
+        while (head<header+siz && (err=std::sscanf(head,"%1023[^\n]",item))!=EOF && (*item=='#' || !err))
+          head+=1+(err?std::strlen(item):0);
+        if (std::sscanf(item," P%d",&err)==1) f_type = _pnm;
+        else if (std::sscanf(item," P%c",&cerr)==1 && (cerr=='f' || cerr=='F')) f_type = _pfm;
+      }
+      return f_type;
+    }
+
+    //! Read file data, and check for possible errors.
+    template<typename T>
+    inline int fread(T *const ptr, const unsigned int nmemb, std::FILE *stream) {
+      if (!ptr || nmemb<=0 || !stream)
+        throw CImgArgumentException("cimg::fread() : Invalid reading request of %u %s%s from file %p to buffer %p.",
+                                    nmemb,cimg::type<T>::string(),nmemb>1?"s":"",stream,ptr);
+
+      const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+      unsigned int 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() : Only %u/%u elements could be read from file.",
+             alread,nmemb);
+      return alread;
+    }
+
+    //! Write data to a file, and check for possible errors.
+    template<typename T>
+    inline int fwrite(const T *ptr, const unsigned int nmemb, std::FILE *stream) {
+      if (!ptr || !stream)
+        throw CImgArgumentException("cimg::fwrite() : Invalid writting request of %u %s%s from buffer %p to file %p.",
+                                    nmemb,cimg::type<T>::string(),nmemb>1?"s":"",ptr,stream);
+      if (nmemb<=0) return 0;
+      const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+      unsigned int 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() : Only %u/%u elements could be written in file.",
+             alwrite,nmemb);
+      return alwrite;
+    }
+
+    inline const char* option(const char *const name, const int argc, const char *const *const argv,
+                              const char *const defaut, const char *const usage, const bool reset_static) {
+      static bool first = true, visu = false;
+      if (reset_static) { first = true; return 0; }
+      const char *res = 0;
+      if (first) {
+        first = false;
+        visu = cimg::option("-h",argc,argv,(char*)0,(char*)0,false)!=0;
+        visu |= cimg::option("-help",argc,argv,(char*)0,(char*)0,false)!=0;
+        visu |= cimg::option("--help",argc,argv,(char*)0,(char*)0,false)!=0;
+      }
+      if (!name && visu) {
+        if (usage) {
+          std::fprintf(cimg::output(),"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal);
+          std::fprintf(cimg::output()," : %s",usage);
+          std::fprintf(cimg::output()," (%s, %s)\n\n",__DATE__,__TIME__);
+        }
+        if (defaut) std::fprintf(cimg::output(),"%s\n",defaut);
+      }
+      if (name) {
+        if (argc>0) {
+          int k = 0;
+          while (k<argc && std::strcmp(argv[k],name)) ++k;
+          res = (k++==argc?defaut:(k==argc?argv[--k]:argv[k]));
+        } else res = defaut;
+        if (visu && usage) std::fprintf(cimg::output(),"    %s%-16s%s %-24s %s%s%s\n",
+                                        cimg::t_bold,name,cimg::t_normal,res?res:"0",cimg::t_green,usage,cimg::t_normal);
+      }
+      return res;
+    }
+
+    inline const char* option(const char *const name, const int argc, const char *const *const argv,
+                              const char *const defaut, const char *const usage=0) {
+      return option(name,argc,argv,defaut,usage,false);
+    }
+
+    inline bool option(const char *const name, const int argc, const char *const *const argv,
+                       const bool defaut, const char *const usage=0) {
+      const char *const s = cimg::option(name,argc,argv,(char*)0);
+      const bool res = s?(cimg::strcasecmp(s,"false") && cimg::strcasecmp(s,"off") && cimg::strcasecmp(s,"0")):defaut;
+      cimg::option(name,0,0,res?"true":"false",usage);
+      return res;
+    }
+
+    inline int option(const char *const name, const int argc, const char *const *const argv,
+                      const int defaut, const char *const usage=0) {
+      const char *const s = cimg::option(name,argc,argv,(char*)0);
+      const int res = s?std::atoi(s):defaut;
+      char tmp[256] = { 0 };
+      cimg_snprintf(tmp,sizeof(tmp),"%d",res);
+      cimg::option(name,0,0,tmp,usage);
+      return res;
+    }
+
+    inline char option(const char *const name, const int argc, const char *const *const argv,
+                       const char defaut, const char *const usage=0) {
+      const char *const s = cimg::option(name,argc,argv,(char*)0);
+      const char res = s?*s:defaut;
+      char tmp[8] = { 0 };
+      *tmp = res;
+      cimg::option(name,0,0,tmp,usage);
+      return res;
+    }
+
+    inline float option(const char *const name, const int argc, const char *const *const argv,
+                        const float defaut, const char *const usage=0) {
+      const char *const s = cimg::option(name,argc,argv,(char*)0);
+      const float res = s?(float)cimg::atof(s):defaut;
+      char tmp[256] = { 0 };
+      cimg_snprintf(tmp,sizeof(tmp),"%g",res);
+      cimg::option(name,0,0,tmp,usage);
+      return res;
+    }
+
+    inline double option(const char *const name, const int argc, const char *const *const argv,
+                         const double defaut, const char *const usage=0) {
+      const char *const s = cimg::option(name,argc,argv,(char*)0);
+      const double res = s?cimg::atof(s):defaut;
+      char tmp[256] = { 0 };
+      cimg_snprintf(tmp,sizeof(tmp),"%g",res);
+      cimg::option(name,0,0,tmp,usage);
+      return res;
+    }
+
+    inline const char* argument(const unsigned int nb, const int argc, const char *const *const argv, const unsigned int nb_singles=0, ...) {
+      for (int k = 1, pos = 0; k<argc;) {
+        const char *const item = argv[k];
+        bool option = (*item=='-'), single_option = false;
+        if (option) {
+          va_list ap;
+          va_start(ap,nb_singles);
+          for (unsigned int i = 0; i<nb_singles; ++i) if (!cimg::strcasecmp(item,va_arg(ap,char*))) { single_option = true; break; }
+          va_end(ap);
+        }
+        if (option) { ++k; if (!single_option) ++k; }
+        else { if (pos++==(int)nb) return item; else ++k; }
+      }
+      return 0;
+    }
+
+    //! Print informations about %CImg environement variables.
+    /**
+       Printing is done on the standard error output.
+    **/
+    inline void info() {
+      char tmp[1024] = { 0 };
+      std::fprintf(cimg::output(),"\n %sCImg Library %u.%u.%u%s, compiled %s ( %s ) with the following flags :\n\n",
+                   cimg::t_red,cimg_version/100,(cimg_version/10)%10,cimg_version%10,
+                   cimg::t_normal,__DATE__,__TIME__);
+
+      std::fprintf(cimg::output(),"  > Operating System :       %s%-13s%s %s('cimg_OS'=%d)%s\n",
+                   cimg::t_bold,
+                   cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"),
+                   cimg::t_normal,cimg::t_green,
+                   cimg_OS,
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > CPU endianness :         %s%s Endian%s\n",
+                   cimg::t_bold,
+                   cimg::endianness()?"Big":"Little",
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Verbosity mode :         %s%-13s%s %s('cimg_verbosity'=%d)%s\n",
+                   cimg::t_bold,
+                   cimg_verbosity==0?"Quiet":(cimg_verbosity==1?"Console":(cimg_verbosity==2?"Dialog":(cimg_verbosity==3?"Console+Warnings":"Dialog+Warnings"))),
+                   cimg::t_normal,cimg::t_green,
+                   cimg_verbosity,
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Stricts warnings :       %s%-13s%s %s('cimg_strict_warnings' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_strict_warnings
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Using VT100 messages :   %s%-13s%s %s('cimg_use_vt100' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_vt100
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Display type :           %s%-13s%s %s('cimg_display'=%d)%s\n",
+                   cimg::t_bold,
+                   cimg_display==0?"No display":cimg_display==1?"X11":cimg_display==2?"Windows GDI":"Unknown",
+                   cimg::t_normal,cimg::t_green,
+                   cimg_display,
+                   cimg::t_normal);
+
+#if cimg_display==1
+      std::fprintf(cimg::output(),"  > Using XShm for X11 :     %s%-13s%s %s('cimg_use_xshm' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_xshm
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Using XRand for X11 :    %s%-13s%s %s('cimg_use_xrandr' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_xrandr
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+#endif
+      std::fprintf(cimg::output(),"  > Using OpenMP :           %s%-13s%s %s('cimg_use_openmp' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_openmp
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+      std::fprintf(cimg::output(),"  > Using PNG library :      %s%-13s%s %s('cimg_use_png' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_png
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+      std::fprintf(cimg::output(),"  > Using JPEG library :     %s%-13s%s %s('cimg_use_jpeg' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_jpeg
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Using TIFF library :     %s%-13s%s %s('cimg_use_tiff' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_tiff
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Using Magick++ library : %s%-13s%s %s('cimg_use_magick' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_magick
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Using FFTW3 library :    %s%-13s%s %s('cimg_use_fftw3' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_fftw3
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"  > Using LAPACK library :   %s%-13s%s %s('cimg_use_lapack' %s)%s\n",
+                   cimg::t_bold,
+#ifdef cimg_use_lapack
+                   "Yes",cimg::t_normal,cimg::t_green,"defined",
+#else
+                   "No",cimg::t_normal,cimg::t_green,"undefined",
+#endif
+                   cimg::t_normal);
+
+      cimg_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::imagemagick_path());
+      std::fprintf(cimg::output(),"  > Path of ImageMagick :    %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      cimg_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::graphicsmagick_path());
+      std::fprintf(cimg::output(),"  > Path of GraphicsMagick : %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      cimg_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::medcon_path());
+      std::fprintf(cimg::output(),"  > Path of 'medcon' :       %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      cimg_snprintf(tmp,sizeof(tmp),"\"%.1020s\"",cimg::temporary_path());
+      std::fprintf(cimg::output(),"  > Temporary path :         %s%-13s%s\n",
+                   cimg::t_bold,
+                   tmp,
+                   cimg::t_normal);
+
+      std::fprintf(cimg::output(),"\n");
+    }
+
+    // Declare LAPACK function signatures if necessary.
+#ifdef cimg_use_lapack
+    template<typename T>
+    inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) {
+      dgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
+    }
+
+    inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) {
+      sgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
+    }
+
+    template<typename T>
+    inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) {
+      dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
+    }
+
+    inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) {
+      sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
+    }
+
+    template<typename T>
+    inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN,
+                      T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) {
+      dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
+    }
+
+    inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN,
+                      float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) {
+      sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
+    }
+
+    template<typename T>
+    inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) {
+      int one = 1;
+      dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
+    }
+
+    inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) {
+      int one = 1;
+      sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
+    }
+
+    template<typename T>
+    inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) {
+      dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
+    }
+
+    inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) {
+      ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
+    }
+#endif
+
+    // End of the 'cimg' namespace
+  }
+
+  /*------------------------------------------------
+   #
+   #
+   #   Definition of mathematical operators and
+   #   external functions.
+   #
+   #
+   -------------------------------------------------*/
+
+#define _cimg_create_ext_operators(typ) \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator+(const typ val, const CImg<T>& img) { \
+    return img + val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator-(const typ val, const CImg<T>& img) { \
+    typedef typename cimg::superset<T,typ>::type Tt; \
+    return CImg<Tt>(img._width,img._height,img._depth,img._spectrum,val)-=img; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator*(const typ val, const CImg<T>& img) { \
+    return img*val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator/(const typ val, const CImg<T>& img) { \
+    return val*img.get_invert(); \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator&(const typ val, const CImg<T>& img) { \
+    return img & val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator|(const typ val, const CImg<T>& img) { \
+    return img | val; \
+  } \
+  template<typename T> \
+  inline CImg<typename cimg::superset<T,typ>::type> operator^(const typ val, const CImg<T>& img) { \
+    return img ^ val; \
+  } \
+
+  _cimg_create_ext_operators(bool)
+  _cimg_create_ext_operators(unsigned char)
+  _cimg_create_ext_operators(char)
+  _cimg_create_ext_operators(signed char)
+  _cimg_create_ext_operators(unsigned short)
+  _cimg_create_ext_operators(short)
+  _cimg_create_ext_operators(unsigned int)
+  _cimg_create_ext_operators(int)
+  _cimg_create_ext_operators(unsigned long)
+  _cimg_create_ext_operators(long)
+  _cimg_create_ext_operators(float)
+  _cimg_create_ext_operators(double)
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg<T>& img) {
+    return img + expression;
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator-(const char *const expression, const CImg<T>& img) {
+    return (CImg<_cimg_Tfloat>(img._width,img._height,img._depth,img._spectrum)=expression)-=img;
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg<T>& img) {
+    return img*expression;
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg<T>& img) {
+    return expression*img.get_invert();
+  }
+
+  template<typename T>
+  inline CImg<T> operator&(const char *const expression, const CImg<T>& img) {
+    return img & expression;
+  }
+
+  template<typename T>
+  inline CImg<T> operator|(const char *const expression, const CImg<T>& img) {
+    return img | expression;
+  }
+
+  template<typename T>
+  inline CImg<T> operator^(const char *const expression, const CImg<T>& img) {
+    return img ^ expression;
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> sqr(const CImg<T>& instance) {
+    return instance.get_sqr();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> sqrt(const CImg<T>& instance) {
+    return instance.get_sqrt();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> exp(const CImg<T>& instance) {
+    return instance.get_exp();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> log(const CImg<T>& instance) {
+    return instance.get_log();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> log10(const CImg<T>& instance) {
+    return instance.get_log10();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> abs(const CImg<T>& instance) {
+    return instance.get_abs();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> sign(const CImg<T>& instance) {
+    return instance.get_sign();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> cos(const CImg<T>& instance) {
+    return instance.get_cos();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> sin(const CImg<T>& instance) {
+    return instance.get_sin();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> sinc(const CImg<T>& instance) {
+    return instance.get_sinc();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> tan(const CImg<T>& instance) {
+    return instance.get_tan();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> acos(const CImg<T>& instance) {
+    return instance.get_acos();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> asin(const CImg<T>& instance) {
+    return instance.get_asin();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> atan(const CImg<T>& instance) {
+    return instance.get_atan();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> cosh(const CImg<T>& instance) {
+    return instance.get_cosh();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> sinh(const CImg<T>& instance) {
+    return instance.get_sinh();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> tanh(const CImg<T>& instance) {
+    return instance.get_tanh();
+  }
+
+  template<typename T>
+  inline CImg<T> transpose(const CImg<T>& instance) {
+    return instance.get_transpose();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> invert(const CImg<T>& instance) {
+    return instance.get_invert();
+  }
+
+  template<typename T>
+  inline CImg<_cimg_Tfloat> pseudoinvert(const CImg<T>& instance) {
+    return instance.get_pseudoinvert();
+  }
+
+  /*-------------------------------------------
+   #
+   #
+   #
+   # 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;
+
+    //! Width of the underlying window.
+    volatile unsigned int _window_width;
+
+    //! Height of the underlying window.
+    volatile unsigned int _window_height;
+
+    //! X-pos of the display on the screen.
+    volatile int _window_x;
+
+    //! Y-pos of the display on the screen.
+    volatile int _window_y;
+
+    //! 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;
+
+    //! Normalization type used for the display.
+    unsigned int _normalization;
+
+    //! Display title.
+    char *_title;
+
+    //! Button state of the mouse.
+    volatile unsigned int _button;
+
+    //! Wheel state of the mouse.
+    volatile int _wheel;
+
+    //! Key value if pressed.
+    volatile unsigned int _keys[128];
+    volatile unsigned int _released_keys[128];
+
+    //! Closed state of the window.
+    volatile bool _is_closed;
+
+    //! Resized state of the window.
+    volatile bool _is_resized;
+
+    //! Moved state of the window.
+    volatile bool _is_moved;
+
+    //! Event state of the window.
+    volatile bool _is_event;
+
+    //! Current state of the corresponding key (exists for all referenced keys).
+    volatile bool _is_keyESC;
+    volatile bool _is_keyF1;
+    volatile bool _is_keyF2;
+    volatile bool _is_keyF3;
+    volatile bool _is_keyF4;
+    volatile bool _is_keyF5;
+    volatile bool _is_keyF6;
+    volatile bool _is_keyF7;
+    volatile bool _is_keyF8;
+    volatile bool _is_keyF9;
+    volatile bool _is_keyF10;
+    volatile bool _is_keyF11;
+    volatile bool _is_keyF12;
+    volatile bool _is_keyPAUSE;
+    volatile bool _is_key1;
+    volatile bool _is_key2;
+    volatile bool _is_key3;
+    volatile bool _is_key4;
+    volatile bool _is_key5;
+    volatile bool _is_key6;
+    volatile bool _is_key7;
+    volatile bool _is_key8;
+    volatile bool _is_key9;
+    volatile bool _is_key0;
+    volatile bool _is_keyBACKSPACE;
+    volatile bool _is_keyINSERT;
+    volatile bool _is_keyHOME;
+    volatile bool _is_keyPAGEUP;
+    volatile bool _is_keyTAB;
+    volatile bool _is_keyQ;
+    volatile bool _is_keyW;
+    volatile bool _is_keyE;
+    volatile bool _is_keyR;
+    volatile bool _is_keyT;
+    volatile bool _is_keyY;
+    volatile bool _is_keyU;
+    volatile bool _is_keyI;
+    volatile bool _is_keyO;
+    volatile bool _is_keyP;
+    volatile bool _is_keyDELETE;
+    volatile bool _is_keyEND;
+    volatile bool _is_keyPAGEDOWN;
+    volatile bool _is_keyCAPSLOCK;
+    volatile bool _is_keyA;
+    volatile bool _is_keyS;
+    volatile bool _is_keyD;
+    volatile bool _is_keyF;
+    volatile bool _is_keyG;
+    volatile bool _is_keyH;
+    volatile bool _is_keyJ;
+    volatile bool _is_keyK;
+    volatile bool _is_keyL;
+    volatile bool _is_keyENTER;
+    volatile bool _is_keySHIFTLEFT;
+    volatile bool _is_keyZ;
+    volatile bool _is_keyX;
+    volatile bool _is_keyC;
+    volatile bool _is_keyV;
+    volatile bool _is_keyB;
+    volatile bool _is_keyN;
+    volatile bool _is_keyM;
+    volatile bool _is_keySHIFTRIGHT;
+    volatile bool _is_keyARROWUP;
+    volatile bool _is_keyCTRLLEFT;
+    volatile bool _is_keyAPPLEFT;
+    volatile bool _is_keyALT;
+    volatile bool _is_keySPACE;
+    volatile bool _is_keyALTGR;
+    volatile bool _is_keyAPPRIGHT;
+    volatile bool _is_keyMENU;
+    volatile bool _is_keyCTRLRIGHT;
+    volatile bool _is_keyARROWLEFT;
+    volatile bool _is_keyARROWDOWN;
+    volatile bool _is_keyARROWRIGHT;
+    volatile bool _is_keyPAD0;
+    volatile bool _is_keyPAD1;
+    volatile bool _is_keyPAD2;
+    volatile bool _is_keyPAD3;
+    volatile bool _is_keyPAD4;
+    volatile bool _is_keyPAD5;
+    volatile bool _is_keyPAD6;
+    volatile bool _is_keyPAD7;
+    volatile bool _is_keyPAD8;
+    volatile bool _is_keyPAD9;
+    volatile bool _is_keyPADADD;
+    volatile bool _is_keyPADSUB;
+    volatile bool _is_keyPADMUL;
+    volatile bool _is_keyPADDIV;
+
+    //! Fullscreen state of the display.
+    bool _is_fullscreen;
+
+    // Internal variables.
+    float _fps_fps, _min, _max;
+    unsigned long _timer, _fps_frames, _fps_timer;
+
+    //@}
+    //---------------------------
+    //
+    //! \name Plugins
+    //@{
+    //---------------------------
+
+#ifdef cimgdisplay_plugin
+#include cimgdisplay_plugin
+#endif
+#ifdef cimgdisplay_plugin1
+#include cimgdisplay_plugin1
+#endif
+#ifdef cimgdisplay_plugin2
+#include cimgdisplay_plugin2
+#endif
+#ifdef cimgdisplay_plugin3
+#include cimgdisplay_plugin3
+#endif
+#ifdef cimgdisplay_plugin4
+#include cimgdisplay_plugin4
+#endif
+#ifdef cimgdisplay_plugin5
+#include cimgdisplay_plugin5
+#endif
+#ifdef cimgdisplay_plugin6
+#include cimgdisplay_plugin6
+#endif
+#ifdef cimgdisplay_plugin7
+#include cimgdisplay_plugin7
+#endif
+#ifdef cimgdisplay_plugin8
+#include cimgdisplay_plugin8
+#endif
+
+    //@}
+    //--------------------------------------------------------
+    //
+    //! \name Constructors / Destructor / Instance Management
+    //@{
+    //--------------------------------------------------------
+
+    //! Destructor.
+    ~CImgDisplay() {
+      assign();
+    }
+
+    //! Create an empty display window.
+    CImgDisplay():
+      _width(0),_height(0),_window_width(0),_window_height(0),_window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),
+      _normalization(0),_title(0),_button(0),_wheel(0),_is_closed(true),_is_resized(false),_is_moved(false),_is_event(false),
+      _is_fullscreen(false),_min(0),_max(0) {
+      assign();
+    }
+
+    //! Create a display window with a specified size \p pwidth x \p height.
+    /** \param width Width of the display window.
+        \param height Height of the display window.
+        \param title Title of the display window.
+        \param normalization Normalization type of the display window (0=none, 1=always, 2=once).
+        \param is_fullscreen : Fullscreen mode.
+        \param is_closed : Initially visible mode.
+        A black image will be initially displayed in the display window.
+    **/
+    CImgDisplay(const unsigned int width, const unsigned int height,
+                const char *const title=0, const unsigned int normalization=3,
+                const bool is_fullscreen=false, const bool is_closed=false):
+      _width(0),_height(0),_window_width(0),_window_height(0),_window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),
+      _normalization(0),_title(0),_button(0),_wheel(0),_is_closed(true),_is_resized(false),_is_moved(false),_is_event(false),
+      _is_fullscreen(false),_min(0),_max(0) {
+      assign(width,height,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! 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 : Normalization type of the display window.
+        \param is_fullscreen : Fullscreen mode.
+        \param is_closed : Initially visible mode.
+    **/
+    template<typename T>
+    explicit CImgDisplay(const CImg<T>& img,
+                         const char *const title=0, const unsigned int normalization=3,
+                         const bool is_fullscreen=false, const bool is_closed=false):
+      _width(0),_height(0),_window_width(0),_window_height(0),_window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),
+      _normalization(0),_title(0),_button(0),_wheel(0),_is_closed(true),_is_resized(false),_is_moved(false),_is_event(false),
+      _is_fullscreen(false),_min(0),_max(0) {
+      assign(img,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! 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 : Normalization type of the display window.
+        \param is_fullscreen : Fullscreen mode.
+        \param is_closed : Initially visible mode.
+    **/
+    template<typename T>
+    explicit CImgDisplay(const CImgList<T>& list,
+                         const char *const title=0, const unsigned int normalization=3,
+                         const bool is_fullscreen=false, const bool is_closed=false):
+      _width(0),_height(0),_window_width(0),_window_height(0),_window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),
+      _normalization(0),_title(0),_button(0),_wheel(0),_is_closed(true),_is_resized(false),_is_moved(false),_is_event(false),
+      _is_fullscreen(false),_min(0),_max(0) {
+      assign(list,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! Create a display window by copying another one.
+    /**
+        \param disp  : Display window to copy.
+    **/
+    CImgDisplay(const CImgDisplay& disp):
+      _width(0),_height(0),_window_width(0),_window_height(0),_window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),
+      _normalization(0),_title(0),_button(0),_wheel(0),_is_closed(true),_is_resized(false),_is_moved(false),_is_event(false),
+      _is_fullscreen(false),_min(0),_max(0) {
+      assign(disp);
+    }
+
+#if cimg_display==0
+
+    static void _no_display_exception() {
+      throw CImgDisplayException("CImgDisplay() : No display available.");
+    }
+
+    //! In-place version of the destructor.
+    CImgDisplay& assign() {
+      _no_display_exception();
+      return flush();
+    }
+
+    //! In-place version of the constructor.
+    CImgDisplay& assign(const unsigned int width, const unsigned int height,
+                        const char *const title=0, const unsigned int normalization=3,
+                        const bool is_fullscreen=false, const bool is_closed=false) {
+      cimg::unused(width,height,title,normalization,is_fullscreen,is_closed);
+      return assign();
+    }
+
+    //! In-place version of the constructor.
+    template<typename T>
+    CImgDisplay& assign(const CImg<T>& img,
+                        const char *const title=0, const unsigned int normalization=3,
+                        const bool is_fullscreen=false, const bool is_closed=false) {
+      return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! In-place version of the constructor.
+    template<typename T>
+    CImgDisplay& assign(const CImgList<T>& list,
+                        const char *const title=0, const unsigned int normalization=3,
+                        const bool is_fullscreen=false, const bool is_closed=false) {
+      return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed);
+    }
+
+    //! In-place version of the constructor.
+    CImgDisplay& assign(const CImgDisplay &disp) {
+      return assign(disp._width,disp._height);
+    }
+
+#endif
+
+    //! Return a reference to an empty display.
+    static CImgDisplay& empty() {
+      static CImgDisplay _empty;
+      return _empty.assign();
+    }
+
+#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,128,-85,false),CImgDisplay::_fitscreen(dx,dy,dz,128,-85,true)
+    static unsigned int _fitscreen(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1,
+                                   const int dmin=128, const int dmax=-85,const bool return_last=false) {
+      unsigned int nw = dx + (dz>1?dz:0), nh = dy + (dz>1?dz:0);
+      const unsigned int
+        sw = CImgDisplay::screen_width(), sh = CImgDisplay::screen_height(),
+        mw = dmin<0?(unsigned int)(sw*-dmin/100):(unsigned int)dmin,
+        mh = dmin<0?(unsigned int)(sh*-dmin/100):(unsigned int)dmin,
+        Mw = dmax<0?(unsigned int)(sw*-dmax/100):(unsigned int)dmax,
+        Mh = dmax<0?(unsigned int)(sh*-dmax/100):(unsigned int)dmax;
+      if (nw<mw) { nh = nh*mw/nw; nh+=(nh==0?1:0); nw = mw; }
+      if (nh<mh) { nw = nw*mh/nh; nw+=(nw==0?1:0); nh = mh; }
+      if (nw>Mw) { nh = nh*Mw/nw; nh+=(nh==0?1:0); nw = Mw; }
+      if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0?1:0); nh = Mh; }
+      if (nw<mw) nw = mw;
+      if (nh<mh) nh = mh;
+      if (return_last) return nh;
+      return nw;
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Overloaded Operators
+    //@{
+    //------------------------------------------
+
+    // Operator=().
+    template<typename t>
+    CImgDisplay& operator=(const CImg<t>& img) {
+      return display(img);
+    }
+
+    // Operator=().
+    template<typename t>
+    CImgDisplay& operator=(const CImgList<t>& list) {
+      return display(list);
+    }
+
+    //! Operator=().
+    CImgDisplay& operator=(const CImgDisplay& disp) {
+      return assign(disp);
+    }
+
+    //! Return true if display is not empty.
+    operator bool() const {
+      return !is_empty();
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Instance Checking
+    //@{
+    //------------------------------------------
+
+    //! Return true is display is empty.
+    bool is_empty() const {
+      return !(_width && _height);
+    }
+
+    bool is_closed() const {
+      return _is_closed;
+    }
+
+    bool is_resized() const {
+      return _is_resized;
+    }
+
+    bool is_moved() const {
+      return _is_moved;
+    }
+
+    bool is_event() const {
+      return _is_event;
+    }
+
+    bool is_fullscreen() const {
+      return _is_fullscreen;
+    }
+
+    bool is_key() const {
+      return _is_keyESC || _is_keyF1 || _is_keyF2 || _is_keyF3 ||
+        _is_keyF4 || _is_keyF5 || _is_keyF6 || _is_keyF7 ||
+        _is_keyF8 || _is_keyF9 || _is_keyF10 || _is_keyF11 ||
+        _is_keyF12 || _is_keyPAUSE || _is_key1 || _is_key2 ||
+        _is_key3 || _is_key4 || _is_key5 || _is_key6 ||
+        _is_key7 || _is_key8 || _is_key9 || _is_key0 ||
+        _is_keyBACKSPACE || _is_keyINSERT || _is_keyHOME ||
+        _is_keyPAGEUP || _is_keyTAB || _is_keyQ || _is_keyW ||
+        _is_keyE || _is_keyR || _is_keyT || _is_keyY ||
+        _is_keyU || _is_keyI || _is_keyO || _is_keyP ||
+        _is_keyDELETE || _is_keyEND || _is_keyPAGEDOWN ||
+        _is_keyCAPSLOCK || _is_keyA || _is_keyS || _is_keyD ||
+        _is_keyF || _is_keyG || _is_keyH || _is_keyJ ||
+        _is_keyK || _is_keyL || _is_keyENTER ||
+        _is_keySHIFTLEFT || _is_keyZ || _is_keyX || _is_keyC ||
+        _is_keyV || _is_keyB || _is_keyN || _is_keyM ||
+        _is_keySHIFTRIGHT || _is_keyARROWUP || _is_keyCTRLLEFT ||
+        _is_keyAPPLEFT || _is_keyALT || _is_keySPACE || _is_keyALTGR ||
+        _is_keyAPPRIGHT || _is_keyMENU || _is_keyCTRLRIGHT ||
+        _is_keyARROWLEFT || _is_keyARROWDOWN || _is_keyARROWRIGHT ||
+        _is_keyPAD0 || _is_keyPAD1 || _is_keyPAD2 ||
+        _is_keyPAD3 || _is_keyPAD4 || _is_keyPAD5 ||
+        _is_keyPAD6 || _is_keyPAD7 || _is_keyPAD8 ||
+        _is_keyPAD9 || _is_keyPADADD || _is_keyPADSUB ||
+        _is_keyPADMUL || _is_keyPADDIV;
+    }
+
+#define _cimg_iskey_def(k) \
+    bool is_key##k() const { \
+      return _is_key##k; \
+    }
+    _cimg_iskey_def(ESC); _cimg_iskey_def(F1); _cimg_iskey_def(F2); _cimg_iskey_def(F3);
+    _cimg_iskey_def(F4); _cimg_iskey_def(F5); _cimg_iskey_def(F6); _cimg_iskey_def(F7);
+    _cimg_iskey_def(F8); _cimg_iskey_def(F9); _cimg_iskey_def(F10); _cimg_iskey_def(F11);
+    _cimg_iskey_def(F12); _cimg_iskey_def(PAUSE); _cimg_iskey_def(1); _cimg_iskey_def(2);
+    _cimg_iskey_def(3); _cimg_iskey_def(4); _cimg_iskey_def(5); _cimg_iskey_def(6);
+    _cimg_iskey_def(7); _cimg_iskey_def(8); _cimg_iskey_def(9); _cimg_iskey_def(0);
+    _cimg_iskey_def(BACKSPACE); _cimg_iskey_def(INSERT); _cimg_iskey_def(HOME);
+    _cimg_iskey_def(PAGEUP); _cimg_iskey_def(TAB); _cimg_iskey_def(Q); _cimg_iskey_def(W);
+    _cimg_iskey_def(E); _cimg_iskey_def(R); _cimg_iskey_def(T); _cimg_iskey_def(Y);
+    _cimg_iskey_def(U); _cimg_iskey_def(I); _cimg_iskey_def(O); _cimg_iskey_def(P);
+    _cimg_iskey_def(DELETE); _cimg_iskey_def(END); _cimg_iskey_def(PAGEDOWN);
+    _cimg_iskey_def(CAPSLOCK); _cimg_iskey_def(A); _cimg_iskey_def(S); _cimg_iskey_def(D);
+    _cimg_iskey_def(F); _cimg_iskey_def(G); _cimg_iskey_def(H); _cimg_iskey_def(J);
+    _cimg_iskey_def(K); _cimg_iskey_def(L); _cimg_iskey_def(ENTER);
+    _cimg_iskey_def(SHIFTLEFT); _cimg_iskey_def(Z); _cimg_iskey_def(X); _cimg_iskey_def(C);
+    _cimg_iskey_def(V); _cimg_iskey_def(B); _cimg_iskey_def(N); _cimg_iskey_def(M);
+    _cimg_iskey_def(SHIFTRIGHT); _cimg_iskey_def(ARROWUP); _cimg_iskey_def(CTRLLEFT);
+    _cimg_iskey_def(APPLEFT); _cimg_iskey_def(ALT); _cimg_iskey_def(SPACE); _cimg_iskey_def(ALTGR);
+    _cimg_iskey_def(APPRIGHT); _cimg_iskey_def(MENU); _cimg_iskey_def(CTRLRIGHT);
+    _cimg_iskey_def(ARROWLEFT); _cimg_iskey_def(ARROWDOWN); _cimg_iskey_def(ARROWRIGHT);
+    _cimg_iskey_def(PAD0); _cimg_iskey_def(PAD1); _cimg_iskey_def(PAD2);
+    _cimg_iskey_def(PAD3); _cimg_iskey_def(PAD4); _cimg_iskey_def(PAD5);
+    _cimg_iskey_def(PAD6); _cimg_iskey_def(PAD7); _cimg_iskey_def(PAD8);
+    _cimg_iskey_def(PAD9); _cimg_iskey_def(PADADD); _cimg_iskey_def(PADSUB);
+    _cimg_iskey_def(PADMUL); _cimg_iskey_def(PADDIV);
+
+    bool is_key(const unsigned int key) const {
+#define _cimg_iskey_test(k) if (key==cimg::key##k) return _is_key##k;
+      _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3);
+      _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7);
+      _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11);
+      _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2);
+      _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6);
+      _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0);
+      _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME);
+      _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W);
+      _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y);
+      _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P);
+      _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN);
+      _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D);
+      _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J);
+      _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER);
+      _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C);
+      _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M);
+      _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT);
+      _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR);
+      _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT);
+      _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT);
+      _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2);
+      _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5);
+      _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8);
+      _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB);
+      _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV);
+      return false;
+    }
+
+    //! Get keycode corresponding to given input string.
+    bool is_key(const char *const textcode) const {
+#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(textcode,#k)) return _is_key##k;
+      _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3);
+      _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7);
+      _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11);
+      _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2);
+      _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6);
+      _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0);
+      _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME);
+      _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W);
+      _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y);
+      _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P);
+      _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN);
+      _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D);
+      _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J);
+      _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER);
+      _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C);
+      _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M);
+      _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT);
+      _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR);
+      _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT);
+      _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT);
+      _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2);
+      _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5);
+      _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8);
+      _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB);
+      _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV);
+      return false;
+    }
+
+    //! Test if a key sequence has been typed.
+    bool is_key_sequence(const unsigned int *const key_sequence, const unsigned int length, const bool remove_sequence=false) {
+      if (key_sequence && length) {
+        const unsigned int
+          *const ps_end = key_sequence + length - 1,
+          *const pk_end = (unsigned int*)_keys + 1 + sizeof(_keys)/sizeof(unsigned int) - length,
+          k = *ps_end;
+        for (unsigned int *pk = (unsigned int*)_keys; pk<pk_end; ) {
+          if (*(pk++)==k) {
+            bool res = true;
+            const unsigned int *ps = ps_end, *pk2 = pk;
+            for (unsigned int i = 1; i<length; ++i) res = (*(--ps)==*(pk2++));
+            if (res) {
+              if (remove_sequence) std::memset((void*)(pk-1),0,sizeof(unsigned int)*length);
+              return true;
+            }
+          }
+        }
+      }
+      return false;
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Instance Characteristics
+    //@{
+    //------------------------------------------
+
+    //! Return display width.
+    int width() const {
+      return (int)_width;
+    }
+
+    //! Return display height.
+    int height() const {
+      return (int)_height;
+    }
+
+    //! Return X-coordinate of the mouse pointer.
+    int mouse_x() const {
+      return _mouse_x;
+    }
+
+    //! Return Y-coordinate of the mouse pointer.
+    int mouse_y() const {
+      return _mouse_y;
+    }
+
+    //! Return current or previous state of the mouse buttons.
+    unsigned int button() const {
+      return _button;
+    }
+
+    //! Return current state of the mouse wheel.
+    int wheel() const {
+      return _wheel;
+    }
+
+    //! Return current or previous state of the keyboard.
+    unsigned int key(const unsigned int pos=0) const {
+      return pos<(sizeof(_keys)/sizeof(unsigned int))?_keys[pos]:0;
+    }
+
+    unsigned int released_key(const unsigned int pos=0) const {
+      return pos<(sizeof(_released_keys)/sizeof(unsigned int))?_released_keys[pos]:0;
+    }
+
+    //! Get keycode corresponding to given input string.
+    static unsigned int keycode(const char *const textcode) {
+#define _cimg_keycode(k) if (!cimg::strcasecmp(textcode,#k)) return cimg::key##k;
+      _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3);
+      _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7);
+      _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11);
+      _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2);
+      _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6);
+      _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0);
+      _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME);
+      _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W);
+      _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y);
+      _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P);
+      _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN);
+      _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D);
+      _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J);
+      _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER);
+      _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C);
+      _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M);
+      _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT);
+      _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR);
+      _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT);
+      _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT);
+      _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2);
+      _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5);
+      _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8);
+      _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB);
+      _cimg_keycode(PADMUL); _cimg_keycode(PADDIV);
+      return 0;
+    }
+
+    //! Return normalization type of the display.
+    unsigned int normalization() const {
+      return _normalization;
+    }
+
+    //! Return title of the display.
+    const char *title() const {
+      return _title;
+    }
+
+    //! Return display window width.
+    int window_width() const {
+      return (int)_window_width;
+    }
+
+    //! Return display window height.
+    int window_height() const {
+      return (int)_window_height;
+    }
+
+    //! Return X-coordinate of the window.
+    int window_x() const {
+      return _window_x;
+    }
+
+    //! Return Y-coordinate of the window.
+    int window_y() const {
+      return _window_y;
+    }
+
+#if cimg_display==0
+
+    //! Return the width of the screen resolution.
+    static int screen_width() {
+      _no_display_exception();
+      return 0;
+    }
+
+    //! Return the height of the screen resolution.
+    static int screen_height() {
+      _no_display_exception();
+      return 0;
+    }
+
+#endif
+
+    //! Return the frame per second rate.
+    float frames_per_second() {
+      if (!_fps_timer) _fps_timer = cimg::time();
+      const float delta = (cimg::time()-_fps_timer)/1000.0f;
+      ++_fps_frames;
+      if (delta>=1) {
+        _fps_fps = _fps_frames/delta;
+        _fps_frames = 0;
+        _fps_timer = cimg::time();
+      }
+      return _fps_fps;
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Display Manipulation
+    //@{
+    //------------------------------------------
+
+#if cimg_display==0
+
+    //! Display an image in a window.
+    template<typename T>
+    CImgDisplay& display(const CImg<T>& img) {
+      return assign(img);
+    }
+
+#endif
+
+    //! Display an image list CImgList<T> into a display window.
+    /** First, all images of the list are appended into a single image used for visualization,
+        then this image is displayed in the current display window.
+        \param list     : The list of images to display.
+        \param axis     : The axis used to append the image for visualization. Can be 'x' (default),'y','z' or 'c'.
+        \param align : Defines the relative alignment of images when displaying images of different sizes.
+        Can be '\p c' (centered, which is the default), '\p p' (top alignment) and '\p n' (bottom aligment).
+    **/
+    template<typename T>
+    CImgDisplay& display(const CImgList<T>& list, const char axis='x', const char align='p') {
+      return display(list.get_append(axis,align));
+    }
+
+    //! Resize a display window in its current size.
+    CImgDisplay& resize(const bool redraw=true) {
+      resize(_window_width,_window_height,redraw);
+      return *this;
+    }
+
+    //! 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);
+    }
+
+#if cimg_display==0
+
+    //! Resize window.
+    CImgDisplay& resize(const int width, const int height, const bool redraw=true) {
+      return assign(width,height,0,3,redraw);
+    }
+
+#endif
+
+    // Render pixel buffer with size (wd,hd) from source buffer of size (ws,hs).
+    template<typename t, typename T>
+    static void _render_resize(const T *ptrs, const unsigned int ws, const unsigned int hs,
+                               t *ptrd, const unsigned int wd, const unsigned int hd) {
+      unsigned int *const offx = new unsigned int[wd], *const offy = new unsigned int[hd+1], *poffx, *poffy;
+      float s, curr, old;
+      s = (float)ws/wd;
+      poffx = offx; curr = 0; for (unsigned int x = 0; x<wd; ++x) { old = curr; curr+=s; *(poffx++) = (unsigned int)curr - (unsigned int)old; }
+      s = (float)hs/hd;
+      poffy = offy; curr = 0; for (unsigned int y = 0; y<hd; ++y) { old = curr; curr+=s; *(poffy++) = ws*((unsigned int)curr - (unsigned int)old); }
+      *poffy = 0;
+      poffy = offy;
+      for (unsigned int y = 0; y<hd; ) {
+        const T *ptr = ptrs;
+        poffx = offx;
+        for (unsigned int x = 0; x<wd; ++x) { *(ptrd++) = *ptr; ptr+=*(poffx++); }
+        ++y;
+        unsigned int dy = *(poffy++);
+        for ( ; !dy && y<hd; std::memcpy(ptrd,ptrd - wd,sizeof(t)*wd), ++y, ptrd+=wd, dy = *(poffy++)) {}
+        ptrs+=dy;
+      }
+      delete[] offx; delete[] offy;
+    }
+
+    //! Set fullscreen mode.
+    CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool redraw=true) {
+      if (is_empty() || _is_fullscreen==is_fullscreen) return *this;
+      return toggle_fullscreen(redraw);
+    }
+
+#if cimg_display==0
+
+    //! Toggle fullscreen mode.
+    CImgDisplay& toggle_fullscreen(const bool redraw=true) {
+      return assign(_width,_height,0,3,redraw);
+    }
+
+    //! Show a closed display.
+    CImgDisplay& show() {
+      return assign();
+    }
+
+    //! Close a visible display.
+    CImgDisplay& close() {
+      return assign();
+    }
+
+    //! Move window.
+    CImgDisplay& move(const int pos_x, const int pos_y) {
+      return assign(pos_x,pos_y);
+    }
+
+    //! Show mouse pointer.
+    CImgDisplay& show_mouse() {
+      return assign();
+    }
+
+    //! Hide mouse pointer.
+    CImgDisplay& hide_mouse() {
+      return assign();
+    }
+
+    //! Move mouse pointer to a specific location.
+    CImgDisplay& set_mouse(const int pos_x, const int pos_y) {
+      return assign(pos_x,pos_y);
+    }
+
+    //! Set the window title.
+    CImgDisplay& set_title(const char *const format, ...) {
+      return assign(0,0,format);
+    }
+
+    //! Render image buffer into GDI native image format.
+    template<typename T>
+    CImgDisplay& render(const CImg<T>& img) {
+      return assign(img);
+    }
+
+    //! Re-paint image content in window.
+    CImgDisplay& paint() {
+      return assign();
+    }
+
+    //! Take a snapshot of the display in the specified image.
+    template<typename T>
+    const CImgDisplay& snapshot(CImg<T>& img) const {
+      _no_display_exception();
+      return *this;
+    }
+#endif
+
+    //! Simulate a mouse button event.
+    CImgDisplay& set_button() {
+      _button = 0;
+      _is_event = true;
+      return *this;
+    }
+
+    CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) {
+      const unsigned int buttoncode = button==1?1:button==2?2:button==3?4:0;
+      if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode;
+      _is_event = buttoncode?true:false;
+      return *this;
+    }
+
+    //! Simulate a mouse wheel event, or flush wheel events.
+    CImgDisplay& set_wheel() {
+      _wheel = 0;
+      _is_event = true;
+      return *this;
+    }
+
+    CImgDisplay& set_wheel(const int amplitude) {
+      _wheel+=amplitude;
+      _is_event = amplitude?true:false;
+      return *this;
+    }
+
+    //! Simulate a keyboard press/release event, or flush all key events.
+    CImgDisplay& set_key() {
+      std::memset((void*)_keys,0,sizeof(_keys));
+      std::memset((void*)_released_keys,0,sizeof(_released_keys));
+      _is_keyESC = _is_keyF1 = _is_keyF2 = _is_keyF3 = _is_keyF4 = _is_keyF5 = _is_keyF6 = _is_keyF7 = _is_keyF8 = _is_keyF9 =
+        _is_keyF10 = _is_keyF11 = _is_keyF12 = _is_keyPAUSE = _is_key1 = _is_key2 = _is_key3 = _is_key4 = _is_key5 = _is_key6 =
+        _is_key7 = _is_key8 = _is_key9 = _is_key0 = _is_keyBACKSPACE = _is_keyINSERT = _is_keyHOME = _is_keyPAGEUP = _is_keyTAB =
+        _is_keyQ = _is_keyW = _is_keyE = _is_keyR = _is_keyT = _is_keyY = _is_keyU = _is_keyI = _is_keyO = _is_keyP = _is_keyDELETE =
+        _is_keyEND = _is_keyPAGEDOWN = _is_keyCAPSLOCK = _is_keyA = _is_keyS = _is_keyD = _is_keyF = _is_keyG = _is_keyH = _is_keyJ =
+        _is_keyK = _is_keyL = _is_keyENTER = _is_keySHIFTLEFT = _is_keyZ = _is_keyX = _is_keyC = _is_keyV = _is_keyB = _is_keyN =
+        _is_keyM = _is_keySHIFTRIGHT = _is_keyARROWUP = _is_keyCTRLLEFT = _is_keyAPPLEFT = _is_keyALT = _is_keySPACE = _is_keyALTGR = _is_keyAPPRIGHT =
+        _is_keyMENU = _is_keyCTRLRIGHT = _is_keyARROWLEFT = _is_keyARROWDOWN = _is_keyARROWRIGHT = _is_keyPAD0 = _is_keyPAD1 = _is_keyPAD2 =
+        _is_keyPAD3 = _is_keyPAD4 = _is_keyPAD5 = _is_keyPAD6 = _is_keyPAD7 = _is_keyPAD8 = _is_keyPAD9 = _is_keyPADADD = _is_keyPADSUB =
+        _is_keyPADMUL = _is_keyPADDIV = false;
+      _is_event = true;
+      return *this;
+    }
+
+    CImgDisplay& set_key(const unsigned int keycode, const bool pressed=true) {
+#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = pressed;
+      _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3);
+      _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7);
+      _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11);
+      _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2);
+      _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6);
+      _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0);
+      _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME);
+      _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W);
+      _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y);
+      _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P);
+      _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN);
+      _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D);
+      _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J);
+      _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER);
+      _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C);
+      _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M);
+      _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT);
+      _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR);
+      _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT);
+      _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT);
+      _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2);
+      _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5);
+      _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8);
+      _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB);
+      _cimg_set_key(PADMUL); _cimg_set_key(PADDIV);
+      if (pressed) {
+        if (*_keys)
+          std::memmove((void*)(_keys+1),(void*)_keys,sizeof(_keys) - sizeof(unsigned int));
+        *_keys = keycode;
+        if (*_released_keys) {
+          std::memmove((void*)(_released_keys+1),(void*)_released_keys,sizeof(_released_keys) - sizeof(unsigned int));
+          *_released_keys = 0;
+        }
+      } else {
+        if (*_keys) {
+          std::memmove((void*)(_keys+1),(void*)_keys,sizeof(_keys) - sizeof(unsigned int));
+          *_keys = 0;
+        }
+        if (*_released_keys)
+          std::memmove((void*)(_released_keys+1),(void*)_released_keys,sizeof(_released_keys) - sizeof(unsigned int));
+        *_released_keys = keycode;
+      }
+      _is_event = keycode?true:false;
+      return *this;
+    }
+
+    //! Flush all display events.
+    CImgDisplay& flush() {
+      set_key().set_button().set_wheel();
+      _is_resized = _is_moved = _is_event = false;
+      _fps_timer = _fps_frames = _timer = 0;
+      _fps_fps = 0;
+      return *this;
+    }
+
+    //! Synchronized waiting function. Same as cimg::wait().
+    CImgDisplay& wait(const unsigned int milliseconds) {
+      cimg::_sleep(milliseconds,_timer);
+      return *this;
+    }
+
+    //! Wait for an event occuring on the current display.
+    CImgDisplay& wait() {
+      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_closed && !disp1._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1 or \c disp2.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2) {
+      disp1._is_event = disp2._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed) &&
+             !disp1._is_event && !disp2._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) {
+      disp1._is_event = disp2._is_event = disp3._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4 or \c disp5.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp6.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp7.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp8.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = disp8._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed || !disp8._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event && !disp8._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp9.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event) wait_all();
+    }
+
+    //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3, \c disp4, ... \c disp10.
+    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4, CImgDisplay& disp5,
+                     CImgDisplay& disp6, CImgDisplay& disp7, CImgDisplay& disp8, CImgDisplay& disp9, CImgDisplay& disp10) {
+      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = disp5._is_event =
+        disp6._is_event = disp7._is_event = disp8._is_event = disp9._is_event = disp10._is_event = 0;
+      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+              !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) &&
+             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+             !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event) wait_all();
+    }
+
+#if cimg_display==0
+
+    //! Wait for a window event in any CImg window.
+    static void wait_all() {
+      return _no_display_exception();
+    }
+
+#endif
+
+    // X11-based implementation
+    //--------------------------
+#if cimg_display==1
+
+    Atom _wm_window_atom, _wm_protocol_atom;
+    Window _window, _background_window;
+    Colormap _colormap;
+    XImage *_image;
+    void *_data;
+#ifdef cimg_use_xshm
+    XShmSegmentInfo *_shminfo;
+#endif
+
+    static int screen_width() {
+      int res = 0;
+      if (!cimg::X11_attr().display) {
+        Display *disp = XOpenDisplay((std::getenv("DISPLAY")?std::getenv("DISPLAY"):":0.0"));
+        if (!disp)
+          throw CImgDisplayException("CImgDisplay::screen_width() : Failed to open X11 display.");
+        res = DisplayWidth(disp,DefaultScreen(disp));
+        XCloseDisplay(disp);
+      } else {
+#ifdef cimg_use_xrandr
+        if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution)
+          res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width;
+        else
+#endif
+          res = DisplayWidth(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display));
+      }
+      return res;
+    }
+
+    static int screen_height() {
+      int res = 0;
+      if (!cimg::X11_attr().display) {
+        Display *disp = XOpenDisplay((std::getenv("DISPLAY") ? std::getenv("DISPLAY") : ":0.0"));
+        if (!disp)
+          throw CImgDisplayException("CImgDisplay::screen_height() : Failed to open X11 display.");
+        res = DisplayHeight(disp,DefaultScreen(disp));
+        XCloseDisplay(disp);
+      } else {
+#ifdef cimg_use_xrandr
+        if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution)
+          res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height;
+        else
+#endif
+          res = DisplayHeight(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display));
+      }
+      return res;
+    }
+
+    static void wait_all() {
+      if (cimg::X11_attr().display) {
+        XLockDisplay(cimg::X11_attr().display);
+        bool flag = true;
+        XEvent event;
+        while (flag) {
+          XNextEvent(cimg::X11_attr().display, &event);
+          for (unsigned int i = 0; i<cimg::X11_attr().nb_wins; ++i)
+            if (!cimg::X11_attr().wins[i]->_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window) {
+              cimg::X11_attr().wins[i]->_handle_events(&event);
+              if (cimg::X11_attr().wins[i]->_is_event) flag = false;
+            }
+        }
+        XUnlockDisplay(cimg::X11_attr().display);
+      }
+    }
+
+    void _handle_events(const XEvent *const pevent) {
+      XEvent event = *pevent;
+      switch (event.type) {
+      case ClientMessage : {
+        if ((int)event.xclient.message_type==(int)_wm_protocol_atom &&
+            (int)event.xclient.data.l[0]==(int)_wm_window_atom) {
+          XUnmapWindow(cimg::X11_attr().display,_window);
+          _is_closed = _is_event = true;
+        }
+      } break;
+      case ConfigureNotify : {
+        while (XCheckWindowEvent(cimg::X11_attr().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::X11_attr().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::X11_attr().display,_window,ExposureMask,&event)) {}
+        _paint(false);
+        if (_is_fullscreen) {
+          XWindowAttributes attr;
+          XGetWindowAttributes(cimg::X11_attr().display,_window,&attr);
+          while (attr.map_state!=IsViewable) XSync(cimg::X11_attr().display,False);
+          XSetInputFocus(cimg::X11_attr().display,_window,RevertToParent,CurrentTime);
+        }
+      } break;
+      case ButtonPress : {
+        do {
+          _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y;
+          if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+          switch (event.xbutton.button) {
+          case 1 : set_button(1); break;
+          case 3 : set_button(2); break;
+          case 2 : set_button(3); break;
+          }
+        } while (XCheckWindowEvent(cimg::X11_attr().display,_window,ButtonPressMask,&event));
+      } break;
+      case ButtonRelease : {
+        do {
+          _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y;
+          if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+          switch (event.xbutton.button) {
+          case 1 : set_button(1,false); break;
+          case 3 : set_button(2,false); break;
+          case 2 : set_button(3,false); break;
+          case 4 : set_wheel(1); break;
+          case 5 : set_wheel(-1); break;
+          }
+        } while (XCheckWindowEvent(cimg::X11_attr().display,_window,ButtonReleaseMask,&event));
+      } break;
+      case KeyPress : {
+        char tmp = 0; KeySym ksym;
+        XLookupString(&event.xkey,&tmp,1,&ksym,0);
+        set_key((unsigned int)ksym,true);
+      } break;
+      case KeyRelease : {
+        char tmp = 0; KeySym ksym;
+        XLookupString(&event.xkey,&tmp,1,&ksym,0);
+        set_key((unsigned int)ksym,false);
+      } break;
+      case EnterNotify: {
+        while (XCheckWindowEvent(cimg::X11_attr().display,_window,EnterWindowMask,&event)) {}
+        _mouse_x = event.xmotion.x;
+        _mouse_y = event.xmotion.y;
+        if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+      } break;
+      case LeaveNotify : {
+        while (XCheckWindowEvent(cimg::X11_attr().display,_window,LeaveWindowMask,&event)) {}
+        _mouse_x = _mouse_y =-1; _is_event = true;
+      } break;
+      case MotionNotify : {
+        while (XCheckWindowEvent(cimg::X11_attr().display,_window,PointerMotionMask,&event)) {}
+        _mouse_x = event.xmotion.x;
+        _mouse_y = event.xmotion.y;
+        if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+        _is_event = true;
+      } break;
+      }
+    }
+
+    static void* _events_thread(void *arg) {
+      arg = 0;
+      XEvent event;
+      pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
+      pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
+      for (;;) {
+        XLockDisplay(cimg::X11_attr().display);
+        bool event_flag = XCheckTypedEvent(cimg::X11_attr().display,ClientMessage,&event);
+        if (!event_flag) event_flag = XCheckMaskEvent(cimg::X11_attr().display,
+                                                      ExposureMask | StructureNotifyMask | ButtonPressMask|
+                                                      KeyPressMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask|
+                                                      ButtonReleaseMask | KeyReleaseMask,&event);
+        if (event_flag) for (unsigned int i = 0; i<cimg::X11_attr().nb_wins; ++i)
+          if (!cimg::X11_attr().wins[i]->_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window)
+            cimg::X11_attr().wins[i]->_handle_events(&event);
+        XUnlockDisplay(cimg::X11_attr().display);
+        pthread_testcancel();
+        cimg::sleep(8);
+      }
+      return 0;
+    }
+
+    void _set_colormap(Colormap& colormap, const unsigned int dim) {
+      XColor palette[256];
+      switch (dim) {
+      case 1 : { // palette for greyscale images
+        for (unsigned int index = 0; index<256; ++index) {
+          palette[index].pixel = index;
+          palette[index].red = palette[index].green = palette[index].blue = (unsigned short)(index<<8);
+          palette[index].flags = DoRed | DoGreen | DoBlue;
+        }
+      } break;
+      case 2 : { // palette for RG images
+        for (unsigned int index = 0, r = 8; r<256; r+=16)
+          for (unsigned int g = 8; g<256; g+=16) {
+            palette[index].pixel = index;
+            palette[index].red = palette[index].blue = (unsigned short)(r<<8);
+            palette[index].green = (unsigned short)(g<<8);
+            palette[index++].flags = DoRed | DoGreen | DoBlue;
+          }
+      } break;
+      default : { // palette for RGB images
+        for (unsigned int index = 0, r = 16; r<256; r+=32)
+          for (unsigned int g = 16; g<256; g+=32)
+            for (unsigned int b = 32; b<256; b+=64) {
+              palette[index].pixel = index;
+              palette[index].red = (unsigned short)(r<<8);
+              palette[index].green = (unsigned short)(g<<8);
+              palette[index].blue = (unsigned short)(b<<8);
+              palette[index++].flags = DoRed | DoGreen | DoBlue;
+            }
+      }
+      }
+      XStoreColors(cimg::X11_attr().display,colormap,palette,256);
+    }
+
+    void _map_window() {
+      XWindowAttributes attr;
+      XEvent event;
+      bool exposed = false, mapped = false;
+      XMapRaised(cimg::X11_attr().display,_window);
+      XSync(cimg::X11_attr().display,False);
+      do {
+        XWindowEvent(cimg::X11_attr().display,_window,StructureNotifyMask | ExposureMask,&event);
+        switch (event.type) {
+        case MapNotify : mapped = true; break;
+        case Expose : exposed = true; break;
+        default : XSync(cimg::X11_attr().display, False); cimg::sleep(10);
+        }
+      } while (!(exposed && mapped));
+      do {
+        XGetWindowAttributes(cimg::X11_attr().display, _window, &attr);
+        if (attr.map_state!=IsViewable) { XSync(cimg::X11_attr().display,False); cimg::sleep(10); }
+      } while (attr.map_state != IsViewable);
+      _window_x = attr.x;
+      _window_y = attr.y;
+    }
+
+    void _paint(const bool wait_expose=true) {
+      if (!_is_closed) {
+        if (wait_expose) {
+          static XEvent event;
+          event.xexpose.type = Expose;
+          event.xexpose.serial = 0;
+          event.xexpose.send_event = True;
+          event.xexpose.display = cimg::X11_attr().display;
+          event.xexpose.window = _window;
+          event.xexpose.x = 0;
+          event.xexpose.y = 0;
+          event.xexpose.width = width();
+          event.xexpose.height = height();
+          event.xexpose.count = 0;
+          XSendEvent(cimg::X11_attr().display, _window, False, 0, &event);
+        } else {
+#ifdef cimg_use_xshm
+          if (_shminfo) XShmPutImage(cimg::X11_attr().display,_window,*cimg::X11_attr().gc,_image,0,0,0,0,_width,_height,False);
+          else
+#endif
+            XPutImage(cimg::X11_attr().display,_window,*cimg::X11_attr().gc,_image,0,0,0,0,_width,_height);
+          XSync(cimg::X11_attr().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 *const nshminfo = new XShmSegmentInfo;
+        XImage *const nimage = XShmCreateImage(cimg::X11_attr().display,DefaultVisual(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),
+                                               cimg::X11_attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy);
+        if (!nimage) { delete nshminfo; return; }
+        else {
+          nshminfo->shmid = shmget(IPC_PRIVATE,ndimx*ndimy*sizeof(T),IPC_CREAT | 0777);
+          if (nshminfo->shmid==-1) { XDestroyImage(nimage); delete nshminfo; return; }
+          else {
+            nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0);
+            if (nshminfo->shmaddr==(char*)-1) { shmctl(nshminfo->shmid,IPC_RMID,0); XDestroyImage(nimage); delete nshminfo; return; }
+            else {
+              nshminfo->readOnly = False;
+              cimg::X11_attr().is_shm_enabled = true;
+              XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+              XShmAttach(cimg::X11_attr().display, nshminfo);
+              XSync(cimg::X11_attr().display, False);
+              XSetErrorHandler(oldXErrorHandler);
+              if (!cimg::X11_attr().is_shm_enabled) {
+                shmdt(nshminfo->shmaddr);
+                shmctl(nshminfo->shmid,IPC_RMID,0);
+                XDestroyImage(nimage);
+                delete nshminfo;
+                return;
+              } else {
+                T *const ndata = (T*)nimage->data;
+                if (redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy);
+                else std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
+                XShmDetach(cimg::X11_attr().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::X11_attr().display,DefaultVisual(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),
+                                cimg::X11_attr().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::X11_attr().display,&foo,&foo)) {
+          XRRRotations(cimg::X11_attr().display, DefaultScreen(cimg::X11_attr().display), &cimg::X11_attr().curr_rotation);
+          if (!cimg::X11_attr().resolutions) {
+            cimg::X11_attr().resolutions = XRRSizes(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display),&foo);
+            cimg::X11_attr().nb_resolutions = (unsigned int)foo;
+          }
+          if (cimg::X11_attr().resolutions) {
+            cimg::X11_attr().curr_resolution = 0;
+            for (unsigned int i = 0; i<cimg::X11_attr().nb_resolutions; ++i) {
+              const unsigned int
+                nw = (unsigned int)(cimg::X11_attr().resolutions[i].width),
+                nh = (unsigned int)(cimg::X11_attr().resolutions[i].height);
+              if (nw>=_width && nh>=_height &&
+                  nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) &&
+                  nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height))
+                cimg::X11_attr().curr_resolution = i;
+            }
+            if (cimg::X11_attr().curr_resolution>0) {
+              XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11_attr().display, DefaultRootWindow(cimg::X11_attr().display));
+              XRRSetScreenConfig(cimg::X11_attr().display, config, DefaultRootWindow(cimg::X11_attr().display),
+                                 cimg::X11_attr().curr_resolution, cimg::X11_attr().curr_rotation, CurrentTime);
+              XRRFreeScreenConfigInfo(config);
+              XSync(cimg::X11_attr().display, False);
+            }
+          }
+        }
+        if (!cimg::X11_attr().resolutions)
+          cimg::warn(_cimgdisplay_instance
+                     "_create_window() : Xrandr extension is not supported by the X server.",
+                     cimgdisplay_instance);
+#endif
+        const unsigned int sx = screen_width(), sy = screen_height();
+        XSetWindowAttributes winattr;
+        winattr.override_redirect = True;
+        if (sx!=_width || sy!=_height) {
+          _background_window = XCreateWindow(cimg::X11_attr().display,
+                                             RootWindow(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),0,0,
+                                             sx,sy,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+          const unsigned int bufsize = sx*sy*(cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4));
+          void *background_data = std::malloc(bufsize);
+          std::memset(background_data,0,bufsize);
+          XImage *background_image = XCreateImage(cimg::X11_attr().display,DefaultVisual(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),
+                                                  cimg::X11_attr().nb_bits,ZPixmap,0,(char*)background_data,sx,sy,8,0);
+          XEvent event;
+          XSelectInput(cimg::X11_attr().display,_background_window,StructureNotifyMask);
+          XMapRaised(cimg::X11_attr().display,_background_window);
+          do XWindowEvent(cimg::X11_attr().display,_background_window,StructureNotifyMask,&event);
+          while (event.type!=MapNotify);
+#ifdef cimg_use_xshm
+          if (_shminfo) XShmPutImage(cimg::X11_attr().display,_background_window,*cimg::X11_attr().gc,background_image,0,0,0,0,sx,sy,False);
+          else
+#endif
+            XPutImage(cimg::X11_attr().display,_background_window,*cimg::X11_attr().gc,background_image,0,0,0,0,sx,sy);
+          XWindowAttributes attr;
+          XGetWindowAttributes(cimg::X11_attr().display, _background_window, &attr);
+          while (attr.map_state != IsViewable) XSync(cimg::X11_attr().display, False);
+          XDestroyImage(background_image);
+        }
+      }
+    }
+
+    void _desinit_fullscreen() {
+      if (_is_fullscreen) {
+        XUngrabKeyboard(cimg::X11_attr().display,CurrentTime);
+#ifdef cimg_use_xrandr
+        if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) {
+          XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11_attr().display, DefaultRootWindow(cimg::X11_attr().display));
+          XRRSetScreenConfig(cimg::X11_attr().display, config, DefaultRootWindow(cimg::X11_attr().display),
+                             0, cimg::X11_attr().curr_rotation, CurrentTime);
+          XRRFreeScreenConfigInfo(config);
+          XSync(cimg::X11_attr().display, False);
+          cimg::X11_attr().curr_resolution = 0;
+        }
+#endif
+        if (_background_window) XDestroyWindow(cimg::X11_attr().display,_background_window);
+        _background_window = 0;
+        _is_fullscreen = false;
+      }
+    }
+
+    static int _assign_xshm(Display *dpy, XErrorEvent *error) {
+      dpy = 0; error = 0;
+      cimg::X11_attr().is_shm_enabled = false;
+      return 0;
+    }
+
+    void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0,
+                 const unsigned int normalization_type=3,
+                 const bool fullscreen_flag=false, const bool closed_flag=false) {
+
+      // Allocate space for window title
+      const char *const nptitle = ptitle?ptitle:"";
+      const unsigned int s = std::strlen(nptitle) + 1;
+      char *const tmp_title = s?new char[s]:0;
+      if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char));
+
+      // Destroy previous display window if existing
+      if (!is_empty()) assign();
+
+      // Open X11 display if necessary.
+      if (!cimg::X11_attr().display) {
+        static bool xinit_threads = false;
+        if (!xinit_threads) { XInitThreads(); xinit_threads = true; }
+        cimg::X11_attr().nb_wins = 0;
+        cimg::X11_attr().display = XOpenDisplay((std::getenv("DISPLAY")?std::getenv("DISPLAY"):":0.0"));
+        if (!cimg::X11_attr().display)
+          throw CImgDisplayException(_cimgdisplay_instance
+                                     "assign() : Failed to open X11 display.",
+                                     cimgdisplay_instance);
+
+        cimg::X11_attr().nb_bits = DefaultDepth(cimg::X11_attr().display, DefaultScreen(cimg::X11_attr().display));
+        if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 && cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32)
+          throw CImgDisplayException(_cimgdisplay_instance
+                                     "assign() : Invalid %u bits screen mode detected "
+                                     "(only 8, 16, 24 and 32 bits modes are managed).",
+                                     cimgdisplay_instance,
+                                     cimg::X11_attr().nb_bits);
+
+        cimg::X11_attr().gc = new GC;
+        *cimg::X11_attr().gc = DefaultGC(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display));
+        Visual *visual = DefaultVisual(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display));
+        XVisualInfo vtemplate;
+        vtemplate.visualid = XVisualIDFromVisual(visual);
+        int nb_visuals;
+        XVisualInfo *vinfo = XGetVisualInfo(cimg::X11_attr().display,VisualIDMask,&vtemplate,&nb_visuals);
+        if (vinfo && vinfo->red_mask<vinfo->blue_mask) cimg::X11_attr().is_blue_first = true;
+        cimg::X11_attr().byte_order = ImageByteOrder(cimg::X11_attr().display);
+        XFree(vinfo);
+        XLockDisplay(cimg::X11_attr().display);
+        cimg::X11_attr().event_thread = new pthread_t;
+        pthread_create(cimg::X11_attr().event_thread,0,_events_thread,0);
+      } else XLockDisplay(cimg::X11_attr().display);
+
+      // Set display variables
+      _width = cimg::min(dimw,(unsigned int)screen_width());
+      _height = cimg::min(dimh,(unsigned int)screen_height());
+      _normalization = normalization_type<4?normalization_type:3;
+      _is_fullscreen = fullscreen_flag;
+      _window_x = _window_y = 0;
+      _is_closed = closed_flag;
+      _title = tmp_title;
+      flush();
+
+      // Create X11 window (and LUT, if 8bits display)
+      if (_is_fullscreen) {
+        if (!_is_closed) _init_fullscreen();
+        const unsigned int sx = screen_width(), sy = screen_height();
+        XSetWindowAttributes winattr;
+        winattr.override_redirect = True;
+        _window = XCreateWindow(cimg::X11_attr().display,
+                                RootWindow(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),
+                                (sx-_width)/2,(sy-_height)/2,
+                                _width,_height,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+      } else
+        _window = XCreateSimpleWindow(cimg::X11_attr().display,
+                                     RootWindow(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),
+                                     0,2,_width,_height,0,0,0x0L);
+      XStoreName(cimg::X11_attr().display,_window,_title?_title:" ");
+      if (cimg::X11_attr().nb_bits==8) {
+        _colormap = XCreateColormap(cimg::X11_attr().display,_window,DefaultVisual(cimg::X11_attr().display,
+                                                                                DefaultScreen(cimg::X11_attr().display)),AllocAll);
+        _set_colormap(_colormap,3);
+        XSetWindowColormap(cimg::X11_attr().display,_window,_colormap);
+      }
+      _window_width = _width;
+      _window_height = _height;
+
+      // Create XImage
+      const unsigned int bufsize = _width*_height*(cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4));
+#ifdef cimg_use_xshm
+      _shminfo = 0;
+      if (XShmQueryExtension(cimg::X11_attr().display)) {
+        _shminfo = new XShmSegmentInfo;
+        _image = XShmCreateImage(cimg::X11_attr().display,DefaultVisual(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),
+                                cimg::X11_attr().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::X11_attr().is_shm_enabled = true;
+              XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+              XShmAttach(cimg::X11_attr().display, _shminfo);
+              XSync(cimg::X11_attr().display, False);
+              XSetErrorHandler(oldXErrorHandler);
+              if (!cimg::X11_attr().is_shm_enabled) {
+                shmdt(_shminfo->shmaddr);
+                shmctl(_shminfo->shmid,IPC_RMID,0);
+                XDestroyImage(_image);
+                delete _shminfo;
+                _shminfo = 0;
+              }
+            }
+          }
+        }
+      }
+      if (!_shminfo)
+#endif
+        {
+          _data = std::malloc(bufsize);
+          _image = XCreateImage(cimg::X11_attr().display,DefaultVisual(cimg::X11_attr().display,DefaultScreen(cimg::X11_attr().display)),
+                               cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,_width,_height,8,0);
+        }
+
+      _wm_window_atom = XInternAtom(cimg::X11_attr().display,"WM_DELETE_WINDOW",False);
+      _wm_protocol_atom = XInternAtom(cimg::X11_attr().display,"WM_PROTOCOLS",False);
+      XSetWMProtocols(cimg::X11_attr().display,_window,&_wm_window_atom,1);
+      XSelectInput(cimg::X11_attr().display,_window,
+                   ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask |
+                   EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask);
+      if (_is_fullscreen) XGrabKeyboard(cimg::X11_attr().display, _window, True, GrabModeAsync, GrabModeAsync, CurrentTime);
+      cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this;
+      if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type<int>::min(); }
+      XUnlockDisplay(cimg::X11_attr().display);
+    }
+
+    CImgDisplay& assign() {
+      if (is_empty()) return *this;
+      XLockDisplay(cimg::X11_attr().display);
+
+      // Remove display window from event thread list.
+      unsigned int i;
+      for (i = 0; i<cimg::X11_attr().nb_wins && cimg::X11_attr().wins[i]!=this; ++i) {}
+      for (; i<cimg::X11_attr().nb_wins-1; ++i) cimg::X11_attr().wins[i] = cimg::X11_attr().wins[i+1];
+      --cimg::X11_attr().nb_wins;
+
+      // Destroy window, image, colormap and title.
+      if (_is_fullscreen && !_is_closed) _desinit_fullscreen();
+      XDestroyWindow(cimg::X11_attr().display,_window);
+      _window = 0;
+#ifdef cimg_use_xshm
+      if (_shminfo) {
+        XShmDetach(cimg::X11_attr().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::X11_attr().nb_bits==8) XFreeColormap(cimg::X11_attr().display,_colormap);
+      _colormap = 0;
+      XSync(cimg::X11_attr().display, False);
+
+      // Reset display variables
+      if (_title) delete[] _title;
+      _width = _height = _normalization = _window_width = _window_height = 0;
+      _window_x = _window_y = 0;
+      _is_fullscreen = false;
+      _is_closed = true;
+      _min = _max = 0;
+      _title = 0;
+      flush();
+
+      // End event thread and close display if necessary
+      XUnlockDisplay(cimg::X11_attr().display);
+      if (!cimg::X11_attr().nb_wins) {
+        // Kill event thread
+        //pthread_cancel(*cimg::X11_attr().event_thread);
+        //XUnlockDisplay(cimg::X11_attr().display);
+        //pthread_join(*cimg::X11_attr().event_thread,0);
+        //delete cimg::X11_attr().event_thread;
+        //cimg::X11_attr().event_thread = 0;
+        // XUnlockDisplay(cimg::X11_attr().display); // <- This call make the library hang sometimes (fix required).
+        // XCloseDisplay(cimg::X11_attr().display); // <- This call make the library hang sometimes (fix required).
+        //cimg::X11_attr().display = 0;
+        //delete cimg::X11_attr().gc;
+        //cimg::X11_attr().gc = 0;
+      }
+      return *this;
+    }
+
+    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0,
+                        const unsigned int normalization_type=3,
+                        const bool fullscreen_flag=false, const bool closed_flag=false) {
+      if (!dimw || !dimh) return assign();
+      _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
+      _min = _max = 0;
+      std::memset(_data,0,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char):
+                          (cimg::X11_attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))*_width*_height);
+      return paint();
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImg<T>& img, const char *const title=0,
+                        const unsigned int normalization_type=3,
+                        const bool fullscreen_flag=false, const bool closed_flag=false) {
+      if (!img) return assign();
+      CImg<T> tmp;
+      const CImg<T>& nimg = (img._depth==1)?img:(tmp=img.get_projections2d(img._width/2,img._height/2,img._depth/2));
+      _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag);
+      if (_normalization==2) _min = (float)nimg.min_max(_max);
+      return render(nimg).paint();
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImgList<T>& list, const char *const title=0,
+                        const unsigned int normalization_type=3,
+                        const bool fullscreen_flag=false, const bool closed_flag=false) {
+      if (!list) return assign();
+      CImg<T> tmp;
+      const CImg<T> img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d(img._width/2,img._height/2,img._depth/2));
+      _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag);
+      if (_normalization==2) _min = (float)nimg.min_max(_max);
+      return render(nimg).paint();
+    }
+
+    CImgDisplay& assign(const CImgDisplay& disp) {
+      if (!disp) return assign();
+      _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed);
+      std::memcpy(_data,disp._data,(cimg::X11_attr().nb_bits==8?sizeof(unsigned char):
+                                  cimg::X11_attr().nb_bits==16?sizeof(unsigned short):
+                                  sizeof(unsigned int))*_width*_height);
+      return paint();
+    }
+
+    CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
+      if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+      if (is_empty()) return assign(nwidth,nheight);
+      const unsigned int
+        tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100),
+        tmpdimy = (nheight>0)?nheight:(-nheight*_height/100),
+        dimx = tmpdimx?tmpdimx:1,
+        dimy = tmpdimy?tmpdimy:1;
+      XLockDisplay(cimg::X11_attr().display);
+      if (_window_width!=dimx || _window_height!=dimy) XResizeWindow(cimg::X11_attr().display,_window,dimx,dimy);
+      if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) {
+      case 8 :  { unsigned char foo = 0; _resize(foo,dimx,dimy,redraw); } break;
+      case 16 : { unsigned short foo = 0; _resize(foo,dimx,dimy,redraw); } break;
+      default : { unsigned int foo = 0; _resize(foo,dimx,dimy,redraw); }
+      }
+      _window_width = _width = dimx; _window_height = _height = dimy;
+      _is_resized = false;
+      XUnlockDisplay(cimg::X11_attr().display);
+      if (_is_fullscreen) move((screen_width()-_width)/2,(screen_height()-_height)/2);
+      if (redraw) return paint();
+      return *this;
+    }
+
+    CImgDisplay& toggle_fullscreen(const bool redraw=true) {
+      if (is_empty()) return *this;
+      if (redraw) {
+        const unsigned int bufsize = _width*_height*(cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4));
+        void *odata = std::malloc(bufsize);
+        std::memcpy(odata,_data,bufsize);
+        assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+        std::memcpy(_data,odata,bufsize);
+        std::free(odata);
+        return paint(false);
+      }
+      return assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+    }
+
+    CImgDisplay& show() {
+      if (!is_empty() && _is_closed) {
+        XLockDisplay(cimg::X11_attr().display);
+        if (_is_fullscreen) _init_fullscreen();
+        _map_window();
+        _is_closed = false;
+        XUnlockDisplay(cimg::X11_attr().display);
+        return paint();
+      }
+      return *this;
+    }
+
+    CImgDisplay& close() {
+      if (!is_empty() && !_is_closed) {
+        XLockDisplay(cimg::X11_attr().display);
+        if (_is_fullscreen) _desinit_fullscreen();
+        XUnmapWindow(cimg::X11_attr().display,_window);
+        _window_x = _window_y = -1;
+        _is_closed = true;
+        XUnlockDisplay(cimg::X11_attr().display);
+      }
+      return *this;
+    }
+
+    CImgDisplay& move(const int posx, const int posy) {
+      if (is_empty()) return *this;
+      show();
+      XLockDisplay(cimg::X11_attr().display);
+      XMoveWindow(cimg::X11_attr().display,_window,posx,posy);
+      _window_x = posx; _window_y = posy;
+      _is_moved = false;
+      XUnlockDisplay(cimg::X11_attr().display);
+      return paint();
+    }
+
+    CImgDisplay& show_mouse() {
+      if (is_empty()) return *this;
+      XLockDisplay(cimg::X11_attr().display);
+      XUndefineCursor(cimg::X11_attr().display,_window);
+      XUnlockDisplay(cimg::X11_attr().display);
+      return *this;
+    }
+
+    CImgDisplay& hide_mouse() {
+      if (is_empty()) return *this;
+      XLockDisplay(cimg::X11_attr().display);
+      const char pix_data[8] = { 0 };
+      XColor col;
+      col.red = col.green = col.blue = 0;
+      Pixmap pix = XCreateBitmapFromData(cimg::X11_attr().display,_window,pix_data,8,8);
+      Cursor cur = XCreatePixmapCursor(cimg::X11_attr().display,pix,pix,&col,&col,0,0);
+      XFreePixmap(cimg::X11_attr().display,pix);
+      XDefineCursor(cimg::X11_attr().display,_window,cur);
+      XUnlockDisplay(cimg::X11_attr().display);
+      return *this;
+    }
+
+    CImgDisplay& set_mouse(const int posx, const int posy) {
+      if (is_empty() || _is_closed) return *this;
+      XLockDisplay(cimg::X11_attr().display);
+      XWarpPointer(cimg::X11_attr().display,0L,_window,0,0,0,0,posx,posy);
+      _mouse_x = posx; _mouse_y = posy;
+      _is_moved = false;
+      XSync(cimg::X11_attr().display, False);
+      XUnlockDisplay(cimg::X11_attr().display);
+      return *this;
+    }
+
+    CImgDisplay& set_title(const char *const format, ...) {
+      if (is_empty()) return *this;
+      char tmp[1024] = { 0 };
+      va_list ap;
+      va_start(ap, format);
+      cimg_vsnprintf(tmp,sizeof(tmp),format,ap);
+      va_end(ap);
+      if (std::strcmp(_title,tmp)) {
+        if (_title) delete[] _title;
+        const unsigned int s = std::strlen(tmp) + 1;
+        _title = new char[s];
+        std::memcpy(_title,tmp,s*sizeof(char));
+        XLockDisplay(cimg::X11_attr().display);
+        XStoreName(cimg::X11_attr().display,_window,tmp);
+        XUnlockDisplay(cimg::X11_attr().display);
+      }
+      return *this;
+    }
+
+    template<typename T>
+    CImgDisplay& display(const CImg<T>& img) {
+      if (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "display() : Empty specified image.",
+                                    cimgdisplay_instance);
+
+      if (is_empty()) assign(img._width,img._height);
+      return render(img).paint(false);
+    }
+
+    CImgDisplay& paint(const bool wait_expose=true) {
+      if (is_empty()) return *this;
+      XLockDisplay(cimg::X11_attr().display);
+      _paint(wait_expose);
+      XUnlockDisplay(cimg::X11_attr().display);
+      return *this;
+    }
+
+    template<typename T>
+    CImgDisplay& render(const CImg<T>& img, const bool flag8=false) {
+      if (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "render() : Empty specified image.",
+                                    cimgdisplay_instance);
+
+      if (is_empty()) return *this;
+      if (img._depth!=1) return render(img.get_projections2d(img._width/2,img._height/2,img._depth/2));
+      if (cimg::X11_attr().nb_bits==8 && (img._width!=_width || img._height!=_height)) return render(img.get_resize(_width,_height,1,-100,1));
+      if (cimg::X11_attr().nb_bits==8 && !flag8 && img._spectrum==3) {
+        static const CImg<typename CImg<T>::ucharT> default_palette = CImg<typename CImg<T>::ucharT>::default_LUT256();
+        return render(img.get_index(default_palette,true,false));
+      }
+
+      const T
+        *data1 = img._data,
+        *data2 = (img._spectrum>1)?img.data(0,0,0,1):data1,
+        *data3 = (img._spectrum>2)?img.data(0,0,0,2):data1;
+
+      if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3);
+      XLockDisplay(cimg::X11_attr().display);
+
+      if (!_normalization || (_normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
+        _min = _max = 0;
+        switch (cimg::X11_attr().nb_bits) {
+        case 8 : { // 256 color palette, no normalization
+          _set_colormap(_colormap,img._spectrum);
+          unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data:new unsigned char[img._width*img._height];
+          unsigned char *ptrd = (unsigned char*)ndata;
+          switch (img._spectrum) {
+          case 1 : for (unsigned int xy = img._width*img._height; xy>0; --xy) (*ptrd++) = (unsigned char)*(data1++);
+            break;
+          case 2 : for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++);
+              (*ptrd++) = (R&0xf0) | (G>>4);
+            } break;
+          default : for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++), B = (unsigned char)*(data3++);
+              (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
+            }
+          }
+          if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); delete[] ndata; }
+        } break;
+        case 16 : { // 16 bits colors, no normalization
+          unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data:new unsigned short[img._width*img._height];
+          unsigned char *ptrd = (unsigned char*)ndata;
+          const unsigned int M = 248;
+          switch (img._spectrum) {
+          case 1 :
+            if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char val = (unsigned char)*(data1++), G = val>>2;
+              *(ptrd++) = (val&M) | (G>>3);
+              *(ptrd++) = (G<<5) | (G>>1);
+            } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char val = (unsigned char)*(data1++), G = val>>2;
+              *(ptrd++) = (G<<5) | (G>>1);
+              *(ptrd++) = (val&M) | (G>>3);
+            }
+            break;
+          case 2 :
+            if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)*(data2++)>>2;
+              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+              *(ptrd++) = (G<<5);
+            } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)*(data2++)>>2;
+              *(ptrd++) = (G<<5);
+              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+            }
+            break;
+          default :
+            if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)*(data2++)>>2;
+              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+              *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
+            } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)*(data2++)>>2;
+              *(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
+              *(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
+            }
+          }
+          if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); delete[] ndata; }
+        } break;
+        default : { // 24 bits colors, no normalization
+          unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data:new unsigned int[img._width*img._height];
+          if (sizeof(int)==4) { // 32 bits int uses optimized version
+            unsigned int *ptrd = ndata;
+            switch (img._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+                for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                  const unsigned char val = (unsigned char)*(data1++);
+                  *(ptrd++) = (val<<16) | (val<<8) | val;
+                }
+              else
+               for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                  const unsigned char val = (unsigned char)*(data1++)<<8;
+                  *(ptrd++) = (val<<16) | (val<<8) | val;
+                }
+              break;
+            case 2 :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+               for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
+              else
+               for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
+              break;
+            default :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+               for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
+              else
+               for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
+            }
+          } else {
+            unsigned char *ptrd = (unsigned char*)ndata;
+            switch (img._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                *(ptrd++) = 0;
+                *(ptrd++) = (unsigned char)*(data1++);
+                *(ptrd++) = 0;
+                *(ptrd++) = 0;
+              } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                *(ptrd++) = 0;
+                *(ptrd++) = 0;
+                *(ptrd++) = (unsigned char)*(data1++);
+                *(ptrd++) = 0;
+              }
+              break;
+            case 2 :
+              if (cimg::X11_attr().byte_order) cimg::swap(data1,data2);
+              for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                *(ptrd++) = 0;
+                *(ptrd++) = (unsigned char)*(data2++);
+                *(ptrd++) = (unsigned char)*(data1++);
+                *(ptrd++) = 0;
+              }
+              break;
+            default :
+              if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                *(ptrd++) = 0;
+                *(ptrd++) = (unsigned char)*(data1++);
+                *(ptrd++) = (unsigned char)*(data2++);
+                *(ptrd++) = (unsigned char)*(data3++);
+              } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                *(ptrd++) = (unsigned char)*(data3++);
+                *(ptrd++) = (unsigned char)*(data2++);
+                *(ptrd++) = (unsigned char)*(data1++);
+                *(ptrd++) = 0;
+              }
+            }
+          }
+          if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); delete[] ndata; }
+        }
+        }
+      } else {
+        if (_normalization==3) {
+          if (cimg::type<T>::is_float()) _min = (float)img.min_max(_max);
+          else { _min = (float)cimg::type<T>::min(); _max = (float)cimg::type<T>::max(); }
+        } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max);
+        const float delta = _max-_min, mm = delta?delta:1.0f;
+        switch (cimg::X11_attr().nb_bits) {
+        case 8 : { // 256 color palette, with normalization
+          _set_colormap(_colormap,img._spectrum);
+          unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data:new unsigned char[img._width*img._height];
+          unsigned char *ptrd = (unsigned char*)ndata;
+          switch (img._spectrum) {
+          case 1 : for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            const unsigned char R = (unsigned char)(255*(*(data1++)-_min)/mm);
+            *(ptrd++) = R;
+          } break;
+          case 2 : for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            const unsigned char
+              R = (unsigned char)(255*(*(data1++)-_min)/mm),
+              G = (unsigned char)(255*(*(data2++)-_min)/mm);
+            (*ptrd++) = (R&0xf0) | (G>>4);
+          } break;
+          default :
+            for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char
+                R = (unsigned char)(255*(*(data1++)-_min)/mm),
+                G = (unsigned char)(255*(*(data2++)-_min)/mm),
+                B = (unsigned char)(255*(*(data3++)-_min)/mm);
+              *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
+            }
+          }
+          if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); delete[] ndata; }
+        } break;
+        case 16 : { // 16 bits colors, with normalization
+          unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data:new unsigned short[img._width*img._height];
+          unsigned char *ptrd = (unsigned char*)ndata;
+          const unsigned int M = 248;
+          switch (img._spectrum) {
+          case 1 :
+            if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char val = (unsigned char)(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::X11_attr().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::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)(255*(*(data2++)-_min)/mm)>>2;
+              *(ptrd++) = ((unsigned char)(255*(*(data1++)-_min)/mm)&M) | (G>>3);
+              *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-_min)/mm)>>3);
+            } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+              const unsigned char G = (unsigned char)(255*(*(data2++)-_min)/mm)>>2;
+              *(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-_min)/mm)>>3);
+              *(ptrd++) = ((unsigned char)(255*(*(data1++)-_min)/mm)&M) | (G>>3);
+            }
+          }
+          if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned short*)_data,_width,_height); delete[] ndata; }
+        } break;
+        default : { // 24 bits colors, with normalization
+          unsigned int *const ndata = (img._width==_width && img._height==_height)?(unsigned int*)_data:new unsigned int[img._width*img._height];
+          if (sizeof(int)==4) { // 32 bits int uses optimized version
+            unsigned int *ptrd = ndata;
+            switch (img._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+                for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                  const unsigned char val = (unsigned char)(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::X11_attr().byte_order==cimg::endianness())
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)(255*(*(data1++)-_min)/mm)<<16) |
+                    ((unsigned char)(255*(*(data2++)-_min)/mm)<<8);
+              else
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)(255*(*(data2++)-_min)/mm)<<16) |
+                    ((unsigned char)(255*(*(data1++)-_min)/mm)<<8);
+              break;
+            default :
+              if (cimg::X11_attr().byte_order==cimg::endianness())
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)(255*(*(data1++)-_min)/mm)<<16) |
+                    ((unsigned char)(255*(*(data2++)-_min)/mm)<<8) |
+                    (unsigned char)(255*(*(data3++)-_min)/mm);
+              else
+                for (unsigned int xy = img._width*img._height; xy>0; --xy)
+                  *(ptrd++) =
+                    ((unsigned char)(255*(*(data3++)-_min)/mm)<<24) |
+                    ((unsigned char)(255*(*(data2++)-_min)/mm)<<16) |
+                    ((unsigned char)(255*(*(data1++)-_min)/mm)<<8);
+            }
+          } else {
+            unsigned char *ptrd = (unsigned char*)ndata;
+            switch (img._spectrum) {
+            case 1 :
+              if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                const unsigned char val = (unsigned char)(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::X11_attr().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::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                (*ptrd++) = 0;
+                (*ptrd++) = (unsigned char)(255*(*(data1++)-_min)/mm);
+                (*ptrd++) = (unsigned char)(255*(*(data2++)-_min)/mm);
+                (*ptrd++) = (unsigned char)(255*(*(data3++)-_min)/mm);
+              } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+                (*ptrd++) = (unsigned char)(255*(*(data3++)-_min)/mm);
+                (*ptrd++) = (unsigned char)(255*(*(data2++)-_min)/mm);
+                (*ptrd++) = (unsigned char)(255*(*(data1++)-_min)/mm);
+                (*ptrd++) = 0;
+              }
+            }
+          }
+          if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); delete[] ndata; }
+        }
+        }
+      }
+      XUnlockDisplay(cimg::X11_attr().display);
+      return *this;
+    }
+
+    template<typename T>
+    const CImgDisplay& snapshot(CImg<T>& img) const {
+      if (is_empty()) { img.assign(); return *this; }
+      const unsigned char *ptrs = (unsigned char*)_data;
+      img.assign(_width,_height,1,3);
+      T
+        *data1 = img.data(0,0,0,0),
+        *data2 = img.data(0,0,0,1),
+        *data3 = img.data(0,0,0,2);
+      if (cimg::X11_attr().is_blue_first) cimg::swap(data1,data3);
+      switch (cimg::X11_attr().nb_bits) {
+      case 8 : {
+        for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          const unsigned char val = *(ptrs++);
+          *(data1++) = val&0xe0;
+          *(data2++) = (val&0x1c)<<3;
+          *(data3++) = val<<6;
+        }
+      } break;
+      case 16 : {
+        if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          const unsigned char val0 = *(ptrs++), val1 = *(ptrs++);
+          *(data1++) = 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 : {
+        if (cimg::X11_attr().byte_order) for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          ++ptrs;
+          *(data1++) = *(ptrs++);
+          *(data2++) = *(ptrs++);
+          *(data3++) = *(ptrs++);
+        } else for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+          *(data3++) = *(ptrs++);
+          *(data2++) = *(ptrs++);
+          *(data1++) = *(ptrs++);
+          ++ptrs;
+        }
+      }
+      }
+      return *this;
+    }
+
+    // Windows-based implementation.
+    //-------------------------------
+#elif cimg_display==2
+
+    CLIENTCREATESTRUCT _ccs;
+    BITMAPINFO _bmi;
+    unsigned int *_data;
+    DEVMODE _curr_mode;
+    HWND _window;
+    HWND _background_window;
+    HDC _hdc;
+    HANDLE _thread;
+    HANDLE _is_created;
+    HANDLE _mutex;
+    bool _is_mouse_tracked;
+    bool _is_cursor_visible;
+
+    static int screen_width() {
+      DEVMODE mode;
+      mode.dmSize = sizeof(DEVMODE);
+      mode.dmDriverExtra = 0;
+      EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+      return mode.dmPelsWidth;
+    }
+
+    static int screen_height() {
+      DEVMODE mode;
+      mode.dmSize = sizeof(DEVMODE);
+      mode.dmDriverExtra = 0;
+      EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+      return mode.dmPelsHeight;
+    }
+
+    static void wait_all() {
+      WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE);
+    }
+
+    static LRESULT APIENTRY _handle_events(HWND window,UINT msg,WPARAM wParam,LPARAM lParam) {
+#ifdef _WIN64
+      CImgDisplay *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;
+        disp->set_button().set_key(0).set_key(0,false)._is_closed = true;
+        ReleaseMutex(disp->_mutex);
+        ShowWindow(disp->_window,SW_HIDE);
+        disp->_is_event = true;
+        SetEvent(cimg::Win32_attr().wait_event);
+        return 0;
+      case WM_SIZE : {
+        while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
+        WaitForSingleObject(disp->_mutex,INFINITE);
+        const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam);
+        if (nw && nh && (nw!=disp->_width || nh!=disp->_height)) {
+          disp->_window_width = nw;
+          disp->_window_height = nh;
+          disp->_mouse_x = disp->_mouse_y = -1;
+          disp->_is_resized = disp->_is_event = true;
+          SetEvent(cimg::Win32_attr().wait_event);
+        }
+        ReleaseMutex(disp->_mutex);
+      } break;
+      case WM_MOVE : {
+        while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
+        WaitForSingleObject(disp->_mutex,INFINITE);
+        const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam));
+        if (nx!=disp->_window_x || ny!=disp->_window_y) {
+          disp->_window_x = nx;
+          disp->_window_y = ny;
+          disp->_is_moved = disp->_is_event = true;
+          SetEvent(cimg::Win32_attr().wait_event);
+        }
+        ReleaseMutex(disp->_mutex);
+      } break;
+      case WM_PAINT :
+        disp->paint();
+        break;
+      case WM_KEYDOWN :
+        disp->set_key((unsigned int)wParam);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_KEYUP :
+        disp->set_key((unsigned int)wParam,false);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_MOUSEMOVE : {
+        while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {}
+        disp->_mouse_x = LOWORD(lParam);
+        disp->_mouse_y = HIWORD(lParam);
+#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT)
+        if (!disp->_is_mouse_tracked) {
+          TRACKMOUSEEVENT tme;
+          tme.cbSize = sizeof(TRACKMOUSEEVENT);
+          tme.dwFlags = TME_LEAVE;
+          tme.hwndTrack = disp->_window;
+          if (TrackMouseEvent(&tme)) disp->_is_mouse_tracked = true;
+        }
+#endif
+        if (disp->_mouse_x<0 || disp->_mouse_y<0 || disp->_mouse_x>=disp->width() || disp->_mouse_y>=disp->height())
+          disp->_mouse_x = disp->_mouse_y = -1;
+        disp->_is_event = true;
+        SetEvent(cimg::Win32_attr().wait_event);
+      } break;
+      case WM_MOUSELEAVE : {
+        disp->_mouse_x = disp->_mouse_y = -1;
+        disp->_is_mouse_tracked = false;
+      } break;
+      case WM_LBUTTONDOWN :
+        disp->set_button(1);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_RBUTTONDOWN :
+        disp->set_button(2);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_MBUTTONDOWN :
+        disp->set_button(3);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_LBUTTONUP :
+        disp->set_button(1,false);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_RBUTTONUP :
+        disp->set_button(2,false);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case WM_MBUTTONUP :
+        disp->set_button(3,false);
+        SetEvent(cimg::Win32_attr().wait_event);
+        break;
+      case 0x020A : // WM_MOUSEWHEEL:
+        disp->set_wheel((int)((short)HIWORD(wParam))/120);
+        SetEvent(cimg::Win32_attr().wait_event);
+      case WM_SETCURSOR :
+        if (disp->_is_cursor_visible) ShowCursor(TRUE);
+        else ShowCursor(FALSE);
+        break;
+      }
+      return DefWindowProc(window,msg,wParam,lParam);
+    }
+
+    static DWORD WINAPI _events_thread(void* arg) {
+      CImgDisplay *disp = (CImgDisplay*)(((void**)arg)[0]);
+      const char *const title = (const char*)(((void**)arg)[1]);
+      MSG msg;
+      delete[] (void**)arg;
+      disp->_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+      disp->_bmi.bmiHeader.biWidth = disp->width();
+      disp->_bmi.bmiHeader.biHeight = -disp->height();
+      disp->_bmi.bmiHeader.biPlanes = 1;
+      disp->_bmi.bmiHeader.biBitCount = 32;
+      disp->_bmi.bmiHeader.biCompression = BI_RGB;
+      disp->_bmi.bmiHeader.biSizeImage = 0;
+      disp->_bmi.bmiHeader.biXPelsPerMeter = 1;
+      disp->_bmi.bmiHeader.biYPelsPerMeter = 1;
+      disp->_bmi.bmiHeader.biClrUsed = 0;
+      disp->_bmi.bmiHeader.biClrImportant = 0;
+      disp->_data = new unsigned int[disp->_width*disp->_height];
+      if (!disp->_is_fullscreen) { // Normal window
+        RECT rect;
+        rect.left = rect.top = 0; rect.right = disp->_width-1; rect.bottom = disp->_height-1;
+        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+        const int
+          border1 = (rect.right - rect.left + 1 - disp->_width)/2,
+          border2 = rect.bottom - rect.top + 1 - disp->_height - border1;
+        disp->_window = CreateWindowA("MDICLIENT",title?title:" ",
+                                     WS_OVERLAPPEDWINDOW | (disp->_is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT,
+                                     disp->_width + 2*border1, disp->_height + border1 + border2,
+                                     0,0,0,&(disp->_ccs));
+        if (!disp->_is_closed) {
+          GetWindowRect(disp->_window,&rect);
+          disp->_window_x = rect.left + border1;
+          disp->_window_y = rect.top + border2;
+        } else disp->_window_x = disp->_window_y = 0;
+      } else { // Fullscreen window
+        const unsigned int sx = screen_width(), sy = screen_height();
+        disp->_window = CreateWindowA("MDICLIENT",title?title:" ",
+                                     WS_POPUP | (disp->_is_closed?0:WS_VISIBLE), (sx-disp->_width)/2, (sy-disp->_height)/2,
+                                     disp->_width,disp->_height,0,0,0,&(disp->_ccs));
+        disp->_window_x = disp->_window_y = 0;
+      }
+      SetForegroundWindow(disp->_window);
+      disp->_hdc = GetDC(disp->_window);
+      disp->_window_width = disp->_width;
+      disp->_window_height = disp->_height;
+      disp->flush();
+#ifdef _WIN64
+      SetWindowLongPtr(disp->_window,GWLP_USERDATA,(LONG_PTR)disp);
+      SetWindowLongPtr(disp->_window,GWLP_WNDPROC,(LONG_PTR)_handle_events);
+#else
+      SetWindowLong(disp->_window,GWL_USERDATA,(LONG)disp);
+      SetWindowLong(disp->_window,GWL_WNDPROC,(LONG)_handle_events);
+#endif
+      SetEvent(disp->_is_created);
+      while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg);
+      return 0;
+    }
+
+    CImgDisplay& _update_window_pos() {
+      if (!_is_closed) {
+        RECT rect;
+        rect.left = rect.top = 0; rect.right = _width-1; rect.bottom = _height-1;
+        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+        const int
+          border1 = (rect.right - rect.left + 1 - _width)/2,
+          border2 = rect.bottom - rect.top + 1 - _height - border1;
+        GetWindowRect(_window,&rect);
+        _window_x = rect.left + border1;
+        _window_y = rect.top + border2;
+      } else _window_x = _window_y = -1;
+      return *this;
+    }
+
+    void _init_fullscreen() {
+      _background_window = 0;
+      if (_is_fullscreen && !_is_closed) {
+        DEVMODE mode;
+        unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U;
+        for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) {
+          const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight;
+          if (nw>=_width && nh>=_height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) {
+            bestbpp = mode.dmBitsPerPel;
+            ibest = imode;
+            bw = nw; bh = nh;
+          }
+        }
+        if (bestbpp) {
+          _curr_mode.dmSize = sizeof(DEVMODE); _curr_mode.dmDriverExtra = 0;
+          EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&_curr_mode);
+          EnumDisplaySettings(0,ibest,&mode);
+          ChangeDisplaySettings(&mode,0);
+        } else _curr_mode.dmSize = 0;
+
+        const unsigned int sx = screen_width(), sy = screen_height();
+        if (sx!=_width || sy!=_height) {
+          CLIENTCREATESTRUCT background_ccs;
+          _background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs);
+          SetForegroundWindow(_background_window);
+        }
+      } else _curr_mode.dmSize = 0;
+    }
+
+    void _desinit_fullscreen() {
+      if (_is_fullscreen) {
+        if (_background_window) DestroyWindow(_background_window);
+        _background_window = 0;
+        if (_curr_mode.dmSize) ChangeDisplaySettings(&_curr_mode,0);
+        _is_fullscreen = false;
+      }
+    }
+
+    CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0,
+                         const unsigned int normalization_type=3,
+                         const bool fullscreen_flag=false, const bool closed_flag=false) {
+
+      // Allocate space for window title
+      const char *const nptitle = ptitle?ptitle:"";
+      const unsigned int s = std::strlen(nptitle) + 1;
+      char *const tmp_title = s?new char[s]:0;
+      if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char));
+
+      // Destroy previous window if existing
+      if (!is_empty()) assign();
+
+      // Set display variables
+      _width = cimg::min(dimw,(unsigned int)screen_width());
+      _height = cimg::min(dimh,(unsigned int)screen_height());
+      _normalization = normalization_type<4?normalization_type:3;
+      _is_fullscreen = fullscreen_flag;
+      _window_x = _window_y = 0;
+      _is_closed = closed_flag;
+      _is_cursor_visible = true;
+      _is_mouse_tracked = false;
+      _title = tmp_title;
+      flush();
+      if (_is_fullscreen) _init_fullscreen();
+
+      // Create event thread
+      void *const arg = (void*)(new void*[2]);
+      ((void**)arg)[0] = (void*)this;
+      ((void**)arg)[1] = (void*)_title;
+      unsigned long ThreadID = 0;
+      _mutex = CreateMutex(0,FALSE,0);
+      _is_created = CreateEvent(0,FALSE,FALSE,0);
+      _thread = CreateThread(0,0,_events_thread,arg,0,&ThreadID);
+      WaitForSingleObject(_is_created,INFINITE);
+      return *this;
+    }
+
+    CImgDisplay& assign() {
+      if (is_empty()) return *this;
+      DestroyWindow(_window);
+      TerminateThread(_thread,0);
+      if (_data) delete[] _data;
+      if (_title) delete[] _title;
+      if (_is_fullscreen) _desinit_fullscreen();
+      _width = _height = _normalization = _window_width = _window_height = 0;
+      _window_x = _window_y = 0;
+      _is_fullscreen = false;
+      _is_closed = true;
+      _min = _max = 0;
+      _title = 0;
+      flush();
+      return *this;
+    }
+
+    CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0,
+                        const unsigned int normalization_type=3,
+                        const bool fullscreen_flag=false, const bool closed_flag=false) {
+      if (!dimw || !dimh) return assign();
+      _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
+      _min = _max = 0;
+      std::memset(_data,0,sizeof(unsigned int)*_width*_height);
+      return paint();
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImg<T>& img, const char *const title=0,
+                        const unsigned int normalization_type=3,
+                        const bool fullscreen_flag=false, const bool closed_flag=false) {
+      if (!img) return assign();
+      CImg<T> tmp;
+      const CImg<T>& nimg = (img._depth==1)?img:(tmp=img.get_projections2d(img._width/2,img._height/2,img._depth/2));
+      _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag);
+      if (_normalization==2) _min = (float)nimg.min_max(_max);
+      return display(nimg);
+    }
+
+    template<typename T>
+    CImgDisplay& assign(const CImgList<T>& list, const char *const title=0,
+                        const unsigned int normalization_type=3,
+                        const bool fullscreen_flag=false, const bool closed_flag=false) {
+      if (!list) return assign();
+      CImg<T> tmp;
+      const CImg<T> img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d(img._width/2,img._height/2,img._depth/2));
+      _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag);
+      if (_normalization==2) _min = (float)nimg.min_max(_max);
+      return display(nimg);
+    }
+
+    CImgDisplay& assign(const CImgDisplay& disp) {
+      if (!disp) return assign();
+      _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed);
+      std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height);
+      return paint();
+    }
+
+    CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
+      if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+      if (is_empty()) return assign(nwidth,nheight);
+      const unsigned int
+        tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100),
+        tmpdimy = (nheight>0)?nheight:(-nheight*_height/100),
+        dimx = tmpdimx?tmpdimx:1,
+        dimy = tmpdimy?tmpdimy:1;
+      if (_window_width!=dimx || _window_height!=dimy) {
+        RECT rect; rect.left = rect.top = 0; rect.right = dimx - 1; rect.bottom = dimy - 1;
+        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+        const int cwidth = rect.right - rect.left + 1, cheight = rect.bottom - rect.top + 1;
+        SetWindowPos(_window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS);
+      }
+      if (_width!=dimx || _height!=dimy) {
+        unsigned int *const ndata = new unsigned int[dimx*dimy];
+        if (redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy);
+        else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
+        delete[] _data;
+        _data = ndata;
+        _bmi.bmiHeader.biWidth = dimx;
+        _bmi.bmiHeader.biHeight = -(int)dimy;
+        _width = dimx;
+        _height = dimy;
+      }
+      _window_width = dimx; _window_height = dimy;
+      _is_resized = false;
+      if (_is_fullscreen) move((screen_width()-_width)/2,(screen_height()-_height)/2);
+      if (redraw) return paint();
+      return *this;
+    }
+
+    CImgDisplay& toggle_fullscreen(const bool redraw=true) {
+      if (is_empty()) return *this;
+      if (redraw) {
+        const unsigned int bufsize = _width*_height*4;
+        void *odata = std::malloc(bufsize);
+        std::memcpy(odata,_data,bufsize);
+        assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+        std::memcpy(_data,odata,bufsize);
+        std::free(odata);
+        return paint();
+      }
+      return assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+    }
+
+    CImgDisplay& show() {
+      if (is_empty()) return *this;
+      if (_is_closed) {
+        _is_closed = false;
+        if (_is_fullscreen) _init_fullscreen();
+        ShowWindow(_window,SW_SHOW);
+        _update_window_pos();
+      }
+      return paint();
+    }
+
+    CImgDisplay& close() {
+      if (is_empty()) return *this;
+      if (!_is_closed && !_is_fullscreen) {
+        if (_is_fullscreen) _desinit_fullscreen();
+        ShowWindow(_window,SW_HIDE);
+        _is_closed = true;
+        _window_x = _window_y = 0;
+      }
+      return *this;
+    }
+
+    CImgDisplay& move(const int posx, const int posy) {
+      if (is_empty()) return *this;
+      if (!_is_fullscreen) {
+        RECT rect; rect.left = rect.top = 0; rect.right = _window_width-1; rect.bottom = _window_height-1;
+        AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+        const int border1 = (rect.right-rect.left+1-_width)/2, border2 = rect.bottom-rect.top+1-_height-border1;
+        SetWindowPos(_window,0,posx-border1,posy-border2,0,0,SWP_NOSIZE | SWP_NOZORDER);
+      } else SetWindowPos(_window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER);
+      _window_x = posx;
+      _window_y = posy;
+      _is_moved = false;
+      return show();
+    }
+
+    CImgDisplay& show_mouse() {
+      if (is_empty()) return *this;
+      _is_cursor_visible = true;
+      ShowCursor(TRUE);
+      SendMessage(_window,WM_SETCURSOR,0,0);
+      return *this;
+    }
+
+    CImgDisplay& hide_mouse() {
+      if (is_empty()) return *this;
+      _is_cursor_visible = false;
+      ShowCursor(FALSE);
+      SendMessage(_window,WM_SETCURSOR,0,0);
+      return *this;
+    }
+
+    CImgDisplay& set_mouse(const int posx, const int posy) {
+      if (!_is_closed && posx>=0 && posy>=0) {
+        _update_window_pos();
+        const int res = (int)SetCursorPos(_window_x + posx,_window_y + posy);
+        if (res) { _mouse_x = posx; _mouse_y = posy; }
+      }
+      return *this;
+    }
+
+    CImgDisplay& set_title(const char *const format, ...) {
+      if (is_empty()) return *this;
+      char tmp[1024] = { 0 };
+      va_list ap;
+      va_start(ap, format);
+      cimg_vsnprintf(tmp,sizeof(tmp),format,ap);
+      va_end(ap);
+      if (std::strcmp(_title,tmp)) {
+        if (_title) delete[] _title;
+        const unsigned int s = std::strlen(tmp) + 1;
+        _title = new char[s];
+        std::memcpy(_title,tmp,s*sizeof(char));
+        SetWindowTextA(_window, tmp);
+      }
+      return *this;
+    }
+
+    template<typename T>
+    CImgDisplay& display(const CImg<T>& img) {
+      if (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "display() : Empty specified image.",
+                                    cimgdisplay_instance);
+
+      if (is_empty()) assign(img._width,img._height);
+      return render(img).paint();
+    }
+
+    CImgDisplay& paint() {
+      if (!_is_closed) {
+        WaitForSingleObject(_mutex,INFINITE);
+        SetDIBitsToDevice(_hdc,0,0,_width,_height,0,0,0,_height,_data,&_bmi,DIB_RGB_COLORS);
+        ReleaseMutex(_mutex);
+      }
+      return *this;
+    }
+
+    template<typename T>
+    CImgDisplay& render(const CImg<T>& img) {
+      if (!img)
+        throw CImgArgumentException(_cimgdisplay_instance
+                                    "render() : Empty specified image.",
+                                    cimgdisplay_instance);
+
+      if (is_empty()) return *this;
+      if (img._depth!=1) return render(img.get_projections2d(img._width/2,img._height/2,img._depth/2));
+
+      const T
+        *data1 = img._data,
+        *data2 = (img._spectrum>=2)?img.data(0,0,0,1):data1,
+        *data3 = (img._spectrum>=3)?img.data(0,0,0,2):data1;
+
+      WaitForSingleObject(_mutex,INFINITE);
+      unsigned int
+        *const ndata = (img._width==_width && img._height==_height)?_data:new unsigned int[img._width*img._height],
+        *ptrd = ndata;
+
+      if (!_normalization || (_normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
+        _min = _max = 0;
+        switch (img._spectrum) {
+        case 1 : {
+          for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            const unsigned char val = (unsigned char)*(data1++);
+            *(ptrd++) = (val<<16) | (val<<8) | val;
+          }
+        } break;
+        case 2 : {
+          for (unsigned int xy = img._width*img._height; xy>0; --xy)
+            *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
+        } break;
+        default : {
+          for (unsigned int xy = img._width*img._height; xy>0; --xy)
+            *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
+        }
+        }
+      } else {
+        if (_normalization==3) {
+          if (cimg::type<T>::is_float()) _min = (float)img.min_max(_max);
+          else { _min = (float)cimg::type<T>::min(); _max = (float)cimg::type<T>::max(); }
+        } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max);
+        const float delta = _max-_min, mm = delta?delta:1.0f;
+        switch (img._spectrum) {
+        case 1 : {
+          for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            const unsigned char val = (unsigned char)(255*(*(data1++)-_min)/mm);
+            *(ptrd++) = (val<<16) | (val<<8) | val;
+          }
+        } break;
+        case 2 : {
+          for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            const unsigned char
+              R = (unsigned char)(255*(*(data1++)-_min)/mm),
+              G = (unsigned char)(255*(*(data2++)-_min)/mm);
+            *(ptrd++) = (R<<16) | (G<<8);
+          }
+        } break;
+        default : {
+          for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+            const unsigned char
+              R = (unsigned char)(255*(*(data1++)-_min)/mm),
+              G = (unsigned char)(255*(*(data2++)-_min)/mm),
+              B = (unsigned char)(255*(*(data3++)-_min)/mm);
+            *(ptrd++) = (R<<16) | (G<<8) | B;
+          }
+        }
+        }
+      }
+      if (ndata!=_data) { _render_resize(ndata,img._width,img._height,_data,_width,_height); delete[] ndata; }
+      ReleaseMutex(_mutex);
+      return *this;
+    }
+
+    template<typename T>
+    const CImgDisplay& snapshot(CImg<T>& img) const {
+      if (is_empty()) { img.assign(); return *this; }
+      const unsigned int *ptrs = _data;
+      img.assign(_width,_height,1,3);
+      T
+        *data1 = img.data(0,0,0,0),
+        *data2 = img.data(0,0,0,1),
+        *data3 = img.data(0,0,0,2);
+      for (unsigned int xy = img._width*img._height; xy>0; --xy) {
+        const unsigned int val = *(ptrs++);
+        *(data1++) = (unsigned char)(val>>16);
+        *(data2++) = (unsigned char)((val>>8)&0xFF);
+        *(data3++) = (unsigned char)(val&0xFF);
+      }
+      return *this;
+    }
+#endif
+
+    //@}
+  };
+
+  /*
+   #--------------------------------------
+   #
+   #
+   #
+   # Definition of the CImg<T> structure
+   #
+   #
+   #
+   #--------------------------------------
+   */
+
+  //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T.
+  /**
+     This is the main class of the %CImg Library. It declares and constructs
+     an image, allows access to its pixel values, and is able to perform various image operations.
+
+     \par Image representation
+
+     A %CImg image is defined as an instance of the container \ref CImg<\c T>, which contains a regular grid of pixels,
+     each pixel value being of type \c T. The image grid can have up to 4 dimensions : width, height, depth
+     and number of channels.
+     Usually, the three first dimensions are used to describe spatial coordinates <tt>(x,y,z)</tt>, while the number of channels
+     is rather used as a vector-valued dimension (it may describe the R,G,B color channels for instance).
+     If you need a fifth dimension, you can use image lists \ref CImgList<\c T> rather than simple images \ref CImg<\c T>.
+
+     Thus, the \ref CImg<\c T> class is able to represent volumetric images of vector-valued pixels,
+     as well as images with less dimensions (1d scalar signal, 2d color images, ...).
+     Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions.
+
+     Concerning the pixel value type \c T :
+     fully supported template types are the basic C++ types : <tt>unsigned char, char, short, unsigned int, int,
+     unsigned long, long, float, double, ... </tt>.
+     Typically, fast image display can be done using <tt>CImg<unsigned char></tt> images,
+     while complex image processing algorithms may be rather coded using <tt>CImg<float></tt> or <tt>CImg<double></tt>
+     images that have floating-point pixel values. The default value for the template T is \c float.
+     Using your own template types may be possible. However, you will certainly have to define the complete set
+     of arithmetic and logical operators for your class.
+
+     \par Image structure
+
+     The \ref CImg<\c T> structure contains \a six fields :
+     - \ref width defines the number of \a columns of the image (size along the X-axis).
+     - \ref height defines the number of \a rows of the image (size along the Y-axis).
+     - \ref depth defines the number of \a slices of the image (size along the Z-axis).
+     - \ref spectrum defines the number of \a channels of the image (size along the C-axis).
+     - \ref data defines a \a pointer to the \a pixel \a data (of type \c T).
+     - \ref is_shared is a boolean that tells if the memory buffer \ref data is shared with
+       another image.
+
+     You can access these fields publicly although it is recommended to use the dedicated functions
+     width(), height(), depth(), spectrum() and ptr() to do so.
+     Image dimensions are not limited to a specific range (as long as you got enough available memory).
+     A value of \e 1 usually means that the corresponding dimension is \a flat.
+     If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty.
+     Empty images should not contain any pixel data and thus, will not be processed by CImg member functions
+     (a CImgInstanceException will be thrown instead).
+     Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage).
+
+     \par Image declaration and construction
+
+     Declaring an image can be done by using one of the several available constructors.
+     Here is a list of the most used :
+
+     - Construct images from arbitrary dimensions :
+         - <tt>CImg<char> img;</tt> declares an empty image.
+         - <tt>CImg<unsigned char> img(128,128);</tt> declares a 128x128 greyscale image with
+         \c unsigned \c char pixel values.
+         - <tt>CImg<double> img(3,3);</tt> declares a 3x3 matrix with \c double coefficients.
+         - <tt>CImg<unsigned char> img(256,256,1,3);</tt> declares a 256x256x1x3 (color) image
+         (colors are stored as an image with three channels).
+         - <tt>CImg<double> img(128,128,128);</tt> declares a 128x128x128 volumetric and greyscale image
+         (with \c double pixel values).
+         - <tt>CImg<> img(128,128,128,3);</tt> declares a 128x128x128 volumetric color image
+         (with \c float pixels, which is the default value of the template parameter \c T).
+         - \b Note : images pixels are <b>not automatically initialized to 0</b>. You may use the function \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()() : allows to access or write pixel values.
+     - display() : displays the image in a new window.
+  **/
+  template<typename T>
+  struct CImg {
+
+    //! Variable representing the width of the instance image (i.e. dimensions along the X-axis).
+    /**
+       \remark
+       - Prefer using the function CImg<T>::width() 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>::height() 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>::depth() 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 C-axis).
+    /**
+       \remark
+       - Prefer using the function CImg<T>::spectrum() 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 spectrum would probably
+       result in a library crash.
+       - Scalar-valued images (one value per pixel) have \c spectrum defined to \c 1.
+       - Empty images have \c depth defined to \c 0.
+    **/
+    unsigned int _spectrum;
+
+    //! 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;
+
+    //! Value type.
+    typedef T value_type;
+
+    // Define common T-dependant types.
+    typedef typename cimg::superset<T,bool>::type Tbool;
+    typedef typename cimg::superset<T,unsigned char>::type Tuchar;
+    typedef typename cimg::superset<T,char>::type Tchar;
+    typedef typename cimg::superset<T,unsigned short>::type Tushort;
+    typedef typename cimg::superset<T,short>::type Tshort;
+    typedef typename cimg::superset<T,unsigned int>::type Tuint;
+    typedef typename cimg::superset<T,int>::type Tint;
+    typedef typename cimg::superset<T,unsigned long>::type Tulong;
+    typedef typename cimg::superset<T,long>::type Tlong;
+    typedef typename cimg::superset<T,float>::type Tfloat;
+    typedef typename cimg::superset<T,double>::type Tdouble;
+    typedef typename cimg::last<T,bool>::type boolT;
+    typedef typename cimg::last<T,unsigned char>::type ucharT;
+    typedef typename cimg::last<T,char>::type charT;
+    typedef typename cimg::last<T,unsigned short>::type ushortT;
+    typedef typename cimg::last<T,short>::type shortT;
+    typedef typename cimg::last<T,unsigned int>::type uintT;
+    typedef typename cimg::last<T,int>::type intT;
+    typedef typename cimg::last<T,unsigned long>::type ulongT;
+    typedef typename cimg::last<T,long>::type longT;
+    typedef typename cimg::last<T,float>::type floatT;
+    typedef typename cimg::last<T,double>::type doubleT;
+
+    //@}
+    //---------------------------
+    //
+    //! \name Plugins
+    //@{
+    //---------------------------
+#ifdef cimg_plugin
+#include cimg_plugin
+#endif
+#ifdef cimg_plugin1
+#include cimg_plugin1
+#endif
+#ifdef cimg_plugin2
+#include cimg_plugin2
+#endif
+#ifdef cimg_plugin3
+#include cimg_plugin3
+#endif
+#ifdef cimg_plugin4
+#include cimg_plugin4
+#endif
+#ifdef cimg_plugin5
+#include cimg_plugin5
+#endif
+#ifdef cimg_plugin6
+#include cimg_plugin6
+#endif
+#ifdef cimg_plugin7
+#include cimg_plugin7
+#endif
+#ifdef cimg_plugin8
+#include cimg_plugin8
+#endif
+
+    //@}
+    //---------------------------------------------------------
+    //
+    //! \name Constructors / Destructor / Instance Management
+    //@{
+    //---------------------------------------------------------
+
+    //! Destructor.
+    /**
+       The destructor destroys the instance image.
+       \remark
+       - Destructing an empty or shared image does nothing.
+       - Otherwise, all memory used to store the pixel data of the instance image is freed.
+       - When destroying a non-shared image, be sure that every shared instances of the same image are
+       also destroyed to avoid further access to desallocated memory buffers.
+    **/
+    ~CImg() {
+      if (_data && !_is_shared) delete[] _data;
+    }
+
+    //! Default constructor.
+    /**
+       The default constructor creates an empty instance image.
+       \remark
+       - An empty image does not contain any data and has all of its dimensions \ref width, \ref height, \ref depth, \ref spectrum
+       set to 0 as well as its pointer to the pixel buffer \ref data.
+       - An empty image is non-shared.
+    **/
+    CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {}
+
+    //! Constructs a new image with given size (\p dx,\p dy,\p dz,\p dc).
+    /**
+       This constructors create an instance image of size (\p dx,\p dy,\p dz,\p dc) 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 dc Desired size along the C-axis, i.e. the number of image channels \ref spectrum.
+       \remark
+       - If one of the input dimension \p dx,\p dy,\p dz or \p dc is set to 0, the created image is empty
+       and all has its dimensions set to 0. No memory for pixel data is then allocated.
+       - This constructor creates only non-shared images.
+       - Image pixels allocated by this constructor are \b not \b initialized.
+       Use the constructor CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
+       to get an image of desired size with pixels set to a particular value.
+    **/
+    explicit CImg(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dc=1):
+      _is_shared(false) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (siz) {
+        _width = dx; _height = dy; _depth = dz; _spectrum = dc;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(dx*dy*dz*dc*sizeof(T)),dx,dy,dz,dc);
+        }
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Construct an image with given size (\p dx,\p dy,\p dz,\p dc) and with pixel having a default value \p val.
+    /**
+       This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dc) 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 dc Desired size along the C-axis, i.e. the number of image channels \p spectrum.
+       \param val Default value for image pixels.
+       \remark
+       - This constructor has the same properties as CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
+    **/
+    CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc, const T val):
+      _is_shared(false) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (siz) {
+        _width = dx; _height = dy; _depth = dz; _spectrum = dc;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(dx*dy*dz*dc*sizeof(T)),dx,dy,dz,dc);
+        }
+        fill(val);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    //! Construct an image with given size (\p dx,\p dy,\p dz,\p dc) and with specified pixel values (int version).
+    CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc,
+         const int val0, const int val1, ...):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+#define _CImg_stdarg(img,a0,a1,N,t) { \
+        unsigned int _siz = (unsigned int)N; \
+        if (_siz--) { \
+          va_list ap; \
+          va_start(ap,a1); \
+          T *ptrd = (img)._data; \
+          *(ptrd++) = (T)a0; \
+          if (_siz--) { \
+            *(ptrd++) = (T)a1; \
+            for (; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \
+          } \
+          va_end(ap); \
+        } \
+      }
+      assign(dx,dy,dz,dc);
+      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dc,int);
+    }
+
+    //! Construct an image with given size (\p dx,\p dy,\p dz,\p dc) and with specified pixel values (double version).
+    CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc,
+         const double val0, const double val1, ...):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(dx,dy,dz,dc);
+      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dc,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 dc,
+         const char *const values, const bool repeat_values):_is_shared(false) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (siz) {
+        _width = dx; _height = dy; _depth = dz; _spectrum = dc;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(dx*dy*dz*dc*sizeof(T)),dx,dy,dz,dc);
+        }
+        fill(values,repeat_values);
+      } else { _width = _height = _depth = _spectrum = 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 dc) 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 dc=1, const bool shared=false):_is_shared(false) {
+      if (shared) {
+        _width = _height = _depth = _spectrum = 0; _data = 0;
+        throw CImgArgumentException(_cimg_instance
+                                    "CImg() : Invalid construction request of a (%u,%u,%u,%u) shared instance from a (%s*) buffer "
+                                    "(pixel types are different).",
+                                    cimg_instance,
+                                    dx,dy,dz,dc,CImg<t>::pixel_type());
+      }
+      const unsigned int siz = dx*dy*dz*dc;
+      if (data_buffer && siz) {
+        _width = dx; _height = dy; _depth = dz; _spectrum = dc;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(dx*dy*dz*dc*sizeof(T)),dx,dy,dz,dc);
+
+        }
+        const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1,
+         const unsigned int dz=1, const unsigned int dc=1, const bool shared=false) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (data_buffer && siz) {
+        _width = dx; _height = dy; _depth = dz; _spectrum = dc; _is_shared = shared;
+        if (_is_shared) _data = const_cast<T*>(data_buffer);
+        else {
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(dx*dy*dz*dc*sizeof(T)),dx,dy,dz,dc);
+          }
+          std::memcpy(_data,data_buffer,siz*sizeof(T)); }
+      } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+    }
+
+    //! Construct an image from an image file.
+    /**
+       This constructor creates an instance image by reading it from a file.
+       \param filename Filename of the image file.
+       \remark
+       - The image format is deduced from the filename only by looking for the filename extension i.e. without
+       analyzing the file itself.
+       - Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with.
+       More informations on this topic can be found in cimg_files_io.
+       - If the filename is not found, a CImgIOException is thrown by this constructor.
+    **/
+    explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(filename);
+    }
+
+    //! Default copy constructor.
+    /**
+       The default copy constructor creates a new instance image having same dimensions
+       (\ref width, \ref height, \ref depth, \ref _spectrum) and same pixel values as the input image \p img.
+       \param img The input image to copy.
+       \remark
+       - If the input image \p img is non-shared or have a different template type \p t != \p T,
+       the default copy constructor allocates a new pixel buffer and copy the pixel data
+       of \p img into it. In this case, the pointers \ref data to the pixel buffers of the two images are different
+       and the resulting instance image is non-shared.
+       - If the input image \p img is shared and has the same template type \p t == \p T,
+       the default copy constructor does not allocate a new pixel buffer and the resulting instance image
+       shares its pixel buffer with the input image \p img, which means that modifying pixels of \p img also modifies
+       the created instance image.
+       - Copying an image having a different template type \p t != \p T performs a crude static cast conversion of each pixel value from
+       type \p t to type \p T.
+       - Copying an image having the same template type \p t == \p T is significantly faster.
+    **/
+    template<typename t>
+    CImg(const CImg<t>& img):_is_shared(false) {
+      const unsigned int siz = img.size();
+      if (img._data && siz) {
+        _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                      img._width,img._height,img._depth,img._spectrum);
+        }
+        const t *ptrs = img._data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    CImg(const CImg<T>& img) {
+      const unsigned int siz = img.size();
+      if (img._data && siz) {
+        _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum; _is_shared = img._is_shared;
+        if (_is_shared) _data = const_cast<T*>(img._data);
+        else {
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                        img._width,img._height,img._depth,img._spectrum);
+
+          }
+          std::memcpy(_data,img._data,siz*sizeof(T));
+        }
+      } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+    }
+
+    //! Advanced copy constructor.
+    /**
+       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 spectrum and same pixel values as the input image \p img.
+       But it also decides if the created instance image shares its memory with the input image \p img (if the input parameter
+       \p shared is set to \p true) or not (if the input parameter \p shared is set to \p false).
+       \param img The input image to copy.
+       \param shared Boolean flag that decides if the copy is shared on non-shared.
+       \remark
+       - It is not possible to create a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
+       - If a non-shared copy of the input image \p img is created, a new memory buffer is allocated for pixel data.
+       - If a shared copy of the input image \p img is created, no extra memory is allocated and the pixel buffer of the instance
+       image is the same as the one used by the input image \p img.
+    **/
+    template<typename t>
+    CImg(const CImg<t>& img, const bool shared):_is_shared(false) {
+      if (shared) {
+        _width = _height = _depth = _spectrum = 0; _data = 0;
+        throw CImgArgumentException(_cimg_instance
+                                    "CImg() : Invalid construction request of a shared instance from a "
+                                    "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).",
+                                    cimg_instance,
+                                    CImg<t>::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data);
+      }
+
+      const unsigned int siz = img.size();
+      if (img._data && siz) {
+        _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum;
+        try { _data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                      img._width,img._height,img._depth,img._spectrum);
+        }
+        const t *ptrs = img._data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+    }
+
+    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; _spectrum = img._spectrum; _is_shared = shared;
+        if (_is_shared) _data = const_cast<T*>(img._data);
+        else {
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "CImg() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(img._width*img._height*img._depth*img._spectrum*sizeof(T)),
+                                        img._width,img._height,img._depth,img._spectrum);
+          }
+          std::memcpy(_data,img._data,siz*sizeof(T));
+        }
+      } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+    }
+
+    //! Construct 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),_spectrum(0),_is_shared(false),_data(0) {
+      assign(img,dimensions);
+    }
+
+    //! Construct an image using dimensions of another image, and fill it with given values.
+    template<typename t>
+    CImg(const CImg<t>& img, const char *const dimensions, const T val):
+      _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(img,dimensions).fill(val);
+    }
+
+    //! Construct an image using dimensions of another image, and fill it with given values.
+    template<typename t>
+    CImg(const CImg<t>& img, const char *const dimensions, const char *const values, const bool repeat_values):
+      _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      assign(img,dimensions).fill(values,repeat_values);
+    }
+
+    //! Construct an image from the content of a CImgDisplay instance.
+    explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+      disp.snapshot(*this);
+    }
+
+    //! In-place version of the default constructor (STL-compliant name).
+    /**
+       This function is strictly equivalent to \ref assign() and has been
+       introduced for having a STL-compatible function name.
+    **/
+    CImg<T>& clear() {
+      return assign();
+    }
+
+    //! In-place version of the default constructor/destructor.
+    /**
+       This function replaces the instance image by an empty image.
+       \remark
+       - Memory used by the previous content of the instance image is freed if necessary.
+       - If the instance image was initially shared, it is replaced by a (non-shared) empty image.
+       - This function is useful to free memory used by an image that is not of use, but which
+       has been created in the current code scope (i.e. not destroyed yet).
+    **/
+    CImg<T>& assign() {
+      if (_data && !_is_shared) delete[] _data;
+      _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0;
+      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 dc) 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 dc Desired size along the C-axis, i.e. the number of image channels \p _spectrum.
+       - If one of the input dimension \p dx,\p dy,\p dz or \p dc is set to 0, the instance image becomes empty
+       and all has its dimensions set to 0. No memory for pixel data is then allocated.
+       - Memory buffer used to store previous pixel values is freed if necessary.
+       - If the instance image is shared, this constructor actually does nothing more than verifying
+       that new and old image dimensions fit.
+       - Image pixels allocated by this function are \b not \b initialized.
+       Use the function assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
+       to assign an image of desired size with pixels set to a particular value.
+    **/
+    CImg<T>& assign(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dc=1) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (!siz) return assign();
+      const unsigned int curr_siz = size();
+      if (siz!=curr_siz) {
+        if (_is_shared)
+          throw CImgArgumentException(_cimg_instance
+                                      "assign() : Invalid assignement request of shared instance from specified image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      dx,dy,dz,dc);
+        else {
+          if (_data) delete[] _data;
+          try { _data = new T[siz]; } catch (...) {
+            _width = _height = _depth = _spectrum = 0; _data = 0;
+            throw CImgInstanceException(_cimg_instance
+                                        "assign() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                        cimg_instance,
+                                        cimg::strbuffersize(dx*dy*dz*dc*sizeof(T)),dx,dy,dz,dc);
+          }
+        }
+      }
+      _width = dx; _height = dy; _depth = dz; _spectrum = dc;
+      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 dc) 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 dc Desired size along the C-axis, i.e. the number of image channels \p _spectrum.
+       \param val Default value for image pixels.
+       \remark
+       - This function has the same properties as assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
+    **/
+    CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc, const T val) {
+      return assign(dx,dy,dz,dc).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 dc,
+                    const int val0, const int val1, ...) {
+      assign(dx,dy,dz,dc);
+      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dc,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 dc,
+                    const double val0, const double val1, ...) {
+      assign(dx,dy,dz,dc);
+      _CImg_stdarg(*this,val0,val1,dx*dy*dz*dc,double);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc,
+                    const char *const values, const bool repeat_values) {
+      return assign(dx,dy,dz,dc).fill(values,repeat_values);
+    }
+
+    //! 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 dc=1) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (!data_buffer || !siz) return assign();
+      assign(dx,dy,dz,dc);
+      const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
+      return *this;
+    }
+
+    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 dc=1) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (!data_buffer || !siz) return assign();
+      const unsigned int curr_siz = size();
+      if (data_buffer==_data && siz==curr_siz) return assign(dx,dy,dz,dc);
+      if (_is_shared || data_buffer+siz<_data || data_buffer>=_data+size()) {
+        assign(dx,dy,dz,dc);
+        if (_is_shared) std::memmove(_data,data_buffer,siz*sizeof(T));
+        else std::memcpy(_data,data_buffer,siz*sizeof(T));
+      } else {
+        T *new_data = 0;
+        try { new_data = new T[siz]; } catch (...) {
+          _width = _height = _depth = _spectrum = 0; _data = 0;
+          throw CImgInstanceException(_cimg_instance
+                                      "assign() : Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+                                      cimg_instance,
+                                      cimg::strbuffersize(dx*dy*dz*dc*sizeof(T)),dx,dy,dz,dc);
+        }
+        std::memcpy(new_data,data_buffer,siz*sizeof(T));
+        delete[] _data; _data = new_data; _width = dx; _height = dy; _depth = dz; _spectrum = dc;
+      }
+      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 dc, const bool shared) {
+      if (shared)
+        throw CImgArgumentException(_cimg_instance
+                                    "assign() : Invalid assignment request of shared instance from (%s*) buffer"
+                                    "(pixel types are different).",
+                                    cimg_instance,
+                                    CImg<t>::pixel_type());
+
+      return assign(data_buffer,dx,dy,dz,dc);
+    }
+
+    CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
+                    const unsigned int dz, const unsigned int dc, const bool shared) {
+      const unsigned int siz = dx*dy*dz*dc;
+      if (!data_buffer || !siz) {
+        if (shared) throw CImgArgumentException(_cimg_instance
+                                                "assign() : Invalid assignment request of shared instance from (null) or empty buffer.",
+                                                cimg_instance);
+        else return assign();
+      }
+      if (!shared) { if (_is_shared) assign(); assign(data_buffer,dx,dy,dz,dc); }
+      else {
+        if (!_is_shared) {
+          if (data_buffer+siz<_data || data_buffer>=_data+size()) assign();
+          else cimg::warn(_cimg_instance
+                          "assign() : Shared instance image has overlapping memory.",
+                          cimg_instance);
+        }
+        _width = dx; _height = dy; _depth = dz; _spectrum = dc; _is_shared = true;
+        _data = const_cast<T*>(data_buffer);
+      }
+      return *this;
+    }
+
+    //! 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 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 non-shared, the content of the input image \p img is copied into a new buffer
+       becoming the new pixel buffer of the instance image, while the old pixel buffer is freed if necessary.
+       - If the instance image is shared, the content of the input image \p img is copied into the current (shared) pixel buffer
+       of the instance image, modifying then the image referenced by the shared instance image. The instance image still remains shared.
+    **/
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img) {
+      return assign(img._data,img._width,img._height,img._depth,img._spectrum);
+    }
+
+    //! In-place version of the advanced constructor.
+    /**
+       This function - as the simpler function assign(const CImg< t >&) - assigns a copy of the input image \p img to the
+       current instance image. But it also decides if the copy is shared (if the input parameter \p shared is set to \c true)
+       or non-shared (if the input parameter \p shared is set to \c false).
+       \param img The input image to copy.
+       \param shared Boolean flag that decides if the copy is shared or non-shared.
+       \remark
+       - It is not possible to assign a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
+       - If a non-shared copy of the input image \p img is assigned, a new memory buffer is allocated for pixel data.
+       - If a shared copy of the input image \p img is assigned, no extra memory is allocated and the pixel buffer of the instance
+       image is the same as the one used by the input image \p img.
+    **/
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img, const bool shared) {
+      return assign(img._data,img._width,img._height,img._depth,img._spectrum,shared);
+    }
+
+    //! In-place version of the previous constructor.
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img, const char *const dimensions) {
+      if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum);
+      unsigned int siz[4] = { 0,1,1,1 }, k = 0;
+      for (const char *s = dimensions; *s && k<4; ++k) {
+        char item[256] = { 0 };
+        if (std::sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item)>0) s+=std::strlen(item);
+        if (*s) {
+          unsigned int val = 0; char sep = 0;
+          if (std::sscanf(s,"%u%c",&val,&sep)>0) {
+            if (sep=='%') siz[k] = val*(k==0?_width:k==1?_height:k==2?_depth:_spectrum)/100;
+            else siz[k] = val;
+            while (*s>='0' && *s<='9') ++s; if (sep=='%') ++s;
+          } else switch (cimg::uncase(*s)) {
+          case 'x' : case 'w' : siz[k] = img._width; ++s; break;
+          case 'y' : case 'h' : siz[k] = img._height; ++s; break;
+          case 'z' : case 'd' : siz[k] = img._depth; ++s; break;
+          case 'v' : case 'c' : siz[k] = img._spectrum; ++s; break;
+          default :
+            throw CImgArgumentException(_cimg_instance
+                                        "assign() : Invalid character '%c' detected in specified dimension string '%s'.",
+                                        cimg_instance,
+                                        *s,dimensions);
+          }
+        }
+      }
+      return assign(siz[0],siz[1],siz[2],siz[3]);
+    }
+
+    //! In-place version of 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.
+    template<typename t>
+    CImg<T>& assign(const CImg<t>& img, const char *const dimensions, const char *const values, const bool repeat_values) {
+      return assign(img,dimensions).fill(values,repeat_values);
+    }
+
+    //! In-place version of the previous constructor.
+    CImg<T>& assign(const CImgDisplay &disp) {
+      disp.snapshot(*this);
+      return *this;
+    }
+
+    //! Move 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>& move_to(CImg<t>& img) {
+      img.assign(*this);
+      assign();
+      return img;
+    }
+
+    CImg<T>& move_to(CImg<T>& img) {
+      if (_is_shared || img._is_shared) img.assign(*this);
+      else swap(img);
+      assign();
+      return img;
+    }
+
+    template<typename t>
+    CImgList<t>& move_to(CImgList<t>& list, const unsigned int pos=~0U) {
+      const unsigned int npos = pos>list._width?list._width:pos;
+      move_to(list.insert(1,npos)[npos]);
+      return list;
+    }
+
+    //! Return a reference to an empty image.
+    static CImg<T>& empty() {
+      static CImg<T> _empty;
+      return _empty.assign();
+    }
+
+    //! Swap all fields of two images. Use with care !
+    CImg<T>& swap(CImg<T>& img) {
+      cimg::swap(_width,img._width);
+      cimg::swap(_height,img._height);
+      cimg::swap(_depth,img._depth);
+      cimg::swap(_spectrum,img._spectrum);
+      cimg::swap(_data,img._data);
+      cimg::swap(_is_shared,img._is_shared);
+      return img;
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Overloaded Operators
+    //@{
+    //------------------------------------------
+
+    //! 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 C-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_verbosity'>=3, boundary checking is performed and warning messages may appear
+       (but function performances decrease).
+
+       \par example:
+       \code
+       CImg<float> img(100,100,1,3,0);                       // Define a 100x100 color image with float-valued black pixels.
+       const float valR = img(10,10,0,0);                    // Read the red component at coordinates (10,10).
+       const float valG = img(10,10,0,1);                    // Read the green component at coordinates (10,10)
+       const float valB = img(10,10,2);                      // Read the blue component at coordinates (10,10) (Z-coordinate omitted here).
+       const float avg = (valR + valG + valB)/3;             // Compute average pixel value.
+       img(10,10,0) = img(10,10,1) = img(10,10,2) = avg;     // Replace the pixel (10,10) by the average grey value.
+       \endcode
+    **/
+#if cimg_verbosity>=3
+    T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+      const unsigned int off = (unsigned int)offset(x,y,z,c);
+      if (!_data || off>=size()) {
+        cimg::warn(_cimg_instance
+                   "operator() : Invalid pixel request, at coordinates (%u,%u,%u,%u) [offset=%u].",
+                   cimg_instance,
+                   x,y,z,c,off);
+        return *_data;
+      }
+      else return _data[off];
+    }
+
+    const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+      return const_cast<CImg<T>*>(this)->operator()(x,y,z,c);
+    }
+
+#else
+    T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+      return _data[x + y*_width + z*_width*_height + c*_width*_height*_depth];
+    }
+
+    const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+      return _data[x + y*_width + z*_width*_height + c*_width*_height*_depth];
+    }
+#endif
+
+    //! Return address of the pixel buffer.
+    operator const T*() const {
+      return _data;
+    }
+
+    operator T*() {
+      return _data;
+    }
+
+    //! Operator=().
+    /**
+       Assignment operator. Fill all pixels of the instance image with the same value.
+       The image size is not modified.
+    **/
+    CImg<T>& operator=(const T val) {
+      return fill(val);
+    }
+
+    //! Operator=().
+    /**
+       Assignment operator.
+       If \p expression is a formula or a list of values, the image pixels are filled
+       according to the expression and the image size is not modified.
+       If \p expression is a filename, the image is replaced by the input file data
+       (so image size is modified).
+    **/
+    CImg<T>& operator=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        fill(expression,true);
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        load(expression);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator=().
+    /**
+       Assignement operator.
+       If instance image is non-shared, replace the instance image by a copy of the argument image.
+       If instance image is shared, replace the image content by the content of the argument image.
+    **/
+    template<typename t>
+    CImg<T>& operator=(const CImg<t>& img) {
+      return assign(img);
+    }
+
+    CImg<T>& operator=(const CImg<T>& img) {
+      return assign(img);
+    }
+
+    //! Operator=().
+    CImg<T>& operator=(const CImgDisplay& disp) {
+      disp.snapshot(*this);
+      return *this;
+    }
+
+    //! Operator+=().
+    template<typename t>
+    CImg<T>& operator+=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd + val);
+      return *this;
+    }
+
+    //! Operator+=().
+    CImg<T>& operator+=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator+=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd + mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this+=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator+=().
+    template<typename t>
+    CImg<T>& operator+=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this+=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd + *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd + *(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator++() (prefix).
+    CImg<T>& operator++() {
+      cimg_for(*this,ptrd,T) ++*ptrd;
+      return *this;
+    }
+
+    //! Operator++() (postfix).
+    CImg<T> operator++(int) {
+      const CImg<T> copy(*this,false);
+      ++*this;
+      return copy;
+    }
+
+    //! Operator+() (unary).
+    /**
+       \remark
+       - This operator always returns a non-shared copy of an image.
+    **/
+    CImg<T> operator+() const {
+      return CImg<T>(*this,false);
+    }
+
+    //! Operator+().
+    template<typename t>
+    CImg<_cimg_Tt> operator+(const t val) const {
+      return CImg<_cimg_Tt>(*this,false)+=val;
+    }
+
+    //! Operator+().
+    CImg<Tfloat> operator+(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)+=expression;
+    }
+
+    //! Operator+().
+    template<typename t>
+    CImg<_cimg_Tt> operator+(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false)+=img;
+    }
+
+    //! Operator-=().
+    template<typename t>
+    CImg<T>& operator-=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd - val);
+      return *this;
+    }
+
+    //! Operator-=().
+    CImg<T>& operator-=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator-=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd - mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this-=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator-=().
+    template<typename t>
+    CImg<T>& operator-=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this-=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd - *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd - *(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator--() (prefix).
+    CImg<T>& operator--() {
+      cimg_for(*this,ptrd,T) *ptrd = *ptrd-(T)1;
+      return *this;
+    }
+
+    //! Operator--() (postfix).
+    CImg<T> operator--(int) {
+      const CImg<T> copy(*this,false);
+      --*this;
+      return copy;
+    }
+
+    //! Operator-() (unary).
+    CImg<T> operator-() const {
+      return CImg<T>(_width,_height,_depth,_spectrum,(T)0)-=*this;
+    }
+
+    //! Operator-().
+    template<typename t>
+    CImg<_cimg_Tt> operator-(const t val) const {
+      return CImg<_cimg_Tt>(*this,false)-=val;
+    }
+
+    //! Operator-().
+    CImg<Tfloat> operator-(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)-=expression;
+    }
+
+    //! Operator-().
+    template<typename t>
+    CImg<_cimg_Tt> operator-(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false)-=img;
+    }
+
+    //! Operator*=().
+    template<typename t>
+    CImg<T>& operator*=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd * val);
+      return *this;
+    }
+
+    //! Operator*=().
+    CImg<T>& operator*=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator*=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd * mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this*=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator*=().
+    template<typename t>
+    CImg<T>& operator*=(const CImg<t>& img) {
+      return ((*this)*img).move_to(*this);
+    }
+
+    //! Operator*().
+    template<typename t>
+    CImg<_cimg_Tt> operator*(const t val) const {
+      return CImg<_cimg_Tt>(*this,false)*=val;
+    }
+
+    //! Operator*().
+    CImg<Tfloat> operator*(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)*=expression;
+    }
+
+    //! Operator*().
+    template<typename t>
+    CImg<_cimg_Tt> operator*(const CImg<t>& img) const {
+      if (_width!=img._height || _depth!=1 || _spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "operator*() : Invalid multiplication of instance by specified matrix (%u,%u,%u,%u,%p)",
+                                    cimg_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data);
+
+      CImg<_cimg_Tt> res(img._width,_height);
+      _cimg_Tt val, *ptrd = res._data;
+#ifdef cimg_use_openmp
+#pragma omp parallel for if (size()>=1000 && img.size()>=1000) private(val)
+#endif
+      cimg_forXY(res,i,j) { val = 0; cimg_forX(*this,k) val+=(*this)(k,j)*img(i,k); *(ptrd++) = val; }
+      return res;
+    }
+
+    //! Operator/=().
+    template<typename t>
+    CImg<T>& operator/=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(*ptrd / val);
+      return *this;
+    }
+
+    //! Operator/=().
+    CImg<T>& operator/=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator/=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd / mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this/=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator/=().
+    template<typename t>
+    CImg<T>& operator/=(const CImg<t>& img) {
+      return (*this*img.get_invert()).move_to(*this);
+    }
+
+    //! Operator/().
+    template<typename t>
+    CImg<_cimg_Tt> operator/(const t val) const {
+      return CImg<_cimg_Tt>(*this,false)/=val;
+    }
+
+    //! Operator/().
+    CImg<Tfloat> operator/(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)/=expression;
+    }
+
+    //! Operator/().
+    template<typename t>
+    CImg<_cimg_Tt> operator/(const CImg<t>& img) const {
+      return (*this)*img.get_invert();
+    }
+
+    //! Operator%=().
+    template<typename t>
+    CImg<T>& operator%=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::mod(*ptrd,(T)val);
+      return *this;
+    }
+
+    //! Operator%=().
+    CImg<T>& operator%=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator%=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::mod(*ptrd,(T)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this%=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator%=().
+    template<typename t>
+    CImg<T>& operator%=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this%=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = cimg::mod(*ptrd,(T)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = cimg::mod(*ptrd,(T)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator%().
+    template<typename t>
+    CImg<_cimg_Tt> operator%(const t val) const {
+      return CImg<_cimg_Tt>(*this,false)%=val;
+    }
+
+    //! Operator%().
+    CImg<Tfloat> operator%(const char *const expression) const {
+      return CImg<Tfloat>(*this,false)%=expression;
+    }
+
+    //! Operator%().
+    template<typename t>
+    CImg<_cimg_Tt> operator%(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false)%=img;
+    }
+
+    //! Operator&=().
+    template<typename t>
+    CImg<T>& operator&=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)((unsigned long)*ptrd & (unsigned long)val);
+      return *this;
+    }
+
+    //! Operator&=().
+    CImg<T>& operator&=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator&=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((unsigned long)*ptrd & (unsigned long)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this&=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator&=().
+    template<typename t>
+    CImg<T>& operator&=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this&=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((unsigned long)*ptrd & (unsigned long)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((unsigned long)*ptrd & (unsigned long)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator&().
+    template<typename t>
+    CImg<T> operator&(const t val) const {
+      return (+*this)&=val;
+    }
+
+    //! Operator|=().
+    template<typename t>
+    CImg<T>& operator|=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)((unsigned long)*ptrd | (unsigned long)val);
+      return *this;
+    }
+
+    //! Operator|=().
+    CImg<T>& operator|=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator|=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((unsigned long)*ptrd | (unsigned long)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this|=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator|=().
+    template<typename t>
+    CImg<T>& operator|=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this|=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((unsigned long)*ptrd | (unsigned long)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((unsigned long)*ptrd | (unsigned long)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator|().
+    template<typename t>
+    CImg<T> operator|(const t val) const {
+      return (+*this)|=val;
+    }
+
+    //! Operator^=().
+    template<typename t>
+    CImg<T>& operator^=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)val);
+      return *this;
+    }
+
+    //! Operator^=().
+    CImg<T>& operator^=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator^=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this^=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator^=().
+    template<typename t>
+    CImg<T>& operator^=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this^=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((unsigned long)*ptrd ^ (unsigned long)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator^().
+    template<typename t>
+    CImg<T> operator^(const t val) const {
+      return (+*this)^=val;
+    }
+
+    //! Operator<<=().
+    template<typename t>
+    CImg<T>& operator<<=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(((long)*ptrd) << (int)val);
+      return *this;
+    }
+
+    //! Operator<<=().
+    CImg<T>& operator<<=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator<<=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((long)*ptrd << (int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this<<=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator<<=().
+    template<typename t>
+    CImg<T>& operator<<=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this^=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((long)*ptrd << (int)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((long)*ptrd << (int)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator<<().
+    template<typename t>
+    CImg<T> operator<<(const t val) const {
+      return (+*this)<<=val;
+    }
+
+    //! Operator>>=().
+    template<typename t>
+    CImg<T>& operator>>=(const t val) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)(((long)*ptrd) >> (int)val);
+      return *this;
+    }
+
+    //! Operator>>=().
+    CImg<T>& operator>>=(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"operator<<=");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)((long)*ptrd >> (int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        *this>>=values;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Operator>>=().
+    template<typename t>
+    CImg<T>& operator>>=(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return *this^=+img;
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)((long)*ptrd >> (int)*(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((long)*ptrd >> (int)*(ptrs++));
+      }
+      return *this;
+    }
+
+    //! Operator>>().
+    template<typename t>
+    CImg<T> operator>>(const t val) const {
+      return (+*this)>>=val;
+    }
+
+    //! Operator==().
+    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;
+    }
+
+    //! Operator!=().
+    template<typename t>
+    bool operator!=(const CImg<t>& img) const {
+      return !((*this)==img);
+    }
+
+    //! Operator,().
+    template<typename t>
+    CImgList<_cimg_Tt> operator,(const CImg<t>& img) const {
+      return CImgList<_cimg_Tt>(*this,img);
+    }
+
+    //! Operator,().
+    template<typename t>
+    CImgList<_cimg_Tt> operator,(CImgList<t>& list) const {
+      return CImgList<_cimg_Tt>(list).insert(*this,0);
+    }
+
+    //! Operator<().
+    CImgList<T> operator<(const char axis) const {
+      return get_split(axis);
+    }
+
+    //! Operator~().
+    CImg<T> operator~() const {
+      CImg<T> res(_width,_height,_depth,_spectrum);
+      const T *ptrs = end();
+      cimg_for(res,ptrd,T) { const unsigned long val = (unsigned long)*(--ptrs); *ptrd = (T)~val; }
+      return res;
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Characteristics
+    //@{
+    //-------------------------------------
+
+    //! 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 number of columns of the instance image (size along the X-axis, i.e image width).
+    int width() const {
+      return (int)_width;
+    }
+
+    //! Return the number of rows of the instance image (size along the Y-axis, i.e image height).
+    int height() const {
+      return (int)_height;
+    }
+
+    //! Return the number of slices of the instance image (size along the Z-axis).
+    int depth() const {
+      return (int)_depth;
+    }
+
+    //! Return the number of vector channels of the instance image (size along the C-axis).
+    int spectrum() const {
+      return (int)_spectrum;
+    }
+
+    //! Return the number of image buffer elements.
+    /**
+       - Equivalent to : width() * height() * depth() * spectrum().
+
+       \par example:
+       \code
+       CImg<> img(100,100,1,3);
+       if (img.size()==100*100*3) std::fprintf(stderr,"This statement is true");
+       \endcode
+    **/
+    unsigned int size() const {
+      return _width*_height*_depth*_spectrum;
+    }
+
+    //! Return a pointer to the pixel buffer.
+    T* data() {
+      return _data;
+    }
+
+    const T* data() const {
+      return _data;
+    }
+
+    //! Return a pointer to the pixel value located at (\p x,\p y,\p z,\p v).
+    /**
+       \param x X-coordinate of the pixel.
+       \param y Y-coordinate of the pixel.
+       \param z Z-coordinate of the pixel.
+       \param v C-coordinate of the pixel.
+
+       - When called without parameters, data() returns a pointer to the begining of the pixel buffer.
+       - If the macro \c 'cimg_verbosity'>=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 = data(10,10);         // Get a pointer to the pixel located at (10,10).
+       float val = *ptr;                 // Get the pixel value.
+       \endcode
+    **/
+#if cimg_verbosity>=3
+    T *data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+      const unsigned int off = (unsigned int)offset(x,y,z,c);
+      if (off>=size()) {
+        cimg::warn(_cimg_instance
+                   "data() : Invalid pointer request, at coordinates (%u,%u,%u,%u) [offset=%u].",
+                   cimg_instance,
+                   x,y,z,c,off);
+        return _data;
+      }
+      return _data + off;
+    }
+
+    const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+      return const_cast<CImg<T>*>(this)->data(x,y,z,c);
+    }
+#else
+    T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+      return _data + x + y*_width + z*_width*_height + c*_width*_height*_depth;
+    }
+
+    const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+      return _data + x + y*_width + z*_width*_height + c*_width*_height*_depth;
+    }
+#endif
+
+    //! Return the offset 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 C-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
+    **/
+    int offset(const int x, const int y=0, const int z=0, const int c=0) const {
+      return x + y*_width + z*_width*_height + c*_width*_height*_depth;
+    }
+
+    //! Return an iterator to the first image pixel
+    iterator begin() {
+      return _data;
+    }
+
+    const_iterator begin() const {
+      return _data;
+    }
+
+    //! Return an iterator pointing after the last image pixel (STL-compliant name).
+    iterator end() {
+      return _data + size();
+    }
+
+    const_iterator end() const {
+      return _data + size();
+    }
+
+    //! Return reference to the first image pixel (STL-compliant name).
+    const T& front() const {
+      return *_data;
+    }
+
+    T& front() {
+      return *_data;
+    }
+
+    //! Return a reference to the last image pixel (STL-compliant name).
+    const T& back() const {
+      return *(_data + size() - 1);
+    }
+
+    T& back() {
+      return *(_data + size() - 1);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions.
+    T& at(const int off, const T out_val) {
+      return (off<0 || off>=(int)size())?(cimg::temporary(out_val)=out_val):(*this)[off];
+    }
+
+    T at(const int off, const T out_val) const {
+      return (off<0 || off>=(int)size())?out_val:(*this)[off];
+    }
+
+    //! Read a pixel value with Neumann boundary conditions.
+    T& at(const int off) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "at() : Empty instance.",
+                                    cimg_instance);
+      return _at(off);
+    }
+
+    T at(const int off) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "at() : Empty instance.",
+                                    cimg_instance);
+      return _at(off);
+    }
+
+    T& _at(const int off) {
+      const unsigned int siz = (unsigned int)size();
+      return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
+    }
+
+    T _at(const int off) const {
+      const unsigned int siz = (unsigned int)size();
+      return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c x).
+    T& atX(const int x, const int y, const int z, const int c, const T out_val) {
+      return (x<0 || x>=width())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,c);
+    }
+
+    T atX(const int x, const int y, const int z, const int c, const T out_val) const {
+      return (x<0 || x>=width())?out_val:(*this)(x,y,z,c);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c x).
+    T& atX(const int x, const int y=0, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atX() : Empty instance.",
+                                    cimg_instance);
+      return _atX(x,y,z,c);
+    }
+
+    T atX(const int x, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atX() : Empty instance.",
+                                    cimg_instance);
+      return _atX(x,y,z,c);
+    }
+
+    T& _atX(const int x, const int y=0, const int z=0, const int c=0) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y,z,c);
+    }
+
+    T _atX(const int x, const int y=0, const int z=0, const int c=0) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y,z,c);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c x,\c y).
+    T& atXY(const int x, const int y, const int z, const int c, const T out_val) {
+      return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,c);
+    }
+
+    T atXY(const int x, const int y, const int z, const int c, const T out_val) const {
+      return (x<0 || y<0 || x>=width() || y>=height())?out_val:(*this)(x,y,z,c);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c x,\c y).
+    T& atXY(const int x, const int y, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXY() : Empty instance.",
+                                    cimg_instance);
+      return _atXY(x,y,z,c);
+    }
+
+    T atXY(const int x, const int y, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXY() : Empty instance.",
+                                    cimg_instance);
+      return _atXY(x,y,z,c);
+    }
+
+    T& _atXY(const int x, const int y, const int z=0, const int c=0) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),z,c);
+    }
+
+    T _atXY(const int x, const int y, const int z=0, const int c=0) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),z,c);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c x,\c y,\c z).
+    T& atXYZ(const int x, const int y, const int z, const int c, const T out_val) {
+      return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?
+        (cimg::temporary(out_val)=out_val):(*this)(x,y,z,c);
+    }
+
+    T atXYZ(const int x, const int y, const int z, const int c, const T out_val) const {
+      return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_val:(*this)(x,y,z,c);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c x,\c y,\c z).
+    T& atXYZ(const int x, const int y, const int z, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZ() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZ(x,y,z,c);
+    }
+
+    T atXYZ(const int x, const int y, const int z, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZ() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZ(x,y,z,c);
+    }
+
+    T& _atXYZ(const int x, const int y, const int z, const int c=0) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z),c);
+    }
+
+    T _atXYZ(const int x, const int y, const int z, const int c=0) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x),y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z),c);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions.
+    T& atXYZC(const int x, const int y, const int z, const int c, const T out_val) {
+      return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?
+        (cimg::temporary(out_val)=out_val):(*this)(x,y,z,c);
+    }
+
+    T atXYZC(const int x, const int y, const int z, const int c, const T out_val) const {
+      return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_val:(*this)(x,y,z,c);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions.
+    T& atXYZC(const int x, const int y, const int z, const int c) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZC() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZC(x,y,z,c);
+    }
+
+    T atXYZC(const int x, const int y, const int z, const int c) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "atXYZC() : Empty instance.",
+                                    cimg_instance);
+      return _atXYZC(x,y,z,c);
+    }
+
+    T& _atXYZC(const int x, const int y, const int z, const int c) {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z), c<0?0:(c>=spectrum()?spectrum()-1:c));
+    }
+
+    T _atXYZC(const int x, const int y, const int z, const int c) const {
+      return (*this)(x<0?0:(x>=width()?width()-1:x), y<0?0:(y>=height()?height()-1:y),
+                     z<0?0:(z>=depth()?depth()-1:z), c<0?0:(c>=spectrum()?spectrum()-1:c));
+    }
+
+    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first coordinate).
+    Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T out_val) const {
+      const int
+        x = (int)fx - (fx>=0?0:1), nx = x + 1;
+      const float
+        dx = fx - x;
+      const Tfloat
+        Ic = (Tfloat)atX(x,y,z,c,out_val), In = (Tfloat)atXY(nx,y,z,c,out_val);
+      return Ic + dx*(In-Ic);
+    }
+
+    //! Read a pixel value using linear interpolation and Neumann boundary conditions (first coordinate).
+    Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atX() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atX(fx,y,z,c);
+    }
+
+    Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+      const float
+        nfx = fx<0?0:(fx>_width-1?_width-1:fx);
+      const unsigned int
+        x = (unsigned int)nfx;
+      const float
+        dx = nfx - x;
+      const unsigned int
+        nx = dx>0?x+1:x;
+      const Tfloat
+        Ic = (Tfloat)(*this)(x,y,z,c), In = (Tfloat)(*this)(nx,y,z,c);
+      return Ic + dx*(In-Ic);
+    }
+
+    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first two coordinates).
+    Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T out_val) const {
+      const int
+        x = (int)fx - (fx>=0?0:1), nx = x + 1,
+        y = (int)fy - (fy>=0?0:1), ny = y + 1;
+      const float
+        dx = fx - x,
+        dy = fy - y;
+      const Tfloat
+        Icc = (Tfloat)atXY(x,y,z,c,out_val),  Inc = (Tfloat)atXY(nx,y,z,c,out_val),
+        Icn = (Tfloat)atXY(x,ny,z,c,out_val), Inn = (Tfloat)atXY(nx,ny,z,c,out_val);
+      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
+    }
+
+    //! Read a pixel value using linear interpolation and Neumann boundary conditions (first two coordinates).
+    Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atXY() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atXY(fx,fy,z,c);
+    }
+
+    Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+      const float
+        nfx = fx<0?0:(fx>_width-1?_width-1:fx),
+        nfy = fy<0?0:(fy>_height-1?_height-1:fy);
+      const unsigned int
+        x = (unsigned int)nfx,
+        y = (unsigned int)nfy;
+      const float
+        dx = nfx - x,
+        dy = nfy - y;
+      const unsigned int
+        nx = dx>0?x+1:x,
+        ny = dy>0?y+1:y;
+      const Tfloat
+        Icc = (Tfloat)(*this)(x,y,z,c),  Inc = (Tfloat)(*this)(nx,y,z,c),
+        Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c);
+      return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
+    }
+
+    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first three coordinates).
+    Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T out_val) const {
+      const int
+        x = (int)fx - (fx>=0?0:1), nx = x + 1,
+        y = (int)fy - (fy>=0?0:1), ny = y + 1,
+        z = (int)fz - (fz>=0?0:1), nz = z + 1;
+      const float
+        dx = fx - x,
+        dy = fy - y,
+        dz = fz - z;
+      const Tfloat
+        Iccc = (Tfloat)atXYZ(x,y,z,c,out_val), Incc = (Tfloat)atXYZ(nx,y,z,c,out_val),
+        Icnc = (Tfloat)atXYZ(x,ny,z,c,out_val), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_val),
+        Iccn = (Tfloat)atXYZ(x,y,nz,c,out_val), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_val),
+        Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_val), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_val);
+      return Iccc +
+        dx*(Incc-Iccc +
+            dy*(Iccc+Innc-Icnc-Incc +
+                dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
+            dz*(Iccc+Incn-Iccn-Incc)) +
+        dy*(Icnc-Iccc +
+            dz*(Iccc+Icnn-Iccn-Icnc)) +
+        dz*(Iccn-Iccc);
+    }
+
+    //! Read a pixel value using linear interpolation and Neumann boundary conditions (first three coordinates).
+    Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atXYZ() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atXYZ(fx,fy,fz,c);
+    }
+
+    Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const {
+      const float
+        nfx = fx<0?0:(fx>_width-1?_width-1:fx),
+        nfy = fy<0?0:(fy>_height-1?_height-1:fy),
+        nfz = fz<0?0:(fz>_depth-1?_depth-1:fz);
+      const unsigned int
+        x = (unsigned int)nfx,
+        y = (unsigned int)nfy,
+        z = (unsigned int)nfz;
+      const float
+        dx = nfx - x,
+        dy = nfy - y,
+        dz = nfz - z;
+      const unsigned int
+        nx = dx>0?x+1:x,
+        ny = dy>0?y+1:y,
+        nz = dz>0?z+1:z;
+      const Tfloat
+        Iccc = (Tfloat)(*this)(x,y,z,c), Incc = (Tfloat)(*this)(nx,y,z,c),
+        Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c),
+        Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c),
+        Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c);
+      return Iccc +
+        dx*(Incc-Iccc +
+            dy*(Iccc+Innc-Icnc-Incc +
+                dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
+            dz*(Iccc+Incn-Iccn-Incc)) +
+        dy*(Icnc-Iccc +
+            dz*(Iccc+Icnn-Iccn-Icnc)) +
+        dz*(Iccn-Iccc);
+    }
+
+    //! Read a pixel value using linear interpolation and Dirichlet boundary conditions.
+    Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T out_val) const {
+      const int
+        x = (int)fx - (fx>=0?0:1), nx = x + 1,
+        y = (int)fy - (fy>=0?0:1), ny = y + 1,
+        z = (int)fz - (fz>=0?0:1), nz = z + 1,
+        c = (int)fc - (fc>=0?0:1), nc = c + 1;
+      const float
+        dx = fx - x,
+        dy = fy - y,
+        dz = fz - z,
+        dc = fc - c;
+      const Tfloat
+        Icccc = (Tfloat)atXYZC(x,y,z,c,out_val), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_val),
+        Icncc = (Tfloat)atXYZC(x,ny,z,c,out_val), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_val),
+        Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_val), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_val),
+        Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_val), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_val),
+        Icccn = (Tfloat)atXYZC(x,y,z,nc,out_val), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_val),
+        Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_val), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_val),
+        Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_val), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_val),
+        Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_val), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_val);
+      return Icccc +
+        dx*(Inccc-Icccc +
+            dy*(Icccc+Inncc-Icncc-Inccc +
+                dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
+                    dc*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
+                dc*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
+            dz*(Icccc+Incnc-Iccnc-Inccc +
+                dc*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
+            dc*(Icccc+Inccn-Inccc-Icccn)) +
+        dy*(Icncc-Icccc +
+            dz*(Icccc+Icnnc-Iccnc-Icncc +
+                dc*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
+            dc*(Icccc+Icncn-Icncc-Icccn)) +
+        dz*(Iccnc-Icccc +
+            dc*(Icccc+Iccnn-Iccnc-Icccn)) +
+        dc*(Icccn-Icccc);
+    }
+
+    //! Read a pixel value using linear interpolation and Neumann boundary conditions.
+    Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "linear_atXYZC() : Empty instance.",
+                                    cimg_instance);
+
+      return _linear_atXYZC(fx,fy,fz,fc);
+    }
+
+    Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const {
+      const float
+        nfx = fx<0?0:(fx>_width-1?_width-1:fx),
+        nfy = fy<0?0:(fy>_height-1?_height-1:fy),
+        nfz = fz<0?0:(fz>_depth-1?_depth-1:fz),
+        nfc = fc<0?0:(fc>_spectrum-1?_spectrum-1:fc);
+      const unsigned int
+        x = (unsigned int)nfx,
+        y = (unsigned int)nfy,
+        z = (unsigned int)nfz,
+        c = (unsigned int)nfc;
+      const float
+        dx = nfx - x,
+        dy = nfy - y,
+        dz = nfz - z,
+        dc = nfc - c;
+      const unsigned int
+        nx = dx>0?x+1:x,
+        ny = dy>0?y+1:y,
+        nz = dz>0?z+1:z,
+        nc = dc>0?c+1:c;
+      const Tfloat
+        Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c),
+        Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c),
+        Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c),
+        Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c),
+        Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc),
+        Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc),
+        Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc),
+        Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc);
+      return Icccc +
+        dx*(Inccc-Icccc +
+            dy*(Icccc+Inncc-Icncc-Inccc +
+                dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
+                    dc*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
+                dc*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
+            dz*(Icccc+Incnc-Iccnc-Inccc +
+                dc*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
+            dc*(Icccc+Inccn-Inccc-Icccn)) +
+        dy*(Icncc-Icccc +
+            dz*(Icccc+Icnnc-Iccnc-Icncc +
+                dc*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
+            dc*(Icccc+Icncn-Icncc-Icccn)) +
+        dz*(Iccnc-Icccc +
+            dc*(Icccc+Iccnn-Iccnc-Icccn)) +
+        dc*(Icccn-Icccc);
+    }
+
+    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions (first coordinates).
+    Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T out_val) const {
+      const int
+        x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2;
+      const float
+        dx = fx - x;
+      const Tfloat
+        Ip = (Tfloat)atX(px,y,z,c,out_val), Ic = (Tfloat)atX(x,y,z,c,out_val),
+        In = (Tfloat)atX(nx,y,z,c,out_val), Ia = (Tfloat)atX(ax,y,z,c,out_val);
+      return Ic + 0.5f*(dx*(-Ip+In) + dx*dx*(2*Ip-5*Ic+4*In-Ia) + dx*dx*dx*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions (first coordinates).
+    Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T out_val,
+                     const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = cubic_atX(fx,y,z,c,out_val);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates).
+    Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "cubic_atX() : Empty instance.",
+                                    cimg_instance);
+      return _cubic_atX(fx,y,z,c);
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates).
+    Tfloat cubic_atX(const float fx, const int y, const int z, const int c,
+                     const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = cubic_atX(fx,y,z,c);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates).
+    Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+      const float
+        nfx = fx<0?0:(fx>_width-1?_width-1:fx);
+      const int
+        x = (int)nfx;
+      const float
+        dx = nfx - x;
+      const int
+        px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=width()?width()-1:x+2;
+      const Tfloat
+        Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c),
+        In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c);
+      return Ic + 0.5f*(dx*(-Ip+In) + dx*dx*(2*Ip-5*Ic+4*In-Ia) + dx*dx*dx*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates).
+    Tfloat _cubic_atX(const float fx, const int y, const int z, const int c,
+                      const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = _cubic_atX(fx,y,z,c);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions.
+    Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T out_val) const {
+      const int
+        x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2,
+        y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2;
+      const float dx = fx - x, dy = fy - y;
+      const Tfloat
+        Ipp = (Tfloat)atXY(px,py,z,c,out_val), Icp = (Tfloat)atXY(x,py,z,c,out_val), Inp = (Tfloat)atXY(nx,py,z,c,out_val), Iap = (Tfloat)atXY(ax,py,z,c,out_val),
+        Ip = Icp + 0.5f*(dx*(-Ipp+Inp) + dx*dx*(2*Ipp-5*Icp+4*Inp-Iap) + dx*dx*dx*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ipc = (Tfloat)atXY(px,y,z,c,out_val),  Icc = (Tfloat)atXY(x, y,z,c,out_val), Inc = (Tfloat)atXY(nx,y,z,c,out_val),  Iac = (Tfloat)atXY(ax,y,z,c,out_val),
+        Ic = Icc + 0.5f*(dx*(-Ipc+Inc) + dx*dx*(2*Ipc-5*Icc+4*Inc-Iac) + dx*dx*dx*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ipn = (Tfloat)atXY(px,ny,z,c,out_val), Icn = (Tfloat)atXY(x,ny,z,c,out_val), Inn = (Tfloat)atXY(nx,ny,z,c,out_val), Ian = (Tfloat)atXY(ax,ny,z,c,out_val),
+        In = Icn + 0.5f*(dx*(-Ipn+Inn) + dx*dx*(2*Ipn-5*Icn+4*Inn-Ian) + dx*dx*dx*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ipa = (Tfloat)atXY(px,ay,z,c,out_val), Ica = (Tfloat)atXY(x,ay,z,c,out_val), Ina = (Tfloat)atXY(nx,ay,z,c,out_val), Iaa = (Tfloat)atXY(ax,ay,z,c,out_val),
+        Ia = Ica + 0.5f*(dx*(-Ipa+Ina) + dx*dx*(2*Ipa-5*Ica+4*Ina-Iaa) + dx*dx*dx*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dy*(-Ip+In) + dy*dy*(2*Ip-5*Ic+4*In-Ia) + dy*dy*dy*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions.
+    Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T out_val,
+                      const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = cubic_atXY(fx,fy,z,c,out_val);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "cubic_atXY() : Empty instance.",
+                                    cimg_instance);
+
+      return _cubic_atXY(fx,fy,z,c);
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c,
+                      const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = cubic_atXY(fx,fy,z,c);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+      const float
+        nfx = fx<0?0:(fx>_width-1?_width-1:fx),
+        nfy = fy<0?0:(fy>_height-1?_height-1:fy);
+      const int x = (int)nfx, y = (int)nfy;
+      const float dx = nfx - x, dy = nfy - y;
+      const int
+        px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=width()?width()-1:x+2,
+        py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=height()?height()-1:y+2;
+      const Tfloat
+        Ipp = (Tfloat)(*this)(px,py,z,c), Icp = (Tfloat)(*this)(x,py,z,c), Inp = (Tfloat)(*this)(nx,py,z,c), Iap = (Tfloat)(*this)(ax,py,z,c),
+        Ip = Icp + 0.5f*(dx*(-Ipp+Inp) + dx*dx*(2*Ipp-5*Icp+4*Inp-Iap) + dx*dx*dx*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ipc = (Tfloat)(*this)(px,y,z,c),  Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c),  Iac = (Tfloat)(*this)(ax,y,z,c),
+        Ic = Icc + 0.5f*(dx*(-Ipc+Inc) + dx*dx*(2*Ipc-5*Icc+4*Inc-Iac) + dx*dx*dx*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c), Ian = (Tfloat)(*this)(ax,ny,z,c),
+        In = Icn + 0.5f*(dx*(-Ipn+Inn) + dx*dx*(2*Ipn-5*Icn+4*Inn-Ian) + dx*dx*dx*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c), Iaa = (Tfloat)(*this)(ax,ay,z,c),
+        Ia = Ica + 0.5f*(dx*(-Ipa+Ina) + dx*dx*(2*Ipa-5*Ica+4*Ina-Iaa) + dx*dx*dx*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dy*(-Ip+In) + dy*dy*(2*Ip-5*Ic+4*In-Ia) + dy*dy*dy*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat _cubic_atXY(const float fx, const float fy, const int z, const int c,
+                       const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = _cubic_atXY(fx,fy,z,c);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions.
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T out_val) const {
+      const int
+        x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2,
+        y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2,
+        z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2;
+      const float dx = fx - x, dy = fy - y, dz = fz - z;
+      const Tfloat
+        Ippp = (Tfloat)atXYZ(px,py,pz,c,out_val), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_val),
+        Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_val), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_val),
+        Ipp = Icpp + 0.5f*(dx*(-Ippp+Inpp) + dx*dx*(2*Ippp-5*Icpp+4*Inpp-Iapp) + dx*dx*dx*(-Ippp+3*Icpp-3*Inpp+Iapp)),
+        Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_val),  Iccp = (Tfloat)atXYZ(x, y,pz,c,out_val),
+        Incp = (Tfloat)atXYZ(nx,y,pz,c,out_val),  Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_val),
+        Icp = Iccp + 0.5f*(dx*(-Ipcp+Incp) + dx*dx*(2*Ipcp-5*Iccp+4*Incp-Iacp) + dx*dx*dx*(-Ipcp+3*Iccp-3*Incp+Iacp)),
+        Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_val), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_val),
+        Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_val), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_val),
+        Inp = Icnp + 0.5f*(dx*(-Ipnp+Innp) + dx*dx*(2*Ipnp-5*Icnp+4*Innp-Ianp) + dx*dx*dx*(-Ipnp+3*Icnp-3*Innp+Ianp)),
+        Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_val), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_val),
+        Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_val), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_val),
+        Iap = Icap + 0.5f*(dx*(-Ipap+Inap) + dx*dx*(2*Ipap-5*Icap+4*Inap-Iaap) + dx*dx*dx*(-Ipap+3*Icap-3*Inap+Iaap)),
+        Ip = Icp + 0.5f*(dy*(-Ipp+Inp) + dy*dy*(2*Ipp-5*Icp+4*Inp-Iap) + dy*dy*dy*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ippc = (Tfloat)atXYZ(px,py,z,c,out_val), Icpc = (Tfloat)atXYZ(x,py,z,c,out_val),
+        Inpc = (Tfloat)atXYZ(nx,py,z,c,out_val), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_val),
+        Ipc = Icpc + 0.5f*(dx*(-Ippc+Inpc) + dx*dx*(2*Ippc-5*Icpc+4*Inpc-Iapc) + dx*dx*dx*(-Ippc+3*Icpc-3*Inpc+Iapc)),
+        Ipcc = (Tfloat)atXYZ(px,y,z,c,out_val),  Iccc = (Tfloat)atXYZ(x, y,z,c,out_val),
+        Incc = (Tfloat)atXYZ(nx,y,z,c,out_val),  Iacc = (Tfloat)atXYZ(ax,y,z,c,out_val),
+        Icc = Iccc + 0.5f*(dx*(-Ipcc+Incc) + dx*dx*(2*Ipcc-5*Iccc+4*Incc-Iacc) + dx*dx*dx*(-Ipcc+3*Iccc-3*Incc+Iacc)),
+        Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_val), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_val),
+        Innc = (Tfloat)atXYZ(nx,ny,z,c,out_val), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_val),
+        Inc = Icnc + 0.5f*(dx*(-Ipnc+Innc) + dx*dx*(2*Ipnc-5*Icnc+4*Innc-Ianc) + dx*dx*dx*(-Ipnc+3*Icnc-3*Innc+Ianc)),
+        Ipac = (Tfloat)atXYZ(px,ay,z,c,out_val), Icac = (Tfloat)atXYZ(x,ay,z,c,out_val),
+        Inac = (Tfloat)atXYZ(nx,ay,z,c,out_val), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_val),
+        Iac = Icac + 0.5f*(dx*(-Ipac+Inac) + dx*dx*(2*Ipac-5*Icac+4*Inac-Iaac) + dx*dx*dx*(-Ipac+3*Icac-3*Inac+Iaac)),
+        Ic = Icc + 0.5f*(dy*(-Ipc+Inc) + dy*dy*(2*Ipc-5*Icc+4*Inc-Iac) + dy*dy*dy*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ippn = (Tfloat)atXYZ(px,py,nz,c,out_val), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_val),
+        Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_val), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_val),
+        Ipn = Icpn + 0.5f*(dx*(-Ippn+Inpn) + dx*dx*(2*Ippn-5*Icpn+4*Inpn-Iapn) + dx*dx*dx*(-Ippn+3*Icpn-3*Inpn+Iapn)),
+        Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_val),  Iccn = (Tfloat)atXYZ(x, y,nz,c,out_val),
+        Incn = (Tfloat)atXYZ(nx,y,nz,c,out_val),  Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_val),
+        Icn = Iccn + 0.5f*(dx*(-Ipcn+Incn) + dx*dx*(2*Ipcn-5*Iccn+4*Incn-Iacn) + dx*dx*dx*(-Ipcn+3*Iccn-3*Incn+Iacn)),
+        Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_val), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_val),
+        Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_val), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_val),
+        Inn = Icnn + 0.5f*(dx*(-Ipnn+Innn) + dx*dx*(2*Ipnn-5*Icnn+4*Innn-Iann) + dx*dx*dx*(-Ipnn+3*Icnn-3*Innn+Iann)),
+        Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_val), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_val),
+        Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_val), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_val),
+        Ian = Ican + 0.5f*(dx*(-Ipan+Inan) + dx*dx*(2*Ipan-5*Ican+4*Inan-Iaan) + dx*dx*dx*(-Ipan+3*Ican-3*Inan+Iaan)),
+        In = Icn + 0.5f*(dy*(-Ipn+Inn) + dy*dy*(2*Ipn-5*Icn+4*Inn-Ian) + dy*dy*dy*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ippa = (Tfloat)atXYZ(px,py,az,c,out_val), Icpa = (Tfloat)atXYZ(x,py,az,c,out_val),
+        Inpa = (Tfloat)atXYZ(nx,py,az,c,out_val), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_val),
+        Ipa = Icpa + 0.5f*(dx*(-Ippa+Inpa) + dx*dx*(2*Ippa-5*Icpa+4*Inpa-Iapa) + dx*dx*dx*(-Ippa+3*Icpa-3*Inpa+Iapa)),
+        Ipca = (Tfloat)atXYZ(px,y,az,c,out_val),  Icca = (Tfloat)atXYZ(x, y,az,c,out_val),
+        Inca = (Tfloat)atXYZ(nx,y,az,c,out_val),  Iaca = (Tfloat)atXYZ(ax,y,az,c,out_val),
+        Ica = Icca + 0.5f*(dx*(-Ipca+Inca) + dx*dx*(2*Ipca-5*Icca+4*Inca-Iaca) + dx*dx*dx*(-Ipca+3*Icca-3*Inca+Iaca)),
+        Ipna = (Tfloat)atXYZ(px,ny,az,c,out_val), Icna = (Tfloat)atXYZ(x,ny,az,c,out_val),
+        Inna = (Tfloat)atXYZ(nx,ny,az,c,out_val), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_val),
+        Ina = Icna + 0.5f*(dx*(-Ipna+Inna) + dx*dx*(2*Ipna-5*Icna+4*Inna-Iana) + dx*dx*dx*(-Ipna+3*Icna-3*Inna+Iana)),
+        Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_val), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_val),
+        Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_val), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_val),
+        Iaa = Icaa + 0.5f*(dx*(-Ipaa+Inaa) + dx*dx*(2*Ipaa-5*Icaa+4*Inaa-Iaaa) + dx*dx*dx*(-Ipaa+3*Icaa-3*Inaa+Iaaa)),
+        Ia = Ica + 0.5f*(dy*(-Ipa+Ina) + dy*dy*(2*Ipa-5*Ica+4*Ina-Iaa) + dy*dy*dy*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dz*(-Ip+In) + dz*dz*(2*Ip-5*Ic+4*In-Ia) + dz*dz*dz*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Read a pixel value using cubic interpolation and Dirichlet boundary conditions.
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T out_val,
+                       const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = cubic_atXYZ(fx,fy,fz,c,out_val);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "cubic_atXYZ() : Empty instance.",
+                                    cimg_instance);
+
+      return _cubic_atXYZ(fx,fy,fz,c);
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c,
+                       const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = cubic_atXYZ(fx,fy,fz,c);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const {
+      const float
+        nfx = fx<0?0:(fx>_width-1?_width-1:fx),
+        nfy = fy<0?0:(fy>_height-1?_height-1:fy),
+        nfz = fz<0?0:(fz>_depth-1?_depth-1:fz);
+      const int x = (int)nfx, y = (int)nfy, z = (int)nfz;
+      const float dx = nfx - x, dy = nfy - y, dz = nfz - z;
+      const int
+        px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=width()?width()-1:x+2,
+        py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=height()?height()-1:y+2,
+        pz = z-1<0?0:z-1, nz = dz>0?z+1:z, az = z+2>=depth()?depth()-1:z+2;
+      const Tfloat
+        Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c),
+        Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c),
+        Ipp = Icpp + 0.5f*(dx*(-Ippp+Inpp) + dx*dx*(2*Ippp-5*Icpp+4*Inpp-Iapp) + dx*dx*dx*(-Ippp+3*Icpp-3*Inpp+Iapp)),
+        Ipcp = (Tfloat)(*this)(px,y,pz,c),  Iccp = (Tfloat)(*this)(x, y,pz,c),
+        Incp = (Tfloat)(*this)(nx,y,pz,c),  Iacp = (Tfloat)(*this)(ax,y,pz,c),
+        Icp = Iccp + 0.5f*(dx*(-Ipcp+Incp) + dx*dx*(2*Ipcp-5*Iccp+4*Incp-Iacp) + dx*dx*dx*(-Ipcp+3*Iccp-3*Incp+Iacp)),
+        Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c),
+        Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c),
+        Inp = Icnp + 0.5f*(dx*(-Ipnp+Innp) + dx*dx*(2*Ipnp-5*Icnp+4*Innp-Ianp) + dx*dx*dx*(-Ipnp+3*Icnp-3*Innp+Ianp)),
+        Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c),
+        Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c),
+        Iap = Icap + 0.5f*(dx*(-Ipap+Inap) + dx*dx*(2*Ipap-5*Icap+4*Inap-Iaap) + dx*dx*dx*(-Ipap+3*Icap-3*Inap+Iaap)),
+        Ip = Icp + 0.5f*(dy*(-Ipp+Inp) + dy*dy*(2*Ipp-5*Icp+4*Inp-Iap) + dy*dy*dy*(-Ipp+3*Icp-3*Inp+Iap)),
+        Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c),
+        Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c),
+        Ipc = Icpc + 0.5f*(dx*(-Ippc+Inpc) + dx*dx*(2*Ippc-5*Icpc+4*Inpc-Iapc) + dx*dx*dx*(-Ippc+3*Icpc-3*Inpc+Iapc)),
+        Ipcc = (Tfloat)(*this)(px,y,z,c),  Iccc = (Tfloat)(*this)(x, y,z,c),
+        Incc = (Tfloat)(*this)(nx,y,z,c),  Iacc = (Tfloat)(*this)(ax,y,z,c),
+        Icc = Iccc + 0.5f*(dx*(-Ipcc+Incc) + dx*dx*(2*Ipcc-5*Iccc+4*Incc-Iacc) + dx*dx*dx*(-Ipcc+3*Iccc-3*Incc+Iacc)),
+        Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c),
+        Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c),
+        Inc = Icnc + 0.5f*(dx*(-Ipnc+Innc) + dx*dx*(2*Ipnc-5*Icnc+4*Innc-Ianc) + dx*dx*dx*(-Ipnc+3*Icnc-3*Innc+Ianc)),
+        Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c),
+        Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c),
+        Iac = Icac + 0.5f*(dx*(-Ipac+Inac) + dx*dx*(2*Ipac-5*Icac+4*Inac-Iaac) + dx*dx*dx*(-Ipac+3*Icac-3*Inac+Iaac)),
+        Ic = Icc + 0.5f*(dy*(-Ipc+Inc) + dy*dy*(2*Ipc-5*Icc+4*Inc-Iac) + dy*dy*dy*(-Ipc+3*Icc-3*Inc+Iac)),
+        Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c),
+        Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c),
+        Ipn = Icpn + 0.5f*(dx*(-Ippn+Inpn) + dx*dx*(2*Ippn-5*Icpn+4*Inpn-Iapn) + dx*dx*dx*(-Ippn+3*Icpn-3*Inpn+Iapn)),
+        Ipcn = (Tfloat)(*this)(px,y,nz,c),  Iccn = (Tfloat)(*this)(x, y,nz,c),
+        Incn = (Tfloat)(*this)(nx,y,nz,c),  Iacn = (Tfloat)(*this)(ax,y,nz,c),
+        Icn = Iccn + 0.5f*(dx*(-Ipcn+Incn) + dx*dx*(2*Ipcn-5*Iccn+4*Incn-Iacn) + dx*dx*dx*(-Ipcn+3*Iccn-3*Incn+Iacn)),
+        Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c),
+        Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c),
+        Inn = Icnn + 0.5f*(dx*(-Ipnn+Innn) + dx*dx*(2*Ipnn-5*Icnn+4*Innn-Iann) + dx*dx*dx*(-Ipnn+3*Icnn-3*Innn+Iann)),
+        Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c),
+        Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c),
+        Ian = Ican + 0.5f*(dx*(-Ipan+Inan) + dx*dx*(2*Ipan-5*Ican+4*Inan-Iaan) + dx*dx*dx*(-Ipan+3*Ican-3*Inan+Iaan)),
+        In = Icn + 0.5f*(dy*(-Ipn+Inn) + dy*dy*(2*Ipn-5*Icn+4*Inn-Ian) + dy*dy*dy*(-Ipn+3*Icn-3*Inn+Ian)),
+        Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c),
+        Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c),
+        Ipa = Icpa + 0.5f*(dx*(-Ippa+Inpa) + dx*dx*(2*Ippa-5*Icpa+4*Inpa-Iapa) + dx*dx*dx*(-Ippa+3*Icpa-3*Inpa+Iapa)),
+        Ipca = (Tfloat)(*this)(px,y,az,c),  Icca = (Tfloat)(*this)(x, y,az,c),
+        Inca = (Tfloat)(*this)(nx,y,az,c),  Iaca = (Tfloat)(*this)(ax,y,az,c),
+        Ica = Icca + 0.5f*(dx*(-Ipca+Inca) + dx*dx*(2*Ipca-5*Icca+4*Inca-Iaca) + dx*dx*dx*(-Ipca+3*Icca-3*Inca+Iaca)),
+        Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c),
+        Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c),
+        Ina = Icna + 0.5f*(dx*(-Ipna+Inna) + dx*dx*(2*Ipna-5*Icna+4*Inna-Iana) + dx*dx*dx*(-Ipna+3*Icna-3*Inna+Iana)),
+        Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c),
+        Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c),
+        Iaa = Icaa + 0.5f*(dx*(-Ipaa+Inaa) + dx*dx*(2*Ipaa-5*Icaa+4*Inaa-Iaaa) + dx*dx*dx*(-Ipaa+3*Icaa-3*Inaa+Iaaa)),
+        Ia = Ica + 0.5f*(dy*(-Ipa+Ina) + dy*dy*(2*Ipa-5*Ica+4*Ina-Iaa) + dy*dy*dy*(-Ipa+3*Ica-3*Ina+Iaa));
+      return Ic + 0.5f*(dz*(-Ip+In) + dz*dz*(2*Ip-5*Ic+4*In-Ia) + dz*dz*dz*(-Ip+3*Ic-3*In+Ia));
+    }
+
+    //! Read a pixel value using cubic interpolation and Neumann boundary conditions.
+    Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c,
+                        const Tfloat min_val, const Tfloat max_val) const {
+      const Tfloat val = _cubic_atXYZ(fx,fy,fz,c);
+      return val<min_val?min_val:val>max_val?max_val:val;
+    }
+
+    //! Set a pixel value, with 3d float coordinates, using linear interpolation.
+    CImg<T>& set_linear_atXYZ(const T& val, const float fx, const float fy=0, const float fz=0, const int c=0,
+                              const bool add=false) {
+      const int
+        x = (int)fx - (fx>=0?0:1), nx = x + 1,
+        y = (int)fy - (fy>=0?0:1), ny = y + 1,
+        z = (int)fz - (fz>=0?0:1), nz = z + 1;
+      const float
+        dx = fx - x,
+        dy = fy - y,
+        dz = fz - z;
+      if (c>=0 && c<spectrum()) {
+        if (z>=0 && z<depth()) {
+          if (y>=0 && y<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*(1-dy)*(1-dz), w2 = add?1:(1-w1);
+              (*this)(x,y,z,c) = (T)(w1*val + w2*(*this)(x,y,z,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*(1-dy)*(1-dz), w2 = add?1:(1-w1);
+              (*this)(nx,y,z,c) = (T)(w1*val + w2*(*this)(nx,y,z,c));
+            }
+          }
+          if (ny>=0 && ny<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*dy*(1-dz), w2 = add?1:(1-w1);
+              (*this)(x,ny,z,c) = (T)(w1*val + w2*(*this)(x,ny,z,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*dy*(1-dz), w2 = add?1:(1-w1);
+              (*this)(nx,ny,z,c) = (T)(w1*val + w2*(*this)(nx,ny,z,c));
+            }
+          }
+        }
+        if (nz>=0 && nz<depth()) {
+          if (y>=0 && y<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
+              (*this)(x,y,nz,c) = (T)(w1*val + w2*(*this)(x,y,nz,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
+              (*this)(nx,y,nz,c) = (T)(w1*val + w2*(*this)(nx,y,nz,c));
+            }
+          }
+          if (ny>=0 && ny<height()) {
+            if (x>=0 && x<width()) {
+              const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
+              (*this)(x,ny,nz,c) = (T)(w1*val + w2*(*this)(x,ny,nz,c));
+            }
+            if (nx>=0 && nx<width()) {
+              const float w1 = dx*dy, w2 = add?1:(1-w1);
+              (*this)(nx,ny,nz,c) = (T)(w1*val + w2*(*this)(nx,ny,nz,c));
+            }
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Set a pixel value, with 2d float coordinates, using linear interpolation.
+    CImg<T>& set_linear_atXY(const T& val, const float fx, const float fy=0, const int z=0, const int c=0,
+                             const bool add=false) {
+      const int
+        x = (int)fx - (fx>=0?0:1), nx = x + 1,
+        y = (int)fy - (fy>=0?0:1), ny = y + 1;
+      const float
+        dx = fx - x,
+        dy = fy - y;
+      if (z>=0 && z<depth() && c>=0 && c<spectrum()) {
+        if (y>=0 && y<height()) {
+          if (x>=0 && x<width()) {
+            const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
+            (*this)(x,y,z,c) = (T)(w1*val + w2*(*this)(x,y,z,c));
+          }
+          if (nx>=0 && nx<width()) {
+            const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
+            (*this)(nx,y,z,c) = (T)(w1*val + w2*(*this)(nx,y,z,c));
+          }
+        }
+        if (ny>=0 && ny<height()) {
+          if (x>=0 && x<width()) {
+            const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
+            (*this)(x,ny,z,c) = (T)(w1*val + w2*(*this)(x,ny,z,c));
+          }
+          if (nx>=0 && nx<width()) {
+            const float w1 = dx*dy, w2 = add?1:(1-w1);
+            (*this)(nx,ny,z,c) = (T)(w1*val + w2*(*this)(nx,ny,z,c));
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Return a C-string containing the values of the instance image.
+    CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
+      if (is_empty()) return CImg<charT>(1,1,1,1,0);
+      const unsigned int siz = (unsigned int)size();
+      CImgList<charT> items;
+      char item[256] = { 0 };
+      const T *ptrs = _data;
+      for (unsigned int off = 0; off<siz-1; ++off) {
+        cimg_snprintf(item,sizeof(item),cimg::type<T>::format(),cimg::type<T>::format(*(ptrs++)));
+        const unsigned int l = std::strlen(item);
+        CImg<charT>(item,l+1).move_to(items).back()[l] = separator;
+      }
+      cimg_snprintf(item,sizeof(item),cimg::type<T>::format(),cimg::type<T>::format(*ptrs));
+      CImg<charT>(item,std::strlen(item)+1).move_to(items);
+      CImg<ucharT> res; (items>'x').move_to(res);
+      if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
+      return res;
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Checking
+    //@{
+    //-------------------------------------
+
+    //! Return \c true if current image has shared memory.
+    bool is_shared() const {
+      return _is_shared;
+    }
+
+    //! Return \c true if current image is empty.
+    bool is_empty() const {
+      return !(_data && _width && _height && _depth && _spectrum);
+    }
+
+    //! Return \c true if image (*this) has the specified width.
+    bool is_sameX(const unsigned int dx) const {
+      return (_width==dx);
+    }
+
+    //! Return \c true if images \c (*this) and \c img have same width.
+    template<typename t>
+    bool is_sameX(const CImg<t>& img) const {
+      return is_sameX(img._width);
+    }
+
+    //! Return \c true if images \c (*this) and the display \c disp have same width.
+    bool is_sameX(const CImgDisplay& disp) const {
+      return is_sameX((unsigned int)disp.width());
+    }
+
+    //! Return \c true if image (*this) has the specified height.
+    bool is_sameY(const unsigned int dy) const {
+      return (_height==dy);
+    }
+
+    //! Return \c true if images \c (*this) and \c img have same height.
+    template<typename t>
+    bool is_sameY(const CImg<t>& img) const {
+      return is_sameY(img._height);
+    }
+
+    //! Return \c true if images \c (*this) and the display \c disp have same height.
+    bool is_sameY(const CImgDisplay& disp) const {
+      return is_sameY((unsigned int)disp.height());
+    }
+
+    //! Return \c true if image (*this) has the specified depth.
+    bool is_sameZ(const unsigned int dz) const {
+      return (_depth==dz);
+    }
+
+    //! Return \c true if images \c (*this) and \c img have same depth.
+    template<typename t>
+    bool is_sameZ(const CImg<t>& img) const {
+      return is_sameZ(img._depth);
+    }
+
+    //! Return \c true if image (*this) has the specified number of channels.
+    bool is_sameC(const unsigned int dc) const {
+      return (_spectrum==dc);
+    }
+
+    //! Return \c true if images \c (*this) and \c img have same _spectrum.
+    template<typename t>
+    bool is_sameC(const CImg<t>& img) const {
+      return is_sameC(img._spectrum);
+    }
+
+    //! Return \c true if image (*this) has the specified width and height.
+    bool is_sameXY(const unsigned int dx, const unsigned int dy) const {
+      return (is_sameX(dx) && is_sameY(dy));
+    }
+
+    //! Return \c true if images have same width and same height.
+    template<typename t>
+    bool is_sameXY(const CImg<t>& img) const {
+      return (is_sameX(img) && is_sameY(img));
+    }
+
+    //! Return \c true if image \c (*this) and the display \c disp have same width and same height.
+    bool is_sameXY(const CImgDisplay& disp) const {
+      return (is_sameX(disp) && is_sameY(disp));
+    }
+
+    //! Return \c true if image (*this) has the specified width and depth.
+    bool is_sameXZ(const unsigned int dx, const unsigned int dz) const {
+      return (is_sameX(dx) && is_sameZ(dz));
+    }
+
+    //! Return \c true if images have same width and same depth.
+    template<typename t>
+    bool is_sameXZ(const CImg<t>& img) const {
+      return (is_sameX(img) && is_sameZ(img));
+    }
+
+    //! Return \c true if image (*this) has the specified width and number of channels.
+    bool is_sameXC(const unsigned int dx, const unsigned int dc) const {
+      return (is_sameX(dx) && is_sameC(dc));
+    }
+
+    //! Return \c true if images have same width and same number of channels.
+    template<typename t>
+    bool is_sameXC(const CImg<t>& img) const {
+      return (is_sameX(img) && is_sameC(img));
+    }
+
+    //! Return \c true if image (*this) has the specified height and depth.
+    bool is_sameYZ(const unsigned int dy, const unsigned int dz) const {
+      return (is_sameY(dy) && is_sameZ(dz));
+    }
+
+    //! Return \c true if images have same height and same depth.
+    template<typename t>
+    bool is_sameYZ(const CImg<t>& img) const {
+      return (is_sameY(img) && is_sameZ(img));
+    }
+
+    //! Return \c true if image (*this) has the specified height and number of channels.
+    bool is_sameYC(const unsigned int dy, const unsigned int dc) const {
+      return (is_sameY(dy) && is_sameC(dc));
+    }
+
+    //! Return \c true if images have same height and same number of channels.
+    template<typename t>
+    bool is_sameYC(const CImg<t>& img) const {
+      return (is_sameY(img) && is_sameC(img));
+    }
+
+    //! Return \c true if image (*this) has the specified depth and number of channels.
+    bool is_sameZC(const unsigned int dz, const unsigned int dc) const {
+      return (is_sameZ(dz) && is_sameC(dc));
+    }
+
+    //! Return \c true if images have same depth and same number of channels.
+    template<typename t>
+    bool is_sameZC(const CImg<t>& img) const {
+      return (is_sameZ(img) && is_sameC(img));
+    }
+
+    //! Return \c true if image (*this) has the specified width, height and depth.
+    bool is_sameXYZ(const unsigned int dx, const unsigned int dy, const unsigned int dz) const {
+      return (is_sameXY(dx,dy) && is_sameZ(dz));
+    }
+
+    //! Return \c true if images have same width, same height and same depth.
+    template<typename t>
+    bool is_sameXYZ(const CImg<t>& img) const {
+      return (is_sameXY(img) && is_sameZ(img));
+    }
+
+    //! Return \c true if image (*this) has the specified width, height and depth.
+    bool is_sameXYC(const unsigned int dx, const unsigned int dy, const unsigned int dc) const {
+      return (is_sameXY(dx,dy) && is_sameC(dc));
+    }
+
+    //! Return \c true if images have same width, same height and same number of channels.
+    template<typename t>
+    bool is_sameXYC(const CImg<t>& img) const {
+      return (is_sameXY(img) && is_sameC(img));
+    }
+
+    //! Return \c true if image (*this) has the specified width, height and number of channels.
+    bool is_sameXZC(const unsigned int dx, const unsigned int dz, const unsigned int dc) const {
+      return (is_sameXZ(dx,dz) && is_sameC(dc));
+    }
+
+    //! Return \c true if images have same width, same depth and same number of channels.
+    template<typename t>
+    bool is_sameXZC(const CImg<t>& img) const {
+      return (is_sameXZ(img) && is_sameC(img));
+    }
+
+    //! Return \c true if image (*this) has the specified height, depth and number of channels.
+    bool is_sameYZC(const unsigned int dy, const unsigned int dz, const unsigned int dc) const {
+      return (is_sameYZ(dy,dz) && is_sameC(dc));
+    }
+
+    //! Return \c true if images have same heigth, same depth and same number of channels.
+    template<typename t>
+    bool is_sameYZC(const CImg<t>& img) const {
+      return (is_sameYZ(img) && is_sameC(img));
+    }
+
+    //! Return \c true if image (*this) has the specified width, height, depth and number of channels.
+    bool is_sameXYZC(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc) const {
+      return (is_sameXYZ(dx,dy,dz) && is_sameC(dc));
+    }
+
+    //! 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_sameXYZC(const CImg<t>& img) const {
+      return (is_sameXYZ(img) && is_sameC(img));
+    }
+
+    //! Return \c true if pixel (x,y,z,c) is inside image boundaries.
+    bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const {
+      return !is_empty() && x>=0 && x<width() && y>=0 && y<height() && z>=0 && z<depth() && c>=0 && c<spectrum();
+    }
+
+    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z,c).
+    template<typename t>
+    bool contains(const T& pixel, t& x, t& y, t& z, t& c) const {
+      const unsigned int wh = _width*_height, whd = wh*_depth, siz = whd*_spectrum;
+      const T *const ppixel = &pixel;
+      if (is_empty() || ppixel<_data || ppixel>=_data+siz) return false;
+      unsigned int off = (unsigned int)(ppixel - _data);
+      const unsigned int nc = off/whd;
+      off%=whd;
+      const unsigned int nz = off/wh;
+      off%=wh;
+      const unsigned int ny = off/_width, nx = off%_width;
+      x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc;
+      return true;
+    }
+
+    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z).
+    template<typename t>
+    bool contains(const T& pixel, t& x, t& y, t& z) const {
+      const unsigned int wh = _width*_height, whd = wh*_depth, siz = whd*_spectrum;
+      const T *const ppixel = &pixel;
+      if (is_empty() || ppixel<_data || ppixel>=_data+siz) return false;
+      unsigned int off = ((unsigned int)(ppixel - _data))%whd;
+      const unsigned int nz = off/wh;
+      off%=wh;
+      const unsigned int ny = off/_width, nx = off%_width;
+      x = (t)nx; y = (t)ny; z = (t)nz;
+      return true;
+    }
+
+    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y).
+    template<typename t>
+    bool contains(const T& pixel, t& x, t& y) const {
+      const unsigned int wh = _width*_height, siz = wh*_depth*_spectrum;
+      const T *const ppixel = &pixel;
+      if (is_empty() || ppixel<_data || ppixel>=_data+siz) return false;
+      unsigned int off = ((unsigned int)(ppixel - _data))%wh;
+      const unsigned int ny = off/_width, nx = off%_width;
+      x = (t)nx; y = (t)ny;
+      return true;
+    }
+
+    //! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x).
+    template<typename t>
+    bool contains(const T& pixel, t& x) const {
+      const T *const ppixel = &pixel;
+      if (is_empty() || ppixel<_data || ppixel>=_data+size()) return false;
+      x = (t)(((unsigned int)(ppixel - _data))%_width);
+      return true;
+    }
+
+    //! Return \c true if specified referenced value is inside the image boundaries.
+    bool contains(const T& pixel) const {
+      const T *const ppixel = &pixel;
+      return !is_empty() && ppixel>=_data && ppixel<_data + size();
+    }
+
+    //! Return \c true if the memory buffers of the two images overlaps.
+    template<typename t>
+    bool is_overlapped(const CImg<t>& img) const {
+      const unsigned int csiz = size(), isiz = img.size();
+      return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz));
+    }
+
+    //! Return true if the set (instance,primitives,colors,opacities) stands for a valid 3d object.
+    template<typename tp, typename tc, typename to>
+    bool is_object3d(const CImgList<tp>& primitives,
+                     const CImgList<tc>& colors,
+                     const to& opacities,
+                     const bool full_check=true,
+                     char *const error_message=0) const {
+      if (error_message) *error_message = 0;
+
+      // Check consistency for the particular case of an empty 3d object.
+      if (is_empty()) {
+        if (primitives || colors || opacities) {
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has no vertices but %u primitives, %u colors and %u opacities",
+                                          primitives._width,colors._width,opacities.size());
+          return false;
+        }
+        return true;
+      }
+
+      // Check consistency of vertices.
+      if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions.
+        if (error_message) std::sprintf(error_message,
+                                        "3d object (%u,%u) has invalid vertices dimensions (%u,%u,%u,%u)",
+                                        _width,primitives._width,_width,_height,_depth,_spectrum);
+        return false;
+      }
+      if (colors._width>primitives._width+1) {
+        if (error_message) std::sprintf(error_message,
+                                        "3d object (%u,%u) defines %u colors",
+                                        _width,primitives._width,colors._width);
+        return false;
+      }
+      if (opacities.size()>primitives._width) {
+        if (error_message) std::sprintf(error_message,
+                                        "3d object (%u,%u) defines %u opacities",
+                                        _width,primitives._width,opacities.size());
+        return false;
+      }
+      if (!full_check) return true;
+
+      // Check consistency of primitives.
+      cimglist_for(primitives,l) {
+        const CImg<tp>& primitive = primitives[l];
+        const unsigned int psiz = primitive.size();
+        switch (psiz) {
+        case 1 : { // Point.
+          const unsigned int i0 = (unsigned int)primitive(0);
+          if (i0>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indice %u in point primitive %u",
+                                            _width,primitives._width,i0,l);
+            return false;
+          }
+        } break;
+        case 5 : { // Sphere.
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1);
+          if (i0>=_width || i1>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u) in sphere primitive %u",
+                                            _width,primitives._width,i0,i1,l);
+            return false;
+          }
+        } break;
+        case 2 : // Segment.
+        case 6 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1);
+          if (i0>=_width || i1>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u) in segment primitive %u",
+                                            _width,primitives._width,i0,i1,l);
+            return false;
+          }
+        } break;
+        case 3 : // Triangle.
+        case 9 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1),
+            i2 = (unsigned int)primitive(2);
+          if (i0>=_width || i1>=_width || i2>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u,%u) in triangle primitive %u",
+                                            _width,primitives._width,i0,i1,i2,l);
+            return false;
+          }
+        } break;
+        case 4 : // Quadrangle.
+        case 12 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1),
+            i2 = (unsigned int)primitive(2),
+            i3 = (unsigned int)primitive(3);
+          if (i0>=_width || i1>=_width || i2>=_width || i3>=_width) {
+            if (error_message) std::sprintf(error_message,
+                                            "3d object (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in quadrangle primitive %u",
+                                            _width,primitives._width,i0,i1,i2,i3,l);
+            return false;
+          }
+        } break;
+        default :
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has invalid primitive %u of size %u",
+                                          l,psiz);
+          return false;
+        }
+      }
+
+      // Check consistency of colors.
+      cimglist_for(colors,c) {
+        const CImg<tc>& color = colors[c];
+        if (!color) {
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has empty color for primitive %u",
+                                          c);
+          return false;
+        }
+      }
+
+      // Check consistency of light texture.
+      if (colors._width>primitives._width) {
+        const CImg<tc> &light = colors.back();
+        if (!light || light._depth>1) {
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has invalid light texture (%u,%u,%u,%u)",
+                                          light._width,light._height,light._depth,light._spectrum);
+          return false;
+        }
+      }
+
+      // Check consistency of opacities.
+      return _is_object3d(opacities,error_message);
+    }
+
+    template<typename to>
+    bool _is_object3d(const CImgList<to>& opacities, char *const error_message) const {
+      cimglist_for(opacities,o) {
+        const CImg<to>& opacity = opacities[o];
+        if (opacity._spectrum!=1) {
+          if (error_message) std::sprintf(error_message,
+                                          "3d object has invalid opacity (%u,%u,%u,%u) for primitive %u",
+                                          opacity._width,opacity._height,opacity._depth,opacity._spectrum,o);
+          return false;
+        }
+      }
+      return true;
+    }
+
+    template<typename to>
+    bool _is_object3d(const CImg<to>&, char *const) const {
+      return true;
+    }
+
+    //! Test if an image is a valid CImg3d object.
+    bool is_CImg3d(const bool full_check=true, char *const error_message=0) const {
+      if (error_message) *error_message = 0;
+
+      // Check instance dimension and header.
+      if (_width!=1 || _height<8 || _depth!=1 || _spectrum!=1) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d has invalid dimensions (%u,%u,%u,%u)",
+                                        _width,_height,_depth,_spectrum);
+        return false;
+      }
+      const T *ptrs = _data, *const ptre = end();
+      if (!_is_CImg3d(*(ptrs++),'C') || !_is_CImg3d(*(ptrs++),'I') || !_is_CImg3d(*(ptrs++),'m') ||
+          !_is_CImg3d(*(ptrs++),'g') || !_is_CImg3d(*(ptrs++),'3') || !_is_CImg3d(*(ptrs++),'d')) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d header not found");
+        return false;
+      }
+      const unsigned int nb_points = (unsigned int)*(ptrs++), nb_primitives = (unsigned int)*(ptrs++);
+
+      // Check consistency of vertex data.
+      if (!nb_points) {
+        if (nb_primitives) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d has no vertices but %u primitives",
+                                          nb_primitives);
+          return false;
+        }
+        if (ptrs!=ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) is empty but contains %u byte%s more than expected",
+                                          nb_points,nb_primitives,(unsigned int)(ptre-ptrs),(ptre-ptrs)>1?"s":"");
+          return false;
+        }
+        return true;
+      }
+      ptrs+=3*nb_points;
+      if (ptrs>ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has incomplete vertex data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      if (!full_check) return true;
+
+      // Check primitive consistency.
+      if (ptrs==ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has no primitive data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      for (unsigned int p = 0; p<nb_primitives; ++p) {
+        const unsigned int nb_inds = (unsigned int)*(ptrs++);
+        switch (nb_inds) {
+        case 1 : { // Point.
+          const unsigned int ind0 = (unsigned int)*(ptrs++);
+          if (ind0>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive %u",
+                                            nb_points,nb_primitives,ind0,p);
+            return false;
+          }
+        } break;
+        case 5 : { // Sphere.
+          const unsigned int
+            i0 = (unsigned int)*(ptrs++),
+            i1 = (unsigned int)*(ptrs++);
+          ptrs+=3;
+          if (i0>=nb_points || i1>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in sphere primitive %u",
+                                            nb_points,nb_primitives,i0,i1,p);
+            return false;
+          }
+        } break;
+        case 2 : case 6 : { // Segment.
+          const unsigned int
+            i0 = (unsigned int)*(ptrs++),
+            i1 = (unsigned int)*(ptrs++);
+          if (nb_inds==6) ptrs+=4;
+          if (i0>=nb_points || i1>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in segment primitive %u",
+                                            nb_points,nb_primitives,i0,i1,p);
+            return false;
+          }
+        } break;
+        case 3 : case 9 : { // Triangle.
+          const unsigned int
+            i0 = (unsigned int)*(ptrs++),
+            i1 = (unsigned int)*(ptrs++),
+            i2 = (unsigned int)*(ptrs++);
+          if (nb_inds==9) ptrs+=6;
+          if (i0>=nb_points || i1>=nb_points || i2>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in triangle primitive %u",
+                                            nb_points,nb_primitives,i0,i1,i2,p);
+            return false;
+          }
+        } break;
+        case 4 : case 12 : { // Quadrangle.
+          const unsigned int
+            i0 = (unsigned int)*(ptrs++),
+            i1 = (unsigned int)*(ptrs++),
+            i2 = (unsigned int)*(ptrs++),
+            i3 = (unsigned int)*(ptrs++);
+          if (nb_inds==12) ptrs+=8;
+          if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) {
+            if (error_message) std::sprintf(error_message,
+                                            "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u,%u) in quadrangle primitive %u",
+                                            nb_points,nb_primitives,i0,i1,i2,i3,p);
+            return false;
+          }
+        } break;
+        default :
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has invalid primitive %u of size %u",
+                                          nb_points,nb_primitives,p,nb_inds);
+          return false;
+        }
+        if (ptrs>ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has incomplete primitive data for primitive %u",
+                                          nb_points,nb_primitives,p);
+          return false;
+        }
+      }
+
+      // Check color consistency.
+      if (ptrs==ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has no color/texture data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      for (unsigned int c = 0; c<nb_primitives; ++c) {
+        if ((int)*(ptrs++)!=-128) ptrs+=2;
+        else if ((ptrs+=3)<ptre) {
+          const unsigned int w = (unsigned int)*(ptrs-3), h = (unsigned int)*(ptrs-2), s = (unsigned int)*(ptrs-1);
+          if (!h && !s) {
+            if (w>=c) {
+              if (error_message) std::sprintf(error_message,
+                                              "CImg3d (%u,%u) refers to invalid shared sprite/texture indice %u for primitive %u",
+                                              nb_points,nb_primitives,w,c);
+              return false;
+            }
+          } else ptrs+=w*h*s;
+        }
+        if (ptrs>ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has incomplete color/texture data for primitive %u",
+                                          nb_points,nb_primitives,c);
+          return false;
+        }
+      }
+
+      // Check opacity consistency.
+      if (ptrs==ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) has no opacity data",
+                                        nb_points,nb_primitives);
+        return false;
+      }
+      for (unsigned int o = 0; o<nb_primitives; ++o) {
+        if ((int)*(ptrs++)==-128 && (ptrs+=3)<ptre) {
+          const unsigned int w = (unsigned int)*(ptrs-3), h = (unsigned int)*(ptrs-2), s = (unsigned int)*(ptrs-1);
+          if (!h && !s) {
+            if (w>=o) {
+              if (error_message) std::sprintf(error_message,
+                                              "CImg3d (%u,%u) refers to invalid shared opacity indice %u for primitive %u",
+                                              nb_points,nb_primitives,w,o);
+              return false;
+            }
+          } else ptrs+=w*h*s;
+        }
+        if (ptrs>ptre) {
+          if (error_message) std::sprintf(error_message,
+                                          "CImg3d (%u,%u) has incomplete opacity data for primitive %u",
+                                          nb_points,nb_primitives,o);
+          return false;
+        }
+      }
+
+      // Check end of data.
+      if (ptrs<ptre) {
+        if (error_message) std::sprintf(error_message,
+                                        "CImg3d (%u,%u) contains %u byte%s more than expected",
+                                        nb_points,nb_primitives,(unsigned int)(ptre-ptrs),(ptre-ptrs)>1?"s":"");
+        return false;
+      }
+      return true;
+    }
+
+    static bool _is_CImg3d(const T val, const char c) {
+      if (val>=(T)c && val<(T)(c+1)) return true;
+      return false;
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Mathematical Functions
+    //@{
+    //-------------------------------------
+
+    // Define the math formula parser/compiler and evaluator.
+    struct _cimg_math_parser {
+      CImgList<charT> label;
+      CImgList<uintT> code;
+      CImg<uintT> level, opcode;
+      CImg<doubleT> mem;
+      CImg<charT> expr;
+      const CImg<T>& reference;
+      CImg<Tdouble> reference_stats;
+      unsigned int mempos, result;
+      const char *const calling_function;
+#define _cimg_mp_return(x) { *se = saved_char; return x; }
+#define _cimg_mp_opcode0(op) _cimg_mp_return(opcode0(op));
+#define _cimg_mp_opcode1(op,i1) _cimg_mp_return(opcode1(op,i1));
+#define _cimg_mp_opcode2(op,i1,i2) { const unsigned int _i1 = i1, _i2 = i2; _cimg_mp_return(opcode2(op,_i1,_i2)); }
+#define _cimg_mp_opcode3(op,i1,i2,i3) { const unsigned int _i1 = i1, _i2 = i2, _i3 = i3; _cimg_mp_return(opcode3(op,_i1,_i2,_i3)); }
+#define _cimg_mp_opcode5(op,i1,i2,i3,i4,i5) { const unsigned int _i1 = i1, _i2 = i2, _i3 = i3, _i4 = i4, _i5 = i5; \
+          _cimg_mp_return(opcode5(op,_i1,_i2,_i3,_i4,_i5)); }
+
+      // Constructor - Destructor.
+      _cimg_math_parser(const CImg<T>& img, const char *const expression, const char *const funcname=0):
+        reference(img),calling_function(funcname?funcname:"cimg_math_parser") {
+        unsigned int l = 0;
+        if (expression) {
+          l = std::strlen(expression);
+          expr.assign(expression,l+1);
+          if (*expr._data) {
+            char *d = expr._data;
+            for (const char *s = expr._data; *s || (bool)(*d=0); ++s) if (*s!=' ') *(d++) = *s;
+            l = d - expr._data;
+          }
+        }
+        if (!l) throw CImgArgumentException("[_cimg_math_parser] "
+                                            "CImg<%s>::%s() : Empty specified expression.",
+                                            pixel_type(),calling_function);
+
+        int lv = 0; // Count parenthesis level of expression.
+        level.assign(l);
+        unsigned int *pd = level._data;
+        for (const char *ps = expr._data; *ps && lv>=0; ++ps) *(pd++) = (unsigned int)(*ps=='('?lv++:*ps==')'?--lv:lv);
+        if (lv!=0) {
+          throw CImgArgumentException("[_cimg_math_parser] "
+                                      "CImg<%s>::%s() : Unbalanced parentheses in specified expression '%s'.",
+                                      pixel_type(),calling_function,
+                                      expr._data);
+        }
+        // Init constant values.
+        mem.assign(512);
+        label.assign(512);
+        mem[0] = 0;
+        mem[1] = 1;
+        mem[2] = (double)reference._width;
+        mem[3] = (double)reference._height;
+        mem[4] = (double)reference._depth;
+        mem[5] = (double)reference._spectrum;
+        mem[6] = cimg::PI;
+        mem[7] = std::exp(1.0); // Then [8] = x, [9] = y, [10] = z, [11] = c
+        mempos = 12;
+        result = compile(expr._data,expr._data+l); // Compile formula into a serie of opcodes.
+      }
+
+      // Insert code instructions.
+      unsigned int opcode0(const char op) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode1(const char op, const unsigned int arg1) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode2(const char op, const unsigned int arg1, const unsigned int arg2) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1,arg2).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode3(const char op, const unsigned int arg1, const unsigned int arg2, const unsigned int arg3) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1,arg2,arg3).move_to(code);
+        return pos;
+      }
+
+      unsigned int opcode5(const char op, const unsigned int arg1, const unsigned int arg2, const unsigned int arg3,
+                           const unsigned int arg4, const unsigned int arg5) {
+        if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+        const unsigned int pos = mempos++;
+        CImg<uintT>::vector(op,pos,arg1,arg2,arg3,arg4,arg5).move_to(code);
+        return pos;
+      }
+
+      // Compilation procedure.
+      unsigned int compile(char *const ss, char *const se) {
+        if (!ss || se<=ss || !*ss) {
+          throw CImgArgumentException("[_cimg_math_parser] "
+                                      "CImg<%s>::%s() : Missing item in specified expression '%s'.",
+                                      pixel_type(),calling_function,
+                                      expr._data);
+        }
+        char
+          *const se1 = se-1, *const se2 = se-2, *const se3 = se-3, *const se4 = se-4,
+          *const ss1 = ss+1, *const ss2 = ss+2, *const ss3 = ss+3, *const ss4 = ss+4,
+          *const ss5 = ss+5, *const ss6 = ss+6, *const ss7 = ss+7;
+        const char saved_char = *se; *se = 0;
+        const unsigned int clevel = level[ss-expr._data], clevel1 = clevel+1;
+
+        // Look for a single value, variable or variable assignment.
+        char end = 0, sep = 0; double val = 0;
+        const int nb = std::sscanf(ss,"%lf%c%c",&val,&sep,&end);
+        if (nb==1) {
+          if (val==0) _cimg_mp_return(0);
+          if (val==1) _cimg_mp_return(1);
+          if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+          const unsigned int pos = mempos++;
+          mem[pos] = val;
+          _cimg_mp_return(pos);
+        }
+        if (nb==2 && sep=='%') {
+          if (val==0) _cimg_mp_return(0);
+          if (val==100) _cimg_mp_return(1);
+          if (mempos>=mem._width) mem.resize(-200,1,1,1,0);
+          const unsigned int pos = mempos++;
+          mem[pos] = val/100;
+          _cimg_mp_return(pos);
+        }
+        if (ss1==se) switch (*ss) {
+          case 'w' : _cimg_mp_return(2); case 'h' : _cimg_mp_return(3); case 'd' : _cimg_mp_return(4); case 's' : _cimg_mp_return(5);
+          case 'x' : _cimg_mp_return(8); case 'y' : _cimg_mp_return(9); case 'z' : _cimg_mp_return(10); case 'c' : _cimg_mp_return(11);
+          case 'e' : _cimg_mp_return(7);
+          case 'u' : case '?' : _cimg_mp_opcode2(0,0,1);
+          case 'g' : _cimg_mp_opcode0(1);
+          case 'i' : _cimg_mp_opcode0(2);
+          }
+        if (ss1==se1) {
+          if (*ss=='p' && *ss1=='i') _cimg_mp_return(6); // pi
+          if (*ss=='i') {
+            if (*ss1=='m') _cimg_mp_opcode0(57); // im
+            if (*ss1=='M') _cimg_mp_opcode0(58); // iM
+            if (*ss1=='a') _cimg_mp_opcode0(59); // ia
+            if (*ss1=='v') _cimg_mp_opcode0(60); // iv
+          }
+          if (*ss1=='m') {
+            if (*ss=='x') _cimg_mp_opcode0(61); // xm
+            if (*ss=='y') _cimg_mp_opcode0(62); // ym
+            if (*ss=='z') _cimg_mp_opcode0(63); // zm
+            if (*ss=='c') _cimg_mp_opcode0(64); // cm
+          }
+          if (*ss1=='M') {
+            if (*ss=='x') _cimg_mp_opcode0(65); // xM
+            if (*ss=='y') _cimg_mp_opcode0(66); // yM
+            if (*ss=='z') _cimg_mp_opcode0(67); // zM
+            if (*ss=='c') _cimg_mp_opcode0(68); // cM
+          }
+        }
+        if (ss3==se) {
+          if (*ss=='x' && *ss1=='/' && *ss2=='w') _cimg_mp_opcode0(3);
+          if (*ss=='y' && *ss1=='/' && *ss2=='h') _cimg_mp_opcode0(4);
+          if (*ss=='z' && *ss1=='/' && *ss2=='d') _cimg_mp_opcode0(5);
+          if (*ss=='c' && *ss1=='/' && *ss2=='s') _cimg_mp_opcode0(6);
+        }
+
+        // Look for variable declarations.
+        for (char *s = se2; s>ss; --s) if (*s==';' && level[s-expr._data]==clevel) { compile(ss,s); _cimg_mp_return(compile(s+1,se)); }
+        for (char *s = ss1, *ps = ss, *ns = ss2; s<se1; ++s, ++ps, ++ns)
+           if (*s=='=' && *ns!='=' && *ps!='=' && *ps!='>' && *ps!='<' && *ps!='!' && level[s-expr._data]==clevel) {
+             CImg<charT> variable_name(ss,s-ss+1); variable_name.back() = 0;
+             bool is_valid_name = true;
+             if ((*ss>='0' && *ss<='9') ||
+                 (s==ss+1 && (*ss=='x' || *ss=='y' || *ss=='z' || *ss=='c' ||
+                              *ss=='w' || *ss=='h' || *ss=='d' || *ss=='s' ||
+                              *ss=='e' || *ss=='u' || *ss=='g' || *ss=='i')) ||
+                 (s==ss+2 && ((*ss=='p' && *(ss+1)=='i') ||
+                              (*ss=='i' && (*(ss+1)=='m' || *(ss+1)=='M' || *(ss+1)=='a' || *(ss+1)=='v'))))) is_valid_name = false;
+             for (const char *ns = ss; ns<s; ++ns)
+               if ((*ns<'a' || *ns>'z') && (*ns<'A' || *ns>'Z') && (*ns<'0' || *ns>'9') && *ns!='_') {
+                 is_valid_name = false; break;
+               }
+             if (!is_valid_name) {
+               *se = saved_char;
+               if (!std::strcmp(variable_name,"x") || !std::strcmp(variable_name,"y") || !std::strcmp(variable_name,"z") ||
+                   !std::strcmp(variable_name,"c") || !std::strcmp(variable_name,"w") || !std::strcmp(variable_name,"h") ||
+                   !std::strcmp(variable_name,"d") || !std::strcmp(variable_name,"s") || !std::strcmp(variable_name,"e") ||
+                   !std::strcmp(variable_name,"u") || !std::strcmp(variable_name,"g") || !std::strcmp(variable_name,"i") ||
+                   !std::strcmp(variable_name,"pi") || !std::strcmp(variable_name,"im") || !std::strcmp(variable_name,"iM") ||
+                   !std::strcmp(variable_name,"ia") || !std::strcmp(variable_name,"iv"))
+                  throw CImgArgumentException("[_cimg_math_parser] "
+                                             "CImg<%s>::%s() : Invalid assignment of reserved variable name '%s' in specified expression '%s'.",
+                                             pixel_type(),calling_function,
+                                             variable_name._data,expr._data);
+               else
+                 throw CImgArgumentException("[_cimg_math_parser] "
+                                             "CImg<%s>::%s() : Invalid variable name '%s' in specified expression '%s'.",
+                                             pixel_type(),calling_function,
+                                             variable_name._data,expr._data);
+             }
+             for (unsigned int i = 0; i<mempos; ++i) // Check for existing variable with same name.
+               if (label[i]._data && !std::strcmp(variable_name,label[i])) {
+                 *se = saved_char;
+                 throw CImgArgumentException("[_cimg_math_parser] "
+                                             "CImg<%s>::%s() : Invalid multiple assignments of variable '%s' in specified expression '%s'.",
+                                             pixel_type(),calling_function,
+                                             variable_name._data,expr._data);
+               }
+             const unsigned int src_pos = compile(s+1,se);
+             if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+             const unsigned int dest_pos = mempos++;
+             variable_name.move_to(label[dest_pos]);
+             CImg<uintT>::vector(7,dest_pos,src_pos).move_to(code);
+             _cimg_mp_return(dest_pos);
+           }
+
+        // Look for unary/binary operators. The operator precedences is defined as in C++.
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='|' && *ns=='|' && level[s-expr._data]==clevel) _cimg_mp_opcode2(8,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='&' && *ns=='&' && level[s-expr._data]==clevel) _cimg_mp_opcode2(9,compile(ss,s),compile(s+2,se));
+        for (char *s = se2; s>ss; --s) if (*s=='|' && level[s-expr._data]==clevel) _cimg_mp_opcode2(10,compile(ss,s),compile(s+1,se));
+        for (char *s = se2; s>ss; --s) if (*s=='&' && level[s-expr._data]==clevel) _cimg_mp_opcode2(11,compile(ss,s),compile(s+1,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='!' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(12,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='=' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(13,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='<' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(14,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='>' && *ns=='=' && level[s-expr._data]==clevel) _cimg_mp_opcode2(15,compile(ss,s),compile(s+2,se));
+        for (char *s = se2, *ns = se1, *ps = se3; s>ss; --s, --ns, --ps)
+          if (*s=='<' && *ns!='<' && *ps!='<' && level[s-expr._data]==clevel) _cimg_mp_opcode2(16,compile(ss,s),compile(s+1,se));
+        for (char *s = se2, *ns = se1, *ps = se3; s>ss; --s, --ns, --ps)
+          if (*s=='>' && *ns!='>' && *ps!='>' && level[s-expr._data]==clevel) _cimg_mp_opcode2(17,compile(ss,s),compile(s+1,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='<' && *ns=='<' && level[s-expr._data]==clevel) _cimg_mp_opcode2(18,compile(ss,s),compile(s+2,se));
+        for (char *s = se3, *ns = se2; s>ss; --s, --ns) if (*s=='>' && *ns=='>' && level[s-expr._data]==clevel) _cimg_mp_opcode2(19,compile(ss,s),compile(s+2,se));
+        for (char *s = se2, *ps = se3; s>ss; --s, --ps)
+          if (*s=='+' && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' &&
+              (*ps!='e' || !(ps>ss && (*(ps-1)=='.' || (*(ps-1)>='0' && *(ps-1)<='9')))) && level[s-expr._data]==clevel)
+            _cimg_mp_opcode2(21,compile(ss,s),compile(s+1,se));
+        for (char *s = se2, *ps = se3; s>ss; --s, --ps)
+          if (*s=='-' && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' && *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' &&
+              (*ps!='e' || !(ps>ss && (*(ps-1)=='.' || (*(ps-1)>='0' && *(ps-1)<='9')))) && level[s-expr._data]==clevel)
+            _cimg_mp_opcode2(20,compile(ss,s),compile(s+1,se));
+        for (char *s = se2; s>ss; --s) if (*s=='*' && level[s-expr._data]==clevel) _cimg_mp_opcode2(22,compile(ss,s),compile(s+1,se));
+        for (char *s = se2; s>ss; --s) if (*s=='/' && level[s-expr._data]==clevel) _cimg_mp_opcode2(23,compile(ss,s),compile(s+1,se));
+        for (char *s = se2, *ns = se1; s>ss; --s, --ns)
+          if (*s=='%' && *ns!='^' && level[s-expr._data]==clevel)
+            _cimg_mp_opcode2(24,compile(ss,s),compile(s+1,se));
+        if (ss<se1) {
+          if (*ss=='+') _cimg_mp_return(compile(ss1,se));
+          if (*ss=='-') _cimg_mp_opcode1(26,compile(ss1,se));
+          if (*ss=='!') _cimg_mp_opcode1(27,compile(ss1,se));
+          if (*ss=='~') _cimg_mp_opcode1(28,compile(ss1,se));
+        }
+        for (char *s = se2; s>ss; --s) if (*s=='^' && level[s-expr._data]==clevel) _cimg_mp_opcode2(25,compile(ss,s),compile(s+1,se));
+
+        // Look for a function call or a parenthesis.
+        if (*se1==')') {
+          if (*ss=='(') _cimg_mp_return(compile(ss1,se1));
+          if (!std::strncmp(ss,"sin(",4)) _cimg_mp_opcode1(29,compile(ss4,se1));
+          if (!std::strncmp(ss,"cos(",4)) _cimg_mp_opcode1(30,compile(ss4,se1));
+          if (!std::strncmp(ss,"tan(",4)) _cimg_mp_opcode1(31,compile(ss4,se1));
+          if (!std::strncmp(ss,"asin(",5)) _cimg_mp_opcode1(32,compile(ss5,se1));
+          if (!std::strncmp(ss,"acos(",5)) _cimg_mp_opcode1(33,compile(ss5,se1));
+          if (!std::strncmp(ss,"atan(",5)) _cimg_mp_opcode1(34,compile(ss5,se1));
+          if (!std::strncmp(ss,"sinh(",5)) _cimg_mp_opcode1(35,compile(ss5,se1));
+          if (!std::strncmp(ss,"cosh(",5)) _cimg_mp_opcode1(36,compile(ss5,se1));
+          if (!std::strncmp(ss,"tanh(",5)) _cimg_mp_opcode1(37,compile(ss5,se1));
+          if (!std::strncmp(ss,"log10(",6)) _cimg_mp_opcode1(38,compile(ss6,se1));
+          if (!std::strncmp(ss,"log(",4)) _cimg_mp_opcode1(39,compile(ss4,se1));
+          if (!std::strncmp(ss,"exp(",4)) _cimg_mp_opcode1(40,compile(ss4,se1));
+          if (!std::strncmp(ss,"sqrt(",5)) _cimg_mp_opcode1(41,compile(ss5,se1));
+          if (!std::strncmp(ss,"sign(",5)) _cimg_mp_opcode1(42,compile(ss5,se1));
+          if (!std::strncmp(ss,"abs(",4)) _cimg_mp_opcode1(43,compile(ss4,se1));
+          if (!std::strncmp(ss,"atan2(",6)) {
+            char *s1 = ss6; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            _cimg_mp_opcode2(44,compile(ss6,s1),compile(s1+1,se1));
+          }
+          if (!std::strncmp(ss,"if(",3)) {
+            char *s1 = ss3; while (s1<se4 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+            _cimg_mp_opcode3(45,compile(ss3,s1),compile(s1+1,s2),compile(s2+1,se1));
+          }
+          if (!std::strncmp(ss,"round(",6)) {
+            unsigned int value = 0, round = 1, direction = 0;
+            char *s1 = ss6; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            value = compile(ss6,s1==se2?++s1:s1);
+            if (s1<se1) {
+              char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+              round = compile(s1+1,s2==se2?++s2:s2);
+              if (s2<se1) direction = compile(s2+1,se1);
+            }
+            _cimg_mp_opcode3(46,value,round,direction);
+          }
+          if (!std::strncmp(ss,"?(",2) || !std::strncmp(ss,"u(",2)) {
+            if (*ss2==')') _cimg_mp_opcode2(0,0,1);
+            char *s1 = ss2; while (s1<se1 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            if (s1<se1) _cimg_mp_opcode2(0,compile(ss2,s1),compile(s1+1,se1));
+            _cimg_mp_opcode2(0,0,compile(ss2,s1));
+          }
+          if (!std::strncmp(ss,"i(",2)) {
+            if (*ss2==')') _cimg_mp_return(0);
+            unsigned int indx = 8, indy = 9, indz = 10, indc = 11, borders = 0;
+            if (ss2!=se1) {
+              char *s1 = ss2; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+              indx = compile(ss2,s1==se2?++s1:s1);
+              if (s1<se1) {
+                char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+                indy = compile(s1+1,s2==se2?++s2:s2);
+                if (s2<se1) {
+                  char *s3 = s2+1; while (s3<se2 && (*s3!=',' || level[s3-expr._data]!=clevel1)) ++s3;
+                  indz = compile(s2+1,s3==se2?++s3:s3);
+                  if (s3<se1) {
+                    char *s4 = s3+1; while (s4<se2 && (*s4!=',' || level[s4-expr._data]!=clevel1)) ++s4;
+                    indc = compile(s3+1,s4==se2?++s4:s4);
+                    if (s4<se1) borders = compile(s4+1,se1);
+                  }
+                }
+              }
+            }
+            _cimg_mp_opcode5(47,indx,indy,indz,indc,borders);
+          }
+          if (!std::strncmp(ss,"min(",4) || !std::strncmp(ss,"max(",4)) {
+            CImgList<uintT> opcode;
+            if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+            const unsigned int pos = mempos++;
+            CImg<uintT>::vector(ss[1]=='i'?48:49,pos).move_to(opcode);
+            for (char *s = ss4; s<se; ++s) {
+              char *ns = s; while (ns<se && (*ns!=',' || level[ns-expr._data]!=clevel1) && (*ns!=')' || level[ns-expr._data]!=clevel)) ++ns;
+              CImg<uintT>::vector(compile(s,ns)).move_to(opcode);
+              s = ns;
+            }
+            (opcode>'y').move_to(code);
+            _cimg_mp_return(pos);
+          }
+          if (!std::strncmp(ss,"arg(",4)) {
+            CImgList<uintT> opcode;
+            if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+            const unsigned int pos = mempos++;
+            CImg<uintT>::vector(69,pos).move_to(opcode);
+            for (char *s = ss4; s<se; ++s) {
+              char *ns = s; while (ns<se && (*ns!=',' || level[ns-expr._data]!=clevel1) && (*ns!=')' || level[ns-expr._data]!=clevel)) ++ns;
+              CImg<uintT>::vector(compile(s,ns)).move_to(opcode);
+              s = ns;
+            }
+            (opcode>'y').move_to(code);
+            _cimg_mp_return(pos);
+          }
+          if (!std::strncmp(ss,"narg(",5)) {
+            if (*ss5==')') _cimg_mp_return(0);
+            unsigned int nb_args = 0;
+            for (char *s = ss5; s<se; ++s) {
+              char *ns = s; while (ns<se && (*ns!=',' || level[ns-expr._data]!=clevel1) && (*ns!=')' || level[ns-expr._data]!=clevel)) ++ns;
+              ++nb_args; s = ns;
+            }
+            if (nb_args==0 || nb_args==1) _cimg_mp_return(nb_args);
+            if (mempos>=mem.size()) mem.resize(-200,1,1,1,0);
+            const unsigned int pos = mempos++;
+            mem[pos] = nb_args;
+            _cimg_mp_return(pos);
+          }
+          if (!std::strncmp(ss,"isval(",6)) {
+            char sep = 0, end = 0; double val = 0;
+            if (std::sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1);
+            _cimg_mp_return(0);
+          }
+          if (!std::strncmp(ss,"isnan(",6)) _cimg_mp_opcode1(50,compile(ss6,se1));
+          if (!std::strncmp(ss,"isinf(",6)) _cimg_mp_opcode1(51,compile(ss6,se1));
+          if (!std::strncmp(ss,"isint(",6)) _cimg_mp_opcode1(52,compile(ss6,se1));
+          if (!std::strncmp(ss,"isbool(",7)) _cimg_mp_opcode1(53,compile(ss7,se1));
+          if (!std::strncmp(ss,"rol(",4) || !std::strncmp(ss,"ror(",4)) {
+            unsigned int value = 0, nb = 1;
+            char *s1 = ss4; while (s1<se2 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+            value = compile(ss4,s1==se2?++s1:s1);
+            if (s1<se1) {
+              char *s2 = s1+1; while (s2<se2 && (*s2!=',' || level[s2-expr._data]!=clevel1)) ++s2;
+              nb = compile(s1+1,se1);
+            }
+            _cimg_mp_opcode2(*ss2=='l'?54:55,value,nb);
+          }
+
+          if (!std::strncmp(ss,"sinc(",5)) _cimg_mp_opcode1(56,compile(ss5,se1));
+          if (!std::strncmp(ss,"int(",4)) _cimg_mp_opcode1(70,compile(ss4,se1));
+        }
+
+        // No known item found, assuming this is an already initialize variable.
+        CImg<charT> variable_name(ss,se-ss+1); variable_name.back() = 0;
+        for (unsigned int i = 0; i<mempos; ++i) if (label[i]._data && !std::strcmp(variable_name,label[i])) _cimg_mp_return(i);
+        *se = saved_char;
+        throw CImgArgumentException("[_cimg_math_parser] "
+                                    "CImg<%s>::%s() : Invalid item '%s' in specified expression '%s'.\n",
+                                    pixel_type(),calling_function,
+                                    variable_name._data,expr._data);
+        return 0;
+      }
+
+      // Evaluation functions, known by the parser.
+      double mp_u() {
+        return mem[opcode(2)] + cimg::rand()*(mem[opcode(3)]-mem[opcode(2)]);
+      }
+      double mp_g() {
+        return cimg::grand();
+      }
+      double mp_i() {
+        return (double)reference.atXYZC((int)mem[8],(int)mem[9],(int)mem[10],(int)mem[11],0);
+      }
+      double mp_xw() {
+        return mem[8]/reference.width();
+      }
+      double mp_yh() {
+        return mem[9]/reference.height();
+      }
+      double mp_zd() {
+        return mem[10]/reference.depth();
+      }
+      double mp_cs() {
+        return mem[11]/reference.spectrum();
+      }
+      double mp_equal() {
+        return mem[opcode[2]];
+      }
+      double mp_logical_and() {
+        return (double)((bool)mem[opcode(2)] && (bool)mem[opcode(3)]);
+      }
+      double mp_logical_or() {
+        return (double)((bool)mem[opcode(2)] || (bool)mem[opcode(3)]);
+      }
+      double mp_infeq() {
+        return (double)(mem[opcode(2)] <= mem[opcode(3)]);
+      }
+      double mp_supeq() {
+        return (double)(mem[opcode(2)] >= mem[opcode(3)]);
+      }
+      double mp_noteq() {
+        return (double)(mem[opcode(2)] != mem[opcode(3)]);
+      }
+      double mp_eqeq() {
+        return (double)(mem[opcode(2)] == mem[opcode(3)]);
+      }
+      double mp_inf() {
+        return (double)(mem[opcode(2)] < mem[opcode(3)]);
+      }
+      double mp_sup() {
+        return (double)(mem[opcode(2)] > mem[opcode(3)]);
+      }
+      double mp_add() {
+        return mem[opcode(2)] + mem[opcode(3)];
+      }
+      double mp_sub() {
+        return mem[opcode(2)] - mem[opcode(3)];
+      }
+      double mp_mul() {
+        return mem[opcode(2)] * mem[opcode(3)];
+      }
+      double mp_div() {
+        return mem[opcode(2)] / mem[opcode(3)];
+      }
+      double mp_minus() {
+        return -mem[opcode(2)];
+      }
+      double mp_not() {
+        return !mem[opcode(2)];
+      }
+      double mp_logical_not() {
+        return !mem[opcode(2)];
+      }
+      double mp_bitwise_not() {
+        return ~(unsigned long)mem[opcode(2)];
+      }
+      double mp_modulo() {
+        return cimg::mod(mem[opcode(2)],mem[opcode(3)]);
+      }
+      double mp_bitwise_and() {
+        return ((unsigned long)mem[opcode(2)] & (unsigned long)mem[opcode(3)]);
+      }
+      double mp_bitwise_or() {
+        return ((unsigned long)mem[opcode(2)] | (unsigned long)mem[opcode(3)]);
+      }
+      double mp_pow() {
+        return std::pow(mem[opcode(2)],mem[opcode(3)]);
+      }
+      double mp_sin() {
+        return std::sin(mem[opcode(2)]);
+      }
+      double mp_cos() {
+        return std::cos(mem[opcode(2)]);
+      }
+      double mp_tan() {
+        return std::tan(mem[opcode(2)]);
+      }
+      double mp_asin() {
+        return std::asin(mem[opcode(2)]);
+      }
+      double mp_acos() {
+        return std::acos(mem[opcode(2)]);
+      }
+      double mp_atan() {
+        return std::atan(mem[opcode(2)]);
+      }
+      double mp_sinh() {
+        return std::sinh(mem[opcode(2)]);
+      }
+      double mp_cosh() {
+        return std::cosh(mem[opcode(2)]);
+      }
+      double mp_tanh() {
+        return std::tanh(mem[opcode(2)]);
+      }
+      double mp_log10() {
+        return std::log10(mem[opcode(2)]);
+      }
+      double mp_log() {
+        return std::log(mem[opcode(2)]);
+      }
+      double mp_exp() {
+        return std::exp(mem[opcode(2)]);
+      }
+      double mp_sqrt() {
+        return std::sqrt(mem[opcode(2)]);
+      }
+      double mp_sign() {
+        return cimg::sign(mem[opcode(2)]);
+      }
+      double mp_abs() {
+        return cimg::abs(mem[opcode(2)]);
+      }
+      double mp_atan2() {
+        return std::atan2(mem[opcode(2)],mem[opcode(3)]);
+      }
+      double mp_if() {
+        return mem[opcode(2)]?mem[opcode(3)]:mem[opcode(4)];
+      }
+      double mp_round() {
+        return cimg::round(mem[opcode(2)],mem[opcode(3)],(int)mem[opcode(4)]);
+      }
+      double mp_ixyzc() {
+        const int c = (int)mem[opcode(5)], b = (int)mem[opcode(6)];
+        if (b==0) return (double)reference.linear_atXYZ((float)mem[opcode(2)],
+                                                        (float)mem[opcode(3)],
+                                                        (float)mem[opcode(4)],
+                                                        c<0?0:c>=reference.spectrum()?reference.spectrum()-1:c,0);
+        if (b==1) return (double)reference.linear_atXYZ((float)mem[opcode(2)],
+                                                        (float)mem[opcode(3)],
+                                                        (float)mem[opcode(4)],
+                                                        c<0?0:c>=reference.spectrum()?reference.spectrum()-1:c);
+        return (double)reference.linear_atXYZ((float)cimg::mod(mem[opcode(2)],(double)reference.width()),
+                                              (float)cimg::mod(mem[opcode(3)],(double)reference.height()),
+                                              (float)cimg::mod(mem[opcode(4)],(double)reference.depth()),
+                                              c<0?0:c>=reference.spectrum()?reference.spectrum()-1:c);
+      }
+      double mp_min() {
+        double val = mem[opcode(2)];
+        for (unsigned int i = 3; i<opcode._height; ++i) val = cimg::min(val,mem[opcode(i)]);
+        return val;
+      }
+      double mp_max() {
+        double val = mem[opcode(2)];
+        for (unsigned int i = 3; i<opcode._height; ++i) val = cimg::max(val,mem[opcode(i)]);
+        return val;
+      }
+      double mp_isnan() {
+        const double val = mem[opcode(2)];
+        return !(val == val);
+      }
+      double mp_isinf() {
+        const double val = mem[opcode(2)];
+        return val == (val+1);
+      }
+      double mp_isint() {
+        const double val = mem[opcode(2)];
+        return (double)(int)val == val;
+      }
+      double mp_isbool() {
+        const double val = mem[opcode(2)];
+        return (val==0.0 || val==1.0);
+      }
+      double mp_rol() {
+        return cimg::rol(mem[opcode(2)],(unsigned int)mem[opcode(3)]);
+      }
+      double mp_ror() {
+        return cimg::ror(mem[opcode(2)],(unsigned int)mem[opcode(3)]);
+      }
+      double mp_lsl() {
+        return (long)mem[opcode(2)]<<(unsigned int)mem[opcode(3)];
+      }
+      double mp_lsr() {
+        return (long)mem[opcode(2)]>>(unsigned int)mem[opcode(3)];
+      }
+      double mp_sinc() {
+        return cimg::sinc(mem[opcode(2)]);
+      }
+      double mp_im() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[0]:0;
+      }
+      double mp_iM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[1]:0;
+      }
+      double mp_ia() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[2]:0;
+      }
+      double mp_iv() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[3]:0;
+      }
+      double mp_xm() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[4]:0;
+      }
+      double mp_ym() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[5]:0;
+      }
+      double mp_zm() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[6]:0;
+      }
+      double mp_cm() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[7]:0;
+      }
+      double mp_xM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[8]:0;
+      }
+      double mp_yM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[9]:0;
+      }
+      double mp_zM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[10]:0;
+      }
+      double mp_cM() {
+        if (!reference_stats) reference.get_stats().move_to(reference_stats);
+        return reference_stats?reference_stats[11]:0;
+      }
+      double mp_arg() {
+        const int _ind = (int)mem[opcode(2)];
+        const unsigned int nb_args = opcode._height-2, ind = _ind<0?_ind+nb_args:(unsigned int)_ind;
+        if (ind>=nb_args) return 0;
+        return mem[opcode(ind+2)];
+      }
+      double mp_int() {
+        return (double)(long)mem[opcode(2)];
+      }
+
+      // Evaluation procedure, with image data.
+      double eval(const double x, const double y, const double z, const double c) {
+        typedef double (_cimg_math_parser::*mp_func)();
+        const mp_func mp_funcs[] = {
+          &_cimg_math_parser::mp_u,            // 0
+          &_cimg_math_parser::mp_g,            // 1
+          &_cimg_math_parser::mp_i,            // 2
+          &_cimg_math_parser::mp_xw,           // 3
+          &_cimg_math_parser::mp_yh,           // 4
+          &_cimg_math_parser::mp_zd,           // 5
+          &_cimg_math_parser::mp_cs,           // 6
+          &_cimg_math_parser::mp_equal,        // 7
+          &_cimg_math_parser::mp_logical_or,   // 8
+          &_cimg_math_parser::mp_logical_and,  // 9
+          &_cimg_math_parser::mp_bitwise_or,   // 10
+          &_cimg_math_parser::mp_bitwise_and,  // 11
+          &_cimg_math_parser::mp_noteq,        // 12
+          &_cimg_math_parser::mp_eqeq,         // 13
+          &_cimg_math_parser::mp_infeq,        // 14
+          &_cimg_math_parser::mp_supeq,        // 15
+          &_cimg_math_parser::mp_inf,          // 16
+          &_cimg_math_parser::mp_sup,          // 17
+          &_cimg_math_parser::mp_lsl,          // 18
+          &_cimg_math_parser::mp_lsr,          // 19
+          &_cimg_math_parser::mp_sub,          // 20
+          &_cimg_math_parser::mp_add,          // 21
+          &_cimg_math_parser::mp_mul,          // 22
+          &_cimg_math_parser::mp_div,          // 23
+          &_cimg_math_parser::mp_modulo,       // 24
+          &_cimg_math_parser::mp_pow,          // 25
+          &_cimg_math_parser::mp_minus,        // 26
+          &_cimg_math_parser::mp_logical_not,  // 27
+          &_cimg_math_parser::mp_bitwise_not,  // 28
+          &_cimg_math_parser::mp_sin,          // 29
+          &_cimg_math_parser::mp_cos,          // 30
+          &_cimg_math_parser::mp_tan,          // 31
+          &_cimg_math_parser::mp_asin,         // 32
+          &_cimg_math_parser::mp_acos,         // 33
+          &_cimg_math_parser::mp_atan,         // 34
+          &_cimg_math_parser::mp_sinh,         // 35
+          &_cimg_math_parser::mp_cosh,         // 36
+          &_cimg_math_parser::mp_tanh,         // 37
+          &_cimg_math_parser::mp_log10,        // 38
+          &_cimg_math_parser::mp_log,          // 39
+          &_cimg_math_parser::mp_exp,          // 40
+          &_cimg_math_parser::mp_sqrt,         // 41
+          &_cimg_math_parser::mp_sign,         // 42
+          &_cimg_math_parser::mp_abs,          // 43
+          &_cimg_math_parser::mp_atan2,        // 44
+          &_cimg_math_parser::mp_if,           // 45
+          &_cimg_math_parser::mp_round,        // 46
+          &_cimg_math_parser::mp_ixyzc,        // 47
+          &_cimg_math_parser::mp_min,          // 48
+          &_cimg_math_parser::mp_max,          // 49
+          &_cimg_math_parser::mp_isnan,        // 50
+          &_cimg_math_parser::mp_isinf,        // 51
+          &_cimg_math_parser::mp_isint,        // 52
+          &_cimg_math_parser::mp_isbool,       // 53
+          &_cimg_math_parser::mp_rol,          // 54
+          &_cimg_math_parser::mp_ror,          // 55
+          &_cimg_math_parser::mp_sinc,         // 56
+          &_cimg_math_parser::mp_im,           // 57
+          &_cimg_math_parser::mp_iM,           // 58
+          &_cimg_math_parser::mp_ia,           // 59
+          &_cimg_math_parser::mp_iv,           // 60
+          &_cimg_math_parser::mp_xm,           // 61
+          &_cimg_math_parser::mp_ym,           // 62
+          &_cimg_math_parser::mp_zm,           // 63
+          &_cimg_math_parser::mp_cm,           // 64
+          &_cimg_math_parser::mp_xM,           // 65
+          &_cimg_math_parser::mp_yM,           // 66
+          &_cimg_math_parser::mp_zM,           // 67
+          &_cimg_math_parser::mp_cM,           // 68
+          &_cimg_math_parser::mp_arg,          // 69
+          &_cimg_math_parser::mp_int           // 70
+        };
+
+        if (!mem) return 0;
+        mem[8] = x; mem[9] = y; mem[10] = z; mem[11] = c;
+        opcode._is_shared = true; opcode._width = opcode._depth = opcode._spectrum = 1;
+        cimglist_for(code,l) {
+          const CImg<uintT> &op = code[l];
+          opcode._data = op._data; opcode._height = op._height;  // Allows to avoid parameter passing to evaluation functions.
+          mem[opcode(1)] = (this->*mp_funcs[opcode[0]])();
+        }
+        return mem[result];
+      }
+    };
+
+    //! Compute the square value of each pixel.
+    CImg<T>& sqr() {
+      cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(val*val); };
+      return *this;
+    }
+
+    CImg<Tfloat> get_sqr() const {
+      return CImg<Tfloat>(*this,false).sqr();
+    }
+
+    //! Compute the square root of each pixel value.
+    CImg<T>& sqrt() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::sqrt((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_sqrt() const {
+      return CImg<Tfloat>(*this,false).sqrt();
+    }
+
+    //! Compute the exponential of each pixel value.
+    CImg<T>& exp() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::exp((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_exp() const {
+      return CImg<Tfloat>(*this,false).exp();
+    }
+
+    //! Compute the log of each each pixel value.
+    CImg<T>& log() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::log((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_log() const {
+      return CImg<Tfloat>(*this,false).log();
+    }
+
+    //! Compute the log10 of each each pixel value.
+    CImg<T>& log10() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::log10((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_log10() const {
+      return CImg<Tfloat>(*this,false).log10();
+    }
+
+    //! Compute the absolute value of each pixel value.
+    CImg<T>& abs() {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::abs(*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_abs() const {
+      return CImg<Tfloat>(*this,false).abs();
+    }
+
+    //! Compute the sign of each pixel value.
+    CImg<T>& sign() {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::sign(*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_sign() const {
+      return CImg<Tfloat>(*this,false).sign();
+    }
+
+    //! Compute the cosinus of each pixel value.
+    CImg<T>& cos() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::cos((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_cos() const {
+      return CImg<Tfloat>(*this,false).cos();
+    }
+
+    //! Compute the sinus of each pixel value.
+    CImg<T>& sin() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::sin((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_sin() const {
+      return CImg<Tfloat>(*this,false).sin();
+    }
+
+    //! Compute the sinus cardinal of each pixel value.
+    CImg<T>& sinc() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::sinc((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_sinc() const {
+      return CImg<Tfloat>(*this,false).sinc();
+    }
+
+    //! Compute the tangent of each pixel.
+    CImg<T>& tan() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::tan((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_tan() const {
+      return CImg<Tfloat>(*this,false).tan();
+    }
+
+    //! Compute the hyperbolic cosine of each pixel value.
+    CImg<T>& cosh() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::cosh((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_cosh() const {
+      return CImg<Tfloat>(*this,false).cosh();
+    }
+
+    //! Compute the hyperbolic sine of each pixel value.
+    CImg<T>& sinh() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::sinh((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_sinh() const {
+      return CImg<Tfloat>(*this,false).sinh();
+    }
+
+    //! Compute the hyperbolic tangent of each pixel value.
+    CImg<T>& tanh() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::tanh((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_tanh() const {
+      return CImg<Tfloat>(*this,false).tanh();
+    }
+
+    //! Compute the arc-cosine of each pixel value.
+    CImg<T>& acos() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::acos((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_acos() const {
+      return CImg<Tfloat>(*this,false).acos();
+    }
+
+    //! Compute the arc-sinus of each pixel value.
+    CImg<T>& asin() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::asin((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_asin() const {
+      return CImg<Tfloat>(*this,false).asin();
+    }
+
+    //! Compute the arc-tangent of each pixel.
+    CImg<T>& atan() {
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::atan((double)*ptrd);
+      return *this;
+    }
+
+    CImg<Tfloat> get_atan() const {
+      return CImg<Tfloat>(*this,false).atan();
+    }
+
+    //! Compute the arc-tangent of each pixel.
+    template<typename t>
+    CImg<T>& atan2(const CImg<t>& 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)std::atan2((double)*ptrd,(double)*(--ptrs))) {}
+      return *this;
+    }
+
+    template<typename t>
+    CImg<Tfloat> get_atan2(const CImg<t>& img) const {
+      return CImg<Tfloat>(*this,false).atan2(img);
+    }
+
+    //! Pointwise multiplication between an image and an expression.
+    CImg<T>& mul(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"mul");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd * mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        mul(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Pointwise multiplication between two images.
+    template<typename t>
+    CImg<T>& mul(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return mul(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd * *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd * *(ptrs++));
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_mul(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).mul(img);
+    }
+
+    //! Pointwise division between an image and an expression.
+    CImg<T>& div(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"div");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)(*ptrd / mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        cimg::exception_mode() = omode;
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        values = expression;
+        div(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    //! Pointwise division between two images.
+    template<typename t>
+    CImg<T>& div(const CImg<t>& img) {
+      const unsigned int siz = size(), isiz = img.size();
+      if (siz && isiz) {
+        if (is_overlapped(img)) return div(+img);
+        T *ptrd = _data, *const ptre = _data + siz;
+        if (siz>isiz) for (unsigned int n = siz/isiz; n; --n)
+          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd) *ptrd = (T)(*ptrd / *(ptrs++));
+        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd / *(ptrs++));
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_div(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).div(img);
+    }
+
+    //! Compute the power by p of each pixel value.
+    CImg<T>& pow(const double p) {
+      if (p==0) return fill(1);
+      if (p==0.5) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)std::sqrt((double)val); } return *this; }
+      if (p==1) return *this;
+      if (p==2) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val; } return *this; }
+      if (p==3) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val; } return *this; }
+      if (p==4) { cimg_for(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val*val; } return *this; }
+      cimg_for(*this,ptrd,T) *ptrd = (T)std::pow((double)*ptrd,p);
+      return *this;
+    }
+
+    CImg<Tfloat> get_pow(const double p) const {
+      return CImg<Tfloat>(*this,false).pow(p);
+    }
+
+    //! Compute the power of each pixel value.
+    template<typename t>
+    CImg<T>& pow(const CImg<t>& img) {
+      if (is_overlapped(img)) return pow(+img);
+      t *ptrs = img._data;
+      T *ptrf = _data + cimg::min(size(),img.size());
+      for (T* ptrd = _data; ptrd<ptrf; ++ptrd) *ptrd = (T)std::pow((double)*ptrd,(double)(*(ptrs++)));
+      return *this;
+    }
+
+    template<typename t>
+    CImg<Tfloat> get_pow(const CImg<t>& img) const {
+      return CImg<Tfloat>(*this,false).pow(img);
+    }
+
+    //! Compute the power of each pixel value.
+    CImg<T>& pow(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"pow");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)std::pow((double)*ptrd,mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        pow(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<Tfloat> get_pow(const char *const expression) const {
+      return CImg<Tfloat>(*this,false).pow(expression);
+    }
+
+    //! Compute the bitwise left rotation of each pixel value.
+    CImg<T>& rol(const unsigned int n=1) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::rol(*ptrd,n);
+      return *this;
+    }
+
+    CImg<T> get_rol(const unsigned int n=1) const {
+      return (+*this).rol(n);
+    }
+
+    template<typename t>
+    CImg<T>& rol(const CImg<t>& img) {
+      if (is_overlapped(img)) return rol(+img);
+      t *ptrs = img._data;
+      T *ptrf = _data + cimg::min(size(),img.size());
+      for (T* ptrd = _data; ptrd<ptrf; ++ptrd) *ptrd = (T)cimg::rol(*ptrd,(unsigned int)(*(ptrs++)));
+      return *this;
+    }
+
+    template<typename t>
+    CImg<T> get_rol(const CImg<t>& img) const {
+      return (+*this).rol(img);
+    }
+
+    CImg<T>& rol(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"rol");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::rol(*ptrd,(unsigned int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        rol(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<T> get_rol(const char *const expression) const {
+      return (+*this).rol(expression);
+    }
+
+    //! Compute the bitwise right rotation of each pixel value.
+    CImg<T>& ror(const unsigned int n=1) {
+      cimg_for(*this,ptrd,T) *ptrd = (T)cimg::ror(*ptrd,n);
+      return *this;
+    }
+
+    CImg<T> get_ror(const unsigned int n=1) const {
+      return (+*this).ror(n);
+    }
+
+    template<typename t>
+    CImg<T>& ror(const CImg<t>& img) {
+      if (is_overlapped(img)) return ror(+img);
+      t *ptrs = img._data;
+      T *ptrf = _data + cimg::min(size(),img.size());
+      for (T* ptrd = _data; ptrd<ptrf; ++ptrd) *ptrd = (T)cimg::ror(*ptrd,(unsigned int)(*(ptrs++)));
+      return *this;
+    }
+
+    template<typename t>
+    CImg<T> get_ror(const CImg<t>& img) const {
+      return (+*this).ror(img);
+    }
+
+    CImg<T>& ror(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"ror");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::ror(*ptrd,(unsigned int)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<Tfloat> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        ror(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<T> get_ror(const char *const expression) const {
+      return (+*this).ror(expression);
+    }
+
+    //! Pointwise min operator between an image and a value.
+    CImg<T>& min(const T val) {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::min(*ptrd,val);
+      return *this;
+    }
+
+    CImg<T> get_min(const T val) const {
+      return (+*this).min(val);
+    }
+
+    //! Pointwise min operator between two images.
+    template<typename t>
+    CImg<T>& min(const CImg<t>& img) {
+      if (is_overlapped(img)) return min(+img);
+      t *ptrs = img._data;
+      T *ptrf = _data + cimg::min(size(),img.size());
+      for (T* ptrd = _data; ptrd<ptrf; ++ptrd) *ptrd = cimg::min((T)*(ptrs++),*ptrd);
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_min(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).min(img);
+    }
+
+    //! Pointwise min operator between an image and a string.
+    CImg<T>& min(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"min");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::min(*ptrd,(T)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        min(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<Tfloat> get_min(const char *const expression) const {
+      return CImg<Tfloat>(*this,false).min(expression);
+    }
+
+    //! Pointwise max operator between an image and a value.
+    CImg<T>& max(const T val) {
+      cimg_for(*this,ptrd,T) *ptrd = cimg::max(*ptrd,val);
+      return *this;
+    }
+
+    CImg<T> get_max(const T val) const {
+      return (+*this).max(val);
+    }
+
+    //! Pointwise max operator between two images.
+    template<typename t>
+    CImg<T>& max(const CImg<t>& img) {
+      if (is_overlapped(img)) return max(+img);
+      t *ptrs = img._data;
+      T *ptrf = _data + cimg::min(size(),img.size());
+      for (T* ptrd = _data; ptrd<ptrf; ++ptrd) *ptrd = cimg::max((T)*(ptrs++),*ptrd);
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_max(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this,false).max(img);
+    }
+
+    //! Pointwise max operator between an image and a string.
+    CImg<T>& max(const char *const expression) {
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"max");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) { *ptrd = (T)cimg::max(*ptrd,(T)mp.eval(x,y,z,c)); ++ptrd; }
+      } catch (CImgException&) {
+        CImg<T> values(_width,_height,_depth,_spectrum);
+        try {
+          values.fill(expression,true);
+        } catch (CImgException&) {
+          cimg::exception_mode() = omode;
+          values.load(expression);
+        }
+        max(values);
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<Tfloat> get_max(const char *const expression) const {
+      return CImg<Tfloat>(*this,false).max(expression);
+    }
+
+    //! Return a reference to the minimum pixel value of the instance image
+    T& min() {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_min = _data;
+      T min_value = *ptr_min;
+      cimg_for(*this,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      return *ptr_min;
+    }
+
+    const T& min() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_min = _data;
+      T min_value = *ptr_min;
+      cimg_for(*this,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      return *ptr_min;
+    }
+
+    //! Return a reference to the maximum pixel value of the instance image
+    T& max() {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_max = _data;
+      T max_value = *ptr_max;
+      cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      return *ptr_max;
+    }
+
+    const T& max() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_max = _data;
+      T max_value = *ptr_max;
+      cimg_for(*this,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      return *ptr_max;
+    }
+
+    //! Return a reference to the minimum pixel value and return also the maximum pixel value.
+    template<typename t>
+    T& min_max(t& max_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min_max() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_min = _data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val<min_value) { min_value = val; ptr_min = ptrs; }
+        if (val>max_value) max_value = val;
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    template<typename t>
+    const T& min_max(t& max_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "min_max() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_min = _data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val<min_value) { min_value = val; ptr_min = ptrs; }
+        if (val>max_value) max_value = val;
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    //! Return a reference to the maximum pixel value and return also the minimum pixel value.
+    template<typename t>
+    T& max_min(t& min_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max_min() : Empty instance.",
+                                    cimg_instance);
+      T *ptr_max = _data;
+      T max_value = *ptr_max, min_value = max_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val>max_value) { max_value = val; ptr_max = ptrs; }
+        if (val<min_value) min_value = val;
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    template<typename t>
+    const T& max_min(t& min_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "max_min() : Empty instance.",
+                                    cimg_instance);
+      const T *ptr_max = _data;
+      T max_value = *ptr_max, min_value = max_value;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val>max_value) { max_value = val; ptr_max = ptrs; }
+        if (val<min_value) min_value = val;
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    //! Return the kth smallest element of the image.
+    T kth_smallest(const unsigned int k) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "kth_smallest() : Empty instance.",
+                                    cimg_instance);
+      CImg<T> arr(*this);
+      unsigned int l = 0, ir = size() - 1;
+      for (;;) {
+        if (ir<=l+1) {
+          if (ir==l+1 && arr[ir]<arr[l]) cimg::swap(arr[l],arr[ir]);
+          return arr[k];
+        } else {
+          const unsigned int mid = (l + ir)>>1;
+          cimg::swap(arr[mid],arr[l+1]);
+          if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]);
+          if (arr[l+1]>arr[ir]) cimg::swap(arr[l+1],arr[ir]);
+          if (arr[l]>arr[l+1]) cimg::swap(arr[l],arr[l+1]);
+          unsigned int i = l + 1, j = ir;
+          const T pivot = arr[l+1];
+          for (;;) {
+            do ++i; while (arr[i]<pivot);
+            do --j; while (arr[j]>pivot);
+            if (j<i) break;
+            cimg::swap(arr[i],arr[j]);
+          }
+          arr[l+1] = arr[j];
+          arr[j] = pivot;
+          if (j>=k) ir = j - 1;
+          if (j<=k) l = i;
+        }
+      }
+      return 0;
+    }
+
+    //! Return the median value of the image.
+    T median() const {
+      const unsigned int s = size();
+      const T res = kth_smallest(s>>1);
+      return (s%2)?res:((res+kth_smallest((s>>1)-1))/2);
+    }
+
+    //! Return the sum of all the pixel values in an image.
+    Tdouble sum() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "sum() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      cimg_for(*this,ptrs,T) res+=(Tdouble)*ptrs;
+      return res;
+    }
+
+    //! Return the mean pixel value of the instance image.
+    Tdouble mean() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "mean() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      cimg_for(*this,ptrs,T) res+=(Tdouble)*ptrs;
+      return res/size();
+    }
+
+    //! Return the variance of the image.
+    /**
+       @param variance_method Determines how to calculate the variance
+       <table border="0">
+       <tr><td>0</td>
+       <td>Second moment:
+       @f$ v = 1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2
+       = 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right) @f$
+       with @f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$</td></tr>
+       <tr><td>1</td>
+       <td>Best unbiased estimator: @f$ v = \frac{1}{N-1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 @f$</td></tr>
+       <tr><td>2</td>
+       <td>Least median of squares</td></tr>
+       <tr><td>3</td>
+       <td>Least trimmed of squares</td></tr>
+       </table>
+    */
+    Tdouble variance(const unsigned int variance_method=1) const {
+      Tdouble foo;
+      return variance_mean(variance_method,foo);
+    }
+
+    //! Return the variance and the mean of the image.
+    template<typename t>
+    Tdouble variance_mean(const unsigned int variance_method, t& mean) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "variance() : Empty instance.",
+                                    cimg_instance);
+
+      Tdouble variance = 0, average = 0;
+      const unsigned int siz = size();
+      switch (variance_method) {
+      case 0 :{ // Least mean square (standard definition)
+        Tdouble S = 0, S2 = 0;
+        cimg_for(*this,ptrs,T) { const Tdouble val = (Tdouble)*ptrs; S+=val; S2+=val*val; }
+        variance = (S2 - S*S/siz)/siz;
+        average = S;
+      } break;
+      case 1 : { // Least mean square (robust definition)
+        Tdouble S = 0, S2 = 0;
+        cimg_for(*this,ptrs,T) { const Tdouble val = (Tdouble)*ptrs; S+=val; S2+=val*val; }
+        variance = siz>1?(S2 - S*S/siz)/(siz - 1):0;
+        average = S;
+      } break;
+      case 2 : { // Least Median of Squares (MAD)
+        CImg<Tfloat> buf(*this);
+        buf.sort();
+        const unsigned int siz2 = siz>>1;
+        const Tdouble med_i = (double)buf[siz2];
+        cimg_for(buf,ptrs,Tfloat) { const Tdouble val = (Tdouble)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val; }
+        buf.sort();
+        const Tdouble sig = (Tdouble)(1.4828*buf[siz2]);
+        variance = sig*sig;
+      } break;
+      default : { // Least trimmed of Squares
+        CImg<Tfloat> buf(*this);
+        const unsigned int siz2 = siz>>1;
+        cimg_for(buf,ptrs,Tfloat) { const Tdouble val = (Tdouble)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val; }
+        buf.sort();
+        Tdouble a = 0;
+        const Tfloat *ptrs = buf._data;
+        for (unsigned int j = 0; j<siz2; ++j) a+=(Tdouble)*(ptrs++);
+        const Tdouble sig = (Tdouble)(2.6477*std::sqrt(a/siz2));
+        variance = sig*sig;
+      }
+      }
+      mean = (t)(average/siz);
+      return variance>0?variance:0;
+    }
+
+    //! Estimate noise variance of the instance image.
+    Tdouble variance_noise(const unsigned int variance_method=1) const {
+      const unsigned int siz = size();
+      if (!siz || !_data) return 0;
+      if (variance_method>1) return get_laplacian().variance(variance_method);
+      Tdouble variance = 0, S = 0, S2 = 0;
+      if (_depth==1) {
+        CImg_3x3(I,T);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T) {
+          const Tdouble val = (Tdouble)Inc + (Tdouble)Ipc + (Tdouble)Icn + (Tdouble)Icp - 4*(Tdouble)Icc;
+          S+=val; S2+=val*val;
+        }
+      } else {
+        CImg_3x3x3(I,T);
+        cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,T) {
+          const Tdouble val =
+            (Tdouble)Incc + (Tdouble)Ipcc + (Tdouble)Icnc + (Tdouble)Icpc +
+            (Tdouble)Iccn + (Tdouble)Iccp - 6*(Tdouble)Iccc;
+          S+=val; S2+=val*val;
+        }
+      }
+      if (variance_method) variance = siz>1?(S2 - S*S/siz)/(siz - 1):0;
+      else variance = (S2 - S*S/siz)/siz;
+      return variance>0?variance:0;
+    }
+
+    //! Compute the MSE (Mean-Squared Error) between two images.
+    template<typename t>
+    Tdouble MSE(const CImg<t>& img) const {
+      if (img.size()!=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "MSE() : Instance and specified image (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data);
+      Tdouble vMSE = 0;
+      const t* ptr2 = img.end();
+      cimg_for(*this,ptr1,T) {
+        const Tdouble diff = (Tdouble)*ptr1 - (Tdouble)*(--ptr2);
+        vMSE+=diff*diff;
+      }
+      vMSE/=img.size();
+      return vMSE;
+    }
+
+    //! Compute the PSNR between two images.
+    template<typename t>
+    Tdouble PSNR(const CImg<t>& img, const Tdouble valmax=255) const {
+      const Tdouble vMSE = (Tdouble)std::sqrt(MSE(img));
+      return (vMSE!=0)?(Tdouble)(20*std::log10(valmax/vMSE)):(Tdouble)(cimg::type<Tdouble>::max());
+    }
+
+    //! Evaluate math expression.
+    /**
+       If you make successive evaluations on the same image and with the same expression,
+       you can set 'expr' to 0 after the first call, to skip the math parsing step.
+    **/
+    double eval(const char *const expression, const double x=0, const double y=0, const double z=0, const double c=0) const {
+      static _cimg_math_parser *mp = 0;
+      if (expression) { if (mp) delete mp; mp = 0; mp = new _cimg_math_parser(*this,expression,"eval"); }
+      if (!mp)
+        throw CImgArgumentException(_cimg_instance
+                                    "eval() : No expression has been previously specified.",
+                                    cimg_instance);
+      return mp->eval(x,y,z,c);
+    }
+
+    //! Compute a statistics vector (min,max,mean,variance,xmin,ymin,zmin,cmin,xmax,ymax,zmax,cmax).
+    CImg<T>& stats(const unsigned int variance_method=1) {
+      return get_stats(variance_method).move_to(*this);
+    }
+
+    CImg<Tdouble> get_stats(const unsigned int variance_method=1) const {
+      if (is_empty()) return CImg<doubleT>();
+      const unsigned int siz = size();
+      const T *const odata = _data;
+      const T *pm = odata, *pM = odata;
+      Tdouble S = 0, S2 = 0;
+      T m = *pm, M = m;
+      cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        const Tdouble _val = (Tdouble)val;
+        if (val<m) { m = val; pm = ptrs; }
+        if (val>M) { M = val; pM = ptrs; }
+        S+=_val;
+        S2+=_val*_val;
+      }
+      const Tdouble
+        mean_value = S/siz,
+        _variance_value = variance_method==0?(S2 - S*S/siz)/siz:
+        (variance_method==1?(siz>1?(S2 - S*S/siz)/(siz - 1):0):
+         variance(variance_method)),
+        variance_value = _variance_value>0?_variance_value:0;
+      int
+        xm = 0, ym = 0, zm = 0, cm = 0,
+        xM = 0, yM = 0, zM = 0, cM = 0;
+      contains(*pm,xm,ym,zm,cm);
+      contains(*pM,xM,yM,zM,cM);
+      return CImg<Tdouble>(1,12).fill((Tdouble)m,(Tdouble)M,mean_value,variance_value,
+                                      (Tdouble)xm,(Tdouble)ym,(Tdouble)zm,(Tdouble)cm,
+                                      (Tdouble)xM,(Tdouble)yM,(Tdouble)zM,(Tdouble)cM);
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Vector / Matrix Operations
+    //@{
+    //-------------------------------------
+
+    //! Return the norm of the current vector/matrix. \p ntype = norm type (0=L2, 1=L1, -1=Linf).
+    Tdouble magnitude(const int magnitude_type=2) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "magnitude() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      switch (magnitude_type) {
+      case -1 : {
+        cimg_for(*this,ptrs,T) { const Tdouble val = (Tdouble)cimg::abs(*ptrs); if (val>res) res = val; }
+      } break;
+      case 1 : {
+        cimg_for(*this,ptrs,T) res+=(Tdouble)cimg::abs(*ptrs);
+      } break;
+      default : {
+        cimg_for(*this,ptrs,T) res+=(Tdouble)cimg::sqr(*ptrs);
+        res = (Tdouble)std::sqrt(res);
+      }
+      }
+      return res;
+    }
+
+    //! Return the trace of the image, viewed as a matrix.
+    Tdouble trace() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "trace() : Empty instance.",
+                                    cimg_instance);
+      Tdouble res = 0;
+      cimg_forX(*this,k) res+=(Tdouble)(*this)(k,k);
+      return res;
+    }
+
+    //! Return the determinant of the image, viewed as a matrix.
+    Tdouble det() const {
+      if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "det() : Instance is empty or not a square matrix.",
+                                    cimg_instance);
+
+      switch (_width) {
+      case 1 : return (Tdouble)((*this)(0,0));
+      case 2 : return (Tdouble)((*this)(0,0))*(Tdouble)((*this)(1,1)) - (Tdouble)((*this)(0,1))*(Tdouble)((*this)(1,0));
+      case 3 : {
+        const Tdouble
+          a = (Tdouble)_data[0], d = (Tdouble)_data[1], g = (Tdouble)_data[2],
+          b = (Tdouble)_data[3], e = (Tdouble)_data[4], h = (Tdouble)_data[5],
+          c = (Tdouble)_data[6], f = (Tdouble)_data[7], i = (Tdouble)_data[8];
+        return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e;
+      }
+      default : {
+        CImg<Tfloat> lu(*this);
+        CImg<uintT> indx;
+        bool d;
+        lu._LU(indx,d);
+        Tdouble res = d?(Tdouble)1:(Tdouble)-1;
+        cimg_forX(lu,i) res*=lu(i,i);
+        return res;
+      }
+      }
+      return 0;
+    }
+
+    //! Return the dot product of the current vector/matrix with the vector/matrix \p img.
+    template<typename t>
+    Tdouble dot(const CImg<t>& img) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "dot() : Empty instance.",
+                                    cimg_instance);
+      if (!img)
+        throw CImgArgumentException(_cimg_instance
+                                    "dot() : Empty specified image.",
+                                    cimg_instance);
+
+      const unsigned int nb = cimg::min(size(),img.size());
+      Tdouble res = 0;
+      for (unsigned int off = 0; off<nb; ++off) res+=(Tdouble)_data[off]*(Tdouble)img[off];
+      return res;
+    }
+
+    //! Return a new image corresponding to the vector located at (\p x,\p y,\p z) of the current vector-valued image.
+    CImg<T> get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
+      static CImg<T> res;
+      if (res._height!=_spectrum) res.assign(1,_spectrum);
+      const unsigned int whd = _width*_height*_depth;
+      const T *ptrs = data(x,y,z);
+      T *ptrd = res._data;
+      cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+      return res;
+    }
+
+    //! Return a new image corresponding to the \a square \a matrix located at (\p x,\p y,\p z) of the current vector-valued image.
+    CImg<T> get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const {
+      const int n = (int)std::sqrt((double)_spectrum);
+      const T *ptrs = data(x,y,z,0);
+      const unsigned int whd = _width*_height*_depth;
+      CImg<T> res(n,n);
+      T *ptrd = res._data;
+      cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+      return res;
+    }
+
+    //! Return a new image corresponding to the \a diffusion \a tensor located at (\p x,\p y,\p z) of the current vector-valued image.
+    CImg<T> get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
+      const T *ptrs = data(x,y,z,0);
+      const unsigned int whd = _width*_height*_depth;
+      if (_spectrum==6) return tensor(*ptrs,*(ptrs+whd),*(ptrs+2*whd),*(ptrs+3*whd),*(ptrs+4*whd),*(ptrs+5*whd));
+      if (_spectrum==3) return tensor(*ptrs,*(ptrs+whd),*(ptrs+2*whd));
+      return tensor(*ptrs);
+    }
+
+    //! Set the image \p vec as the \a vector \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
+    template<typename t>
+    CImg<T>& set_vector_at(const CImg<t>& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) {
+      if (x<_width && y<_height && z<_depth) {
+        const t *ptrs = vec._data;
+        const unsigned int whd = _width*_height*_depth;
+        T *ptrd = data(x,y,z);
+        for (unsigned int k = cimg::min((unsigned int)vec.size(),_spectrum); k; --k) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+      }
+      return *this;
+    }
+
+    //! Set the image \p vec as the \a square \a matrix-valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
+    template<typename t>
+    CImg<T>& set_matrix_at(const CImg<t>& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
+      return set_vector_at(mat,x,y,z);
+    }
+
+    //! Set the image \p vec as the \a tensor \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
+    template<typename t>
+    CImg<T>& set_tensor_at(const CImg<t>& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
+      T *ptrd = data(x,y,z,0);
+      const unsigned int siz = _width*_height*_depth;
+      if (ten._height==2) {
+        *ptrd = (T)ten[0]; ptrd+=siz;
+        *ptrd = (T)ten[1]; ptrd+=siz;
+        *ptrd = (T)ten[3];
+      }
+      else {
+        *ptrd = (T)ten[0]; ptrd+=siz;
+        *ptrd = (T)ten[1]; ptrd+=siz;
+        *ptrd = (T)ten[2]; ptrd+=siz;
+        *ptrd = (T)ten[4]; ptrd+=siz;
+        *ptrd = (T)ten[5]; ptrd+=siz;
+        *ptrd = (T)ten[8];
+      }
+      return *this;
+    }
+
+    //! Unroll all images values into a one-column vector.
+    CImg<T>& vector() {
+      return unroll('y');
+    }
+
+    CImg<T> get_vector() const {
+      return get_unroll('y');
+    }
+
+    //! Realign pixel values of the instance image as a square matrix
+    CImg<T>& matrix() {
+      const unsigned int siz = size();
+      switch (siz) {
+      case 1 : break;
+      case 4 : _width = _height = 2; break;
+      case 9 : _width = _height = 3; break;
+      case 16 : _width = _height = 4; break;
+      case 25 : _width = _height = 5; break;
+      case 36 : _width = _height = 6; break;
+      case 49 : _width = _height = 7; break;
+      case 64 : _width = _height = 8; break;
+      case 81 : _width = _height = 9; break;
+      case 100 : _width = _height = 10; break;
+      default : {
+        unsigned int i = 11, i2 = i*i;
+        while (i2<siz) { i2+=2*i + 1; ++i; }
+        if (i2==siz) _width = _height = i;
+        else throw CImgInstanceException(_cimg_instance
+                                         "matrix() : Invalid instance size %u (should be a square integer).",
+                                         cimg_instance,
+                                         siz);
+      }
+      }
+      return *this;
+    }
+
+    CImg<T> get_matrix() const {
+      return (+*this).matrix();
+    }
+
+    //! Realign pixel values of the instance image as a symmetric tensor.
+    CImg<T>& tensor() {
+      return get_tensor().move_to(*this);
+    }
+
+    CImg<T> get_tensor() const {
+      CImg<T> res;
+      const unsigned int siz = size();
+      switch (siz) {
+      case 1 : break;
+      case 3 :
+        res.assign(2,2);
+        res(0,0) = (*this)(0);
+        res(1,0) = res(0,1) = (*this)(1);
+        res(1,1) = (*this)(2);
+        break;
+      case 6 :
+        res.assign(3,3);
+        res(0,0) = (*this)(0);
+        res(1,0) = res(0,1) = (*this)(1);
+        res(2,0) = res(0,2) = (*this)(2);
+        res(1,1) = (*this)(3);
+        res(2,1) = res(1,2) = (*this)(4);
+        res(2,2) = (*this)(5);
+        break;
+      default :
+        throw CImgInstanceException(_cimg_instance
+                                    "tensor() : Invalid instance size (does not define a 1x1, 2x2 or 3x3 tensor).",
+                                    cimg_instance);
+      }
+      return res;
+    }
+
+    //! Get a diagonal matrix, whose diagonal coefficients are the coefficients of the input image.
+    CImg<T>& diagonal() {
+      return get_diagonal().move_to(*this);
+    }
+
+    CImg<T> get_diagonal() const {
+      if (is_empty()) return *this;
+      CImg<T> res(size(),size(),1,1,0);
+      cimg_foroff(*this,off) res(off,off) = (*this)(off);
+      return res;
+    }
+
+    //! Get an identity matrix having same dimension than instance image.
+    CImg<T>& identity_matrix() {
+      return identity_matrix(cimg::max(_width,_height)).move_to(*this);
+    }
+
+    CImg<T> get_identity_matrix() const {
+      return identity_matrix(cimg::max(_width,_height));
+    }
+
+    //! Return a N-numbered sequence vector from \p a0 to \p a1.
+    CImg<T>& sequence(const T a0, const T a1) {
+      if (is_empty()) return *this;
+      const unsigned int siz = size() - 1;
+      T* ptr = _data;
+      if (siz) {
+        const Tdouble delta = (Tdouble)a1 - (Tdouble)a0;
+        cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz);
+      } else *ptr = a0;
+      return *this;
+    }
+
+    CImg<T> get_sequence(const T a0, const T a1) const {
+      return (+*this).sequence(a0,a1);
+    }
+
+    //! Transpose the current matrix.
+    CImg<T>& transpose() {
+      if (_width==1) { _width = _height; _height = 1; return *this; }
+      if (_height==1) { _height = _width; _width = 1; return *this; }
+      if (_width==_height) {
+        cimg_forYZC(*this,y,z,c) for (int x = y; x<width(); ++x) cimg::swap((*this)(x,y,z,c),(*this)(y,x,z,c));
+        return *this;
+      }
+      return get_transpose().move_to(*this);
+    }
+
+    CImg<T> get_transpose() const {
+      return get_permute_axes("yxzc");
+    }
+
+    //! Compute the cross product between two 3d vectors.
+    template<typename t>
+    CImg<T>& cross(const CImg<t>& img) {
+      if (_width!=1 || _height<3 || img._width!=1 || img._height<3)
+        throw CImgInstanceException(_cimg_instance
+                                    "cross() : Instance and/or specified image (%u,%u,%u,%u,%p) are not 3d vectors.",
+                                    cimg_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data);
+
+      const T x = (*this)[0], y = (*this)[1], z = (*this)[2];
+      (*this)[0] = (T)(y*img[2] - z*img[1]);
+      (*this)[1] = (T)(z*img[0] - x*img[2]);
+      (*this)[2] = (T)(x*img[1] - y*img[0]);
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_cross(const CImg<t>& img) const {
+      return CImg<_cimg_Tt>(*this).cross(img);
+    }
+
+    //! Invert the current matrix.
+    CImg<T>& invert(const bool use_LU=true) {
+      if (_width!=_height || _depth!=1 || _spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "invert() : Instance is not a square matrix.",
+                                    cimg_instance);
+#ifdef cimg_use_lapack
+      int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N];
+      Tfloat
+        *const lapA = new Tfloat[N*N],
+        *const WORK = new Tfloat[LWORK];
+      cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
+      cimg::getrf(N,lapA,IPIV,INFO);
+      if (INFO)
+        cimg::warn(_cimg_instance
+                   "invert() : LAPACK function dgetrf_() returned error code %d.",
+                   cimg_instance,
+                   INFO);
+      else {
+        cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO);
+        if (INFO)
+          cimg::warn(_cimg_instance
+                     "invert() : LAPACK function dgetri_() returned error code %d.",
+                     cimg_instance,
+                     INFO);
+      }
+      if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N+l]); else fill(0);
+      delete[] IPIV; delete[] lapA; delete[] WORK;
+#else
+      const double dete = _width>3?-1.0:det();
+      if (dete!=0.0 && _width==2) {
+        const double
+          a = _data[0], c = _data[1],
+          b = _data[2], d = _data[3];
+        _data[0] = (T)(d/dete); _data[1] = (T)(-c/dete);
+        _data[2] = (T)(-b/dete); _data[3] = (T)(a/dete);
+      } else if (dete!=0.0 && _width==3) {
+        const double
+          a = _data[0], d = _data[1], g = _data[2],
+          b = _data[3], e = _data[4], h = _data[5],
+          c = _data[6], f = _data[7], i = _data[8];
+        _data[0] = (T)((i*e-f*h)/dete), _data[1] = (T)((g*f-i*d)/dete), _data[2] = (T)((d*h-g*e)/dete);
+        _data[3] = (T)((h*c-i*b)/dete), _data[4] = (T)((i*a-c*g)/dete), _data[5] = (T)((g*b-a*h)/dete);
+        _data[6] = (T)((b*f-e*c)/dete), _data[7] = (T)((d*c-a*f)/dete), _data[8] = (T)((a*e-d*b)/dete);
+      } else {
+        if (use_LU) { // LU-based inverse computation
+          CImg<Tfloat> A(*this), indx, col(1,_width);
+          bool d;
+          A._LU(indx,d);
+          cimg_forX(*this,j) {
+            col.fill(0);
+            col(j) = 1;
+            col._solve(A,indx);
+            cimg_forX(*this,i) (*this)(j,i) = (T)col(i);
+          }
+        } else { // SVD-based inverse computation
+          CImg<Tfloat> U(_width,_width), S(1,_width), V(_width,_width);
+          SVD(U,S,V,false);
+          U.transpose();
+          cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k];
+          S.diagonal();
+          *this = V*S*U;
+        }
+      }
+#endif
+      return *this;
+    }
+
+    CImg<Tfloat> get_invert(const bool use_LU=true) const {
+      return CImg<Tfloat>(*this,false).invert(use_LU);
+    }
+
+    //! Compute the pseudo-inverse (Moore-Penrose) of the matrix.
+    CImg<T>& pseudoinvert() {
+      return get_pseudoinvert().move_to(*this);
+    }
+
+    CImg<Tfloat> get_pseudoinvert() const {
+      CImg<Tfloat> U, S, V;
+      SVD(U,S,V);
+      cimg_forX(V,x) {
+        const Tfloat s = S(x), invs = s!=0?1/s:(Tfloat)0;
+        cimg_forY(V,y) V(x,y)*=invs;
+      }
+      return V*U.transpose();
+    }
+
+    //! Solve a linear system AX=B where B=*this.
+    template<typename t>
+    CImg<T>& solve(const CImg<t>& A) {
+      if (_width!=1 || _depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "solve() : Instance and specified matrix (%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    A._width,A._height,A._depth,A._spectrum,A._data);
+
+      typedef _cimg_Ttfloat Ttfloat;
+      if (A._width==A._height) {
+#ifdef cimg_use_lapack
+        char TRANS='N';
+        int INFO, N = _height, LWORK = 4*N, one = 1, *const IPIV = new int[N];
+        Ttfloat
+          *const lapA = new Ttfloat[N*N],
+          *const lapB = new Ttfloat[N],
+          *const WORK = new Ttfloat[LWORK];
+        cimg_forXY(A,k,l) lapA[k*N+l] = (Ttfloat)(A(k,l));
+        cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i));
+        cimg::getrf(N,lapA,IPIV,INFO);
+        if (INFO)
+          cimg::warn(_cimg_instance
+                     "solve() : LAPACK library function dgetrf_() returned error code %d.",
+                     cimg_instance,
+                     INFO);
+
+        if (!INFO) {
+          cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
+          if (INFO)
+            cimg::warn(_cimg_instance
+                       "solve() : LAPACK library function dgetrs_() returned error code %d.",
+                       cimg_instance,
+                       INFO);
+        }
+        if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0);
+        delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
+#else
+        CImg<Ttfloat> lu(A);
+        CImg<Ttfloat> indx;
+        bool d;
+        lu._LU(indx,d);
+        _solve(lu,indx);
+#endif
+      } else assign(A.get_pseudoinvert()*(*this));
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Ttfloat> get_solve(const CImg<t>& A) const {
+      return CImg<_cimg_Ttfloat>(*this,false).solve(A);
+    }
+
+    template<typename t, typename ti>
+    CImg<T>& _solve(const CImg<t>& A, const CImg<ti>& indx) {
+      typedef _cimg_Ttfloat Ttfloat;
+      const int N = size();
+      int ii = -1;
+      Ttfloat sum;
+      for (int i = 0; i<N; ++i) {
+        const int ip = (int)indx[i];
+        Ttfloat sum = (*this)(ip);
+        (*this)(ip) = (*this)(i);
+        if (ii>=0) for (int j = ii; j<=i-1; ++j) sum-=A(j,i)*(*this)(j);
+        else if (sum!=0) ii = i;
+        (*this)(i) = (T)sum;
+      }
+      for (int i = N - 1; i>=0; --i) {
+        sum = (*this)(i);
+        for (int j = i + 1; j<N; ++j) sum-=A(j,i)*(*this)(j);
+        (*this)(i) = (T)(sum/A(i,i));
+      }
+      return *this;
+    }
+
+    //! Solve a linear system AX=B where B=*this and A is a tridiagonal matrix A = [ b0,c0,0,...; a1,b1,c1,0,... ; ... ; ...,0,aN,bN ].
+    // (Use the Thomas Algorithm).
+    template<typename t>
+    CImg<T>& solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) {
+      const int siz = (int)size();
+      if ((int)a.size()!=siz || (int)b.size()!=siz || (int)c.size()!=siz)
+        throw CImgArgumentException(_cimg_instance
+                                    "solve_tridiagonal() : Instance and tridiagonal coefficients "
+                                    "(%u,%u,%u,%u,%p), (%u,%u,%u,%u,%p) and (%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    a._width,a._height,a._depth,a._spectrum,a._data,
+                                    b._width,b._height,b._depth,b._spectrum,b._data,
+                                    c._width,c._height,c._depth,c._spectrum,c._data);
+
+      typedef _cimg_Ttfloat Ttfloat;
+      CImg<Ttfloat> nc(siz);
+      const T *ptra = a._data, *ptrb = b._data, *ptrc = c._data;
+      T *ptrnc = nc._data, *ptrd = _data;
+      const Ttfloat valb0 = (Ttfloat)*(ptrb++);
+      *ptrnc = *(ptrc++)/valb0;
+      Ttfloat vald = (Ttfloat)(*(ptrd++)/=valb0);
+      for (int i = 1; i<siz; ++i) {
+        const Ttfloat
+          vala = (Tfloat)*(ptra++),
+          id = 1/(*(ptrb++) - *(ptrnc++)*vala);
+        *ptrnc = *(ptrc++)*id;
+        vald = ((*ptrd-=vala*vald)*=id);
+        ++ptrd;
+      }
+      vald = *(--ptrd);
+      for (int i = siz-2; i>=0; --i) vald = (*(--ptrd)-=*(--ptrnc)*vald);
+      return *this;
+    }
+
+    template<typename t>
+    CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) const {
+      return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(a,b,c);
+    }
+
+    //! Compute the eigenvalues and eigenvectors of a matrix.
+    template<typename t>
+    const CImg<T>& eigen(CImg<t>& val, CImg<t> &vec) const {
+      if (is_empty()) { val.assign(); vec.assign(); }
+      else {
+        if (_width!=_height || _depth>1 || _spectrum>1)
+          throw CImgInstanceException(_cimg_instance
+                                      "eigen() : Instance is not a square matrix.",
+                                      cimg_instance);
+
+        if (val.size()<_width) val.assign(1,_width);
+        if (vec.size()<_width*_width) vec.assign(_width,_width);
+        switch (_width) {
+        case 1 : { val[0] = (t)(*this)[0]; vec[0] = (t)1; } break;
+        case 2 : {
+          const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a + d;
+          double f = e*e - 4*(a*d - b*c);
+          if (f<0)
+            cimg::warn(_cimg_instance
+                       "CImg<%s>::eigen() : Complex eigenvalues found.",
+                       cimg_instance);
+
+          f = std::sqrt(f);
+          const double l1 = 0.5*(e-f), l2 = 0.5*(e+f);
+          const double theta1 = std::atan2(l2-a,b), theta2 = std::atan2(l1-a,b);
+          val[0] = (t)l2;
+          val[1] = (t)l1;
+          vec(0,0) = (t)std::cos(theta1);
+          vec(0,1) = (t)std::sin(theta1);
+          vec(1,0) = (t)std::cos(theta2);
+          vec(1,1) = (t)std::sin(theta2);
+        } break;
+        default :
+          throw CImgInstanceException(_cimg_instance
+                                      "eigen() : Eigenvalues computation of general matrices is limited to 2x2 matrices.",
+                                      cimg_instance);
+        }
+      }
+      return *this;
+    }
+
+    CImgList<Tfloat> get_eigen() const {
+      CImgList<Tfloat> res(2);
+      eigen(res[0],res[1]);
+      return res;
+    }
+
+    //! Compute the eigenvalues and eigenvectors of a symmetric matrix.
+    template<typename t>
+    const CImg<T>& symmetric_eigen(CImg<t>& val, CImg<t>& vec) const {
+      if (is_empty()) { val.assign(); vec.assign(); }
+      else {
+#ifdef cimg_use_lapack
+        char JOB = 'V', UPLO = 'U';
+        int N = _width, LWORK = 4*N, INFO;
+        Tfloat
+          *const lapA = new Tfloat[N*N],
+          *const lapW = new Tfloat[N],
+          *const WORK = new Tfloat[LWORK];
+        cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
+        cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO);
+        if (INFO)
+          cimg::warn(_cimg_instance
+                     "symmetric_eigen() : LAPACK library function dsyev_() returned error code %d.",
+                     cimg_instance,
+                     INFO);
+
+        val.assign(1,N);
+        vec.assign(N,N);
+        if (!INFO) {
+          cimg_forY(val,i) val(i) = (T)lapW[N-1-i];
+          cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N-1-k)*N+l]);
+        } else { val.fill(0); vec.fill(0); }
+        delete[] lapA; delete[] lapW; delete[] WORK;
+#else
+        if (_width!=_height || _depth>1 || _spectrum>1)
+          throw CImgInstanceException(_cimg_instance
+                                      "eigen() : Instance is not a square matrix.",
+                                      cimg_instance);
+
+        val.assign(1,_width);
+        if (vec._data) vec.assign(_width,_width);
+        if (_width<3) return eigen(val,vec);
+        CImg<t> V(_width,_width);
+        SVD(vec,val,V,false);
+        bool is_ambiguous = false;
+        float eig = 0;
+        cimg_forY(val,p) {       // check for ambiguous cases.
+          if (val[p]>eig) eig = (float)val[p];
+          t scal = 0;
+          cimg_forY(vec,y) scal+=vec(p,y)*V(p,y);
+          if (cimg::abs(scal)<0.9f) is_ambiguous = true;
+          if (scal<0) val[p] = -val[p];
+        }
+        if (is_ambiguous) {
+          ++(eig*=2);
+          SVD(vec,val,V,false,40,eig);
+          val-=eig;
+        }
+        CImg<intT> permutations;  // sort eigenvalues in decreasing order
+        CImg<t> tmp(_width);
+        val.sort(permutations,false);
+        cimg_forY(vec,k) {
+          cimg_forY(permutations,y) tmp(y) = vec(permutations(y),k);
+          std::memcpy(vec.data(0,k),tmp._data,sizeof(t)*_width);
+        }
+#endif
+      }
+      return *this;
+    }
+
+    CImgList<Tfloat> get_symmetric_eigen() const {
+      CImgList<Tfloat> res(2);
+      symmetric_eigen(res[0],res[1]);
+      return res;
+    }
+
+    //! Sort values of a vector and get corresponding permutations.
+    template<typename t>
+    CImg<T>& sort(CImg<t>& permutations, const bool increasing=true) {
+      permutations.assign(_width,_height,_depth,_spectrum);
+      if (is_empty()) return *this;
+      cimg_foroff(permutations,off) permutations[off] = (t)off;
+      return _quicksort(0,size()-1,permutations,increasing,true);
+    }
+
+    template<typename t>
+    CImg<T> get_sort(CImg<t>& permutations, const bool increasing=true) const {
+      return (+*this).sort(permutations,increasing);
+    }
+
+    //! Sort image values.
+    CImg<T>& sort(const bool increasing=true, const char axis=0) {
+      if (is_empty()) return *this;
+      CImg<uintT> perm;
+      switch (axis) {
+      case 0 :
+        _quicksort(0,size()-1,perm,increasing,false);
+        break;
+      case 'x' : {
+        perm.assign(_width);
+        get_crop(0,0,0,0,_width-1,0,0,0).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(perm[x],y,z,c);
+      } break;
+      case 'y' : {
+        perm.assign(_height);
+        get_crop(0,0,0,0,0,_height-1,0,0).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,perm[y],z,c);
+      } break;
+      case 'z' : {
+        perm.assign(_depth);
+        get_crop(0,0,0,0,0,0,_depth-1,0).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,perm[z],c);
+      } break;
+      case 'c' : {
+        perm.assign(_spectrum);
+        get_crop(0,0,0,0,0,0,0,_spectrum-1).sort(perm,increasing);
+        CImg<T> img(*this,false);
+        cimg_forXYZC(*this,x,y,z,c) (*this)(x,y,z,c) = img(x,y,z,perm[c]);
+      } break;
+      default :
+        throw CImgArgumentException(_cimg_instance
+                                    "sort() : Invalid specified axis '%c' "
+                                    "(should be { x | y | z | c }).",
+                                    cimg_instance,axis);
+      }
+      return *this;
+    }
+
+    CImg<T> get_sort(const bool increasing=true, const char axis=0) const {
+      return (+*this).sort(increasing,axis);
+    }
+
+    template<typename t>
+    CImg<T>& _quicksort(const int indm, const int indM, CImg<t>& permutations, const bool increasing, const bool is_permutations) {
+      if (indm<indM) {
+        const int mid = (indm + indM)/2;
+        if (increasing) {
+          if ((*this)[indm]>(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+          if ((*this)[mid]>(*this)[indM]) {
+            cimg::swap((*this)[indM],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indM],permutations[mid]);
+          }
+          if ((*this)[indm]>(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+        } else {
+          if ((*this)[indm]<(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+          if ((*this)[mid]<(*this)[indM]) {
+            cimg::swap((*this)[indM],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indM],permutations[mid]);
+          }
+          if ((*this)[indm]<(*this)[mid]) {
+            cimg::swap((*this)[indm],(*this)[mid]); if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
+          }
+        }
+        if (indM - indm>=3) {
+          const T pivot = (*this)[mid];
+          int i = indm, j = indM;
+          if (increasing) {
+            do {
+              while ((*this)[i]<pivot) ++i;
+              while ((*this)[j]>pivot) --j;
+              if (i<=j) {
+                if (is_permutations) cimg::swap(permutations[i],permutations[j]);
+                cimg::swap((*this)[i++],(*this)[j--]);
+              }
+            } while (i<=j);
+          } else {
+            do {
+              while ((*this)[i]>pivot) ++i;
+              while ((*this)[j]<pivot) --j;
+              if (i<=j) {
+                if (is_permutations) cimg::swap(permutations[i],permutations[j]);
+                cimg::swap((*this)[i++],(*this)[j--]);
+              }
+            } while (i<=j);
+          }
+          if (indm<j) _quicksort(indm,j,permutations,increasing,is_permutations);
+          if (i<indM) _quicksort(i,indM,permutations,increasing,is_permutations);
+        }
+      }
+      return *this;
+    }
+
+    //! Compute the SVD of a general matrix.
+    template<typename t>
+    const CImg<T>& SVD(CImg<t>& U, CImg<t>& S, CImg<t>& V, const bool sorting=true,
+                       const unsigned int max_iteration=40, const float lambda=0) const {
+      if (is_empty()) { U.assign(); S.assign(); V.assign(); }
+      else {
+        U = *this;
+        if (lambda!=0) {
+          const unsigned int delta = cimg::min(U._width,U._height);
+          for (unsigned int i = 0; i<delta; ++i) U(i,i) = (t)(U(i,i) + lambda);
+        }
+        if (S.size()<_width) S.assign(1,_width);
+        if (V._width<_width || V._height<_height) V.assign(_width,_width);
+        CImg<t> rv1(_width);
+        t anorm = 0, c, f, g = 0, h, s, scale = 0;
+        int l = 0, nm = 0;
+
+        cimg_forX(U,i) {
+          l = i+1; rv1[i] = scale*g; g = s = scale = 0;
+          if (i<height()) {
+            for (int k = i; k<height(); ++k) scale+= cimg::abs(U(i,k));
+            if (scale) {
+              for (int k = i; k<height(); ++k) { U(i,k)/=scale; s+= U(i,k)*U(i,k); }
+              f = U(i,i); g = (t)((f>=0?-1:1)*std::sqrt(s)); h=f*g-s; U(i,i) = f-g;
+              for (int j = l; j<width(); ++j) {
+                s = 0;
+                for (int k=i; k<height(); ++k) s+= U(i,k)*U(j,k);
+                f = s/h;
+                for (int k = i; k<height(); ++k) U(j,k)+= f*U(i,k);
+              }
+              for (int k = i; k<height(); ++k) U(i,k)*= scale;
+            }
+          }
+          S[i]=scale*g;
+
+          g = s = scale = 0;
+          if (i<height() && i!=width()-1) {
+            for (int k = l; k<width(); ++k) scale+=cimg::abs(U(k,i));
+            if (scale) {
+              for (int k = l; k<width(); ++k) { U(k,i)/= scale; s+= U(k,i)*U(k,i); }
+              f = U(l,i); g = (t)((f>=0?-1:1)*std::sqrt(s)); h = f*g-s; U(l,i) = f-g;
+              for (int k = l; k<width(); ++k) rv1[k]=U(k,i)/h;
+              for (int j = l; j<height(); ++j) {
+                s = 0;
+                for (int k = l; k<width(); ++k) s+= U(k,j)*U(k,i);
+                for (int k = l; k<width(); ++k) U(k,j)+= s*rv1[k];
+              }
+              for (int k = l; k<width(); ++k) U(k,i)*= scale;
+            }
+          }
+          anorm = (t)cimg::max((float)anorm,(float)(cimg::abs(S[i])+cimg::abs(rv1[i])));
+        }
+
+        for (int i = width()-1; i>=0; --i) {
+          if (i<width()-1) {
+            if (g) {
+              for (int j = l; j<width(); ++j) V(i,j) =(U(j,i)/U(l,i))/g;
+              for (int j = l; j<width(); ++j) {
+                s = 0;
+                for (int k = l; k<width(); ++k) s+= U(k,i)*V(j,k);
+                for (int k = l; k<width(); ++k) V(j,k)+= s*V(i,k);
+              }
+            }
+            for (int j = l; j<width(); ++j) V(j,i) = V(i,j) = (t)0.0;
+          }
+          V(i,i) = (t)1.0; g = rv1[i]; l = i;
+        }
+
+        for (int i = cimg::min(width(),height())-1; i>=0; --i) {
+          l = i+1; g = S[i];
+          for (int j = l; j<width(); ++j) U(j,i) = 0;
+          if (g) {
+            g = 1/g;
+            for (int j = l; j<width(); ++j) {
+              s = 0; for (int k = l; k<height(); ++k) s+= U(i,k)*U(j,k);
+              f = (s/U(i,i))*g;
+              for (int k = i; k<height(); ++k) U(j,k)+= f*U(i,k);
+            }
+            for (int j = i; j<height(); ++j) U(i,j)*= g;
+          } else for (int j = i; j<height(); ++j) U(i,j) = 0;
+          ++U(i,i);
+        }
+
+        for (int k = width()-1; k>=0; --k) {
+          for (unsigned int its = 0; its<max_iteration; ++its) {
+            bool flag = true;
+            for (l = k; l>=1; --l) {
+              nm = l-1;
+              if ((cimg::abs(rv1[l])+anorm)==anorm) { flag = false; break; }
+              if ((cimg::abs(S[nm])+anorm)==anorm) break;
+            }
+            if (flag) {
+              c = 0; s = 1;
+              for (int i = l; i<=k; ++i) {
+                f = s*rv1[i]; rv1[i] = c*rv1[i];
+                if ((cimg::abs(f)+anorm)==anorm) break;
+                g = S[i]; h = (t)cimg::_pythagore(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h;
+                cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c + z*s; U(i,j) = z*c - y*s; }
+              }
+            }
+            const t z = S[k];
+            if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; }
+            nm = k-1;
+            t x = S[l], y = S[nm];
+            g = rv1[nm]; h = rv1[k];
+            f = ((y-z)*(y+z)+(g-h)*(g+h))/(2*h*y);
+            g = (t)cimg::_pythagore(f,1.0);
+            f = ((x-z)*(x+z)+h*((y/(f+ (f>=0?g:-g)))-h))/x;
+            c = s = 1;
+            for (int j = l; j<=nm; ++j) {
+              const int i = j+1;
+              g = rv1[i]; h = s*g; g = c*g;
+              t y = S[i];
+              t z = (t)cimg::_pythagore(f,h);
+              rv1[j] = z; c = f/z; s = h/z;
+              f = x*c+g*s; g = g*c-x*s; h = y*s; y*=c;
+              cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c + z*s; V(i,jj) = z*c - x*s; }
+              z = (t)cimg::_pythagore(f,h); S[j] = z;
+              if (z) { z = 1/z; c = f*z; s = h*z; }
+              f = c*g+s*y; x = c*y-s*g;
+              cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c + z*s; U(i,jj) = z*c - y*s; }
+            }
+            rv1[l] = 0; rv1[k]=f; S[k]=x;
+          }
+        }
+
+        if (sorting) {
+          CImg<intT> permutations;
+          CImg<t> tmp(_width);
+          S.sort(permutations,false);
+          cimg_forY(U,k) {
+            cimg_forY(permutations,y) tmp(y) = U(permutations(y),k);
+            std::memcpy(U.data(0,k),tmp._data,sizeof(t)*_width);
+          }
+          cimg_forY(V,k) {
+            cimg_forY(permutations,y) tmp(y) = V(permutations(y),k);
+            std::memcpy(V.data(0,k),tmp._data,sizeof(t)*_width);
+          }
+        }
+      }
+      return *this;
+    }
+
+    CImgList<Tfloat> get_SVD(const bool sorting=true,
+                             const unsigned int max_iteration=40, const float lambda=0) const {
+      CImgList<Tfloat> res(3);
+      SVD(res[0],res[1],res[2],sorting,max_iteration,lambda);
+      return res;
+    }
+
+    // INNER ROUTINE : Compute the LU decomposition of a permuted matrix (c.f. numerical recipies)
+    template<typename t>
+    CImg<T>& _LU(CImg<t>& indx, bool& d) {
+      const int N = width();
+      int imax = 0;
+      CImg<Tfloat> vv(N);
+      indx.assign(N);
+      d = true;
+      cimg_forX(*this,i) {
+        Tfloat vmax = 0;
+        cimg_forX(*this,j) {
+          const Tfloat tmp = cimg::abs((*this)(j,i));
+          if (tmp>vmax) vmax = tmp;
+        }
+        if (vmax==0) { indx.fill(0); return fill(0); }
+        vv[i] = 1/vmax;
+      }
+      cimg_forX(*this,j) {
+        for (int i = 0; i<j; ++i) {
+          Tfloat sum=(*this)(j,i);
+          for (int k = 0; k<i; ++k) sum-=(*this)(k,i)*(*this)(j,k);
+          (*this)(j,i) = (T)sum;
+        }
+        Tfloat vmax = 0;
+        for (int i = j; i<width(); ++i) {
+          Tfloat sum=(*this)(j,i);
+          for (int k = 0; k<j; ++k) sum-=(*this)(k,i)*(*this)(j,k);
+          (*this)(j,i) = (T)sum;
+          const Tfloat tmp = vv[i]*cimg::abs(sum);
+          if (tmp>=vmax) { vmax=tmp; imax=i; }
+        }
+        if (j!=imax) {
+          cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j));
+          d =!d;
+          vv[imax] = vv[j];
+        }
+        indx[j] = (t)imax;
+        if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20;
+        if (j<N) {
+          const Tfloat tmp = 1/(Tfloat)(*this)(j,j);
+          for (int i=j+1; i<N; ++i) (*this)(j,i) = (T)((*this)(j,i)*tmp);
+        }
+      }
+      return *this;
+    }
+
+    //! Compute minimal path in a graph, using the Dijkstra algorithm.
+    /**
+       \param distance An object having operator()(unsigned int i, unsigned int j) which returns distance between two nodes (i,j).
+       \param nb_nodes Number of graph nodes.
+       \param starting_node Indice of the starting node.
+       \param ending_node Indice of the ending node (set to ~0U to ignore ending node).
+       \param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
+       \return Array of distances of each node to the starting node.
+    **/
+    template<typename tf, typename t>
+    static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
+                            const unsigned int starting_node, const unsigned int ending_node,
+                            CImg<t>& previous) {
+      if (starting_node>=nb_nodes)
+        throw CImgArgumentException("CImg<%s>::dijkstra() : Specified indice of starting node %u is higher than number of nodes %u.",
+                                    pixel_type(),starting_node,nb_nodes);
+      CImg<T> dist(1,nb_nodes,1,1,cimg::type<T>::max());
+      dist(starting_node) = 0;
+      previous.assign(1,nb_nodes,1,1,(t)-1);
+      previous(starting_node) = (t)starting_node;
+      CImg<uintT> Q(nb_nodes);
+      cimg_forX(Q,u) Q(u) = u;
+      cimg::swap(Q(starting_node),Q(0));
+      unsigned int sizeQ = nb_nodes;
+      while (sizeQ) {
+        // Update neighbors from minimal vertex
+        const unsigned int umin = Q(0);
+        if (umin==ending_node) sizeQ = 0;
+        else {
+          const T dmin = dist(umin);
+          const T infty = cimg::type<T>::max();
+          for (unsigned int q = 1; q<sizeQ; ++q) {
+            const unsigned int v = Q(q);
+            const T d = (T)distance(v,umin);
+            if (d<infty) {
+              const T alt = dmin + d;
+              if (alt<dist(v)) {
+                dist(v) = alt;
+                previous(v) = (t)umin;
+                const T distpos = dist(Q(q));
+                for (unsigned int pos = q, par = 0; pos && distpos<dist(Q(par=(pos+1)/2-1)); pos=par) cimg::swap(Q(pos),Q(par));
+              }
+            }
+          }
+          // Remove minimal vertex from queue
+          Q(0) = Q(--sizeQ);
+          const T distpos = dist(Q(0));
+          for (unsigned int pos = 0, left = 0, right = 0;
+               ((right=2*(pos+1),(left=right-1))<sizeQ && distpos>dist(Q(left))) || (right<sizeQ && distpos>dist(Q(right)));) {
+            if (right<sizeQ) {
+              if (dist(Q(left))<dist(Q(right))) { cimg::swap(Q(pos),Q(left)); pos = left; }
+              else { cimg::swap(Q(pos),Q(right)); pos = right; }
+            } else { cimg::swap(Q(pos),Q(left)); pos = left; }
+          }
+        }
+      }
+      return dist;
+    }
+
+    //! Return minimal path in a graph, using the Dijkstra algorithm.
+    template<typename tf, typename t>
+    static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
+                            const unsigned int starting_node, const unsigned int ending_node=~0U) {
+      CImg<uintT> foo;
+      return dijkstra(distance,nb_nodes,starting_node,ending_node,foo);
+    }
+
+    //! Return minimal path in a graph, using the Dijkstra algorithm.
+    /**
+       Instance image corresponds to the adjacency matrix of the graph.
+       \param starting_node Indice of the starting node.
+       \param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
+       \return Array of distances of each node to the starting node.
+    **/
+    template<typename t>
+    CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) {
+      return get_dijkstra(starting_node,ending_node,previous).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<T> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) const {
+      if (_width!=_height || _depth!=1 || _spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "dijkstra() : Instance is not a graph adjacency matrix.",
+                                    cimg_instance);
+
+      return dijkstra(*this,_width,starting_node,ending_node,previous);
+    }
+
+    //! Return minimal path in a graph, using the Dijkstra algorithm.
+    CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) {
+      return get_dijkstra(starting_node,ending_node).move_to(*this);
+    }
+
+    CImg<Tfloat> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const {
+      CImg<uintT> foo;
+      return get_dijkstra(starting_node,ending_node,foo);
+    }
+
+    //! Return stream line of a 2d or 3d vector field.
+    CImg<floatT> get_streamline(const float x, const float y, const float z,
+                                const float L=256, const float dl=0.1f,
+                                const unsigned int interpolation_type=2, const bool is_backward_tracking=false,
+                                const bool is_oriented_only=false) const {
+      if (_spectrum!=2 && _spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "streamline() : Instance is not a 2d or 3d vector field.",
+                                    cimg_instance);
+      if (_spectrum==2) {
+        if (is_oriented_only) {
+          typename CImg<T>::_functor4d_streamline2d_oriented func(*this);
+          return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true,0,0,0,_width-1.0f,_height-1.0f,0.0f);
+        } else {
+          typename CImg<T>::_functor4d_streamline2d_directed func(*this);
+          return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,false,0,0,0,_width-1.0f,_height-1.0f,0.0f);
+        }
+      }
+      if (is_oriented_only) {
+        typename CImg<T>::_functor4d_streamline3d_oriented func(*this);
+        return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,true,0,0,0,_width-1.0f,_height-1.0f,_depth-1.0f);
+      }
+      typename CImg<T>::_functor4d_streamline3d_directed func(*this);
+      return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,false,0,0,0,_width-1.0f,_height-1.0f,_depth-1.0f);
+    }
+
+    //! Return stream line of a 3d vector field.
+    /**
+       \param interpolation_type Type of interpolation (can be 0=nearest int, 1=linear, 2=2nd-order RK, 3=4th-order RK.
+
+     **/
+    template<typename tfunc>
+    static CImg<floatT> streamline(const tfunc& func,
+                                   const float x, const float y, const float z,
+                                   const float L=256, const float dl=0.1f,
+                                   const unsigned int interpolation_type=2, const bool is_backward_tracking=false,
+                                   const bool is_oriented_only=false,
+                                   const float x0=0, const float y0=0, const float z0=0,
+                                   const float x1=0, const float y1=0, const float z1=0) {
+      if (dl<=0)
+        throw CImgArgumentException("CImg<%s>::streamline() : Invalid specified integration length %g "
+                                    "(should be >0).",
+                                    pixel_type(),
+                                    dl);
+
+      const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1);
+      if (L<=0 || (is_bounded && (x<x0 || x>x1 || y<y0 || y>y1 || z<z0 || z>z1))) return CImg<floatT>();
+      const unsigned int size_L = (unsigned int)cimg::round(L/dl+1,1);
+      CImg<floatT> coordinates(size_L,3);
+      const float dl2 = dl/2;
+      float
+        *ptr_x = coordinates.data(0,0),
+        *ptr_y = coordinates.data(0,1),
+        *ptr_z = coordinates.data(0,2),
+        pu = (float)(dl*func(x,y,z,0)),
+        pv = (float)(dl*func(x,y,z,1)),
+        pw = (float)(dl*func(x,y,z,2)),
+        X = x, Y = y, Z = z;
+
+      switch (interpolation_type) {
+      case 0 : { // Nearest integer interpolation.
+        cimg_forX(coordinates,l) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          const int
+            xi = (int)(X>0?X+0.5f:X-0.5f),
+            yi = (int)(Y>0?Y+0.5f:Y-0.5f),
+            zi = (int)(Z>0?Z+0.5f:Z-0.5f);
+          float
+            u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)),
+            v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)),
+            w = (float)(dl*func((float)xi,(float)yi,(float)zi,2));
+          if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      } break;
+      case 1 : { // First-order interpolation.
+        cimg_forX(coordinates,l) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          float
+            u = (float)(dl*func(X,Y,Z,0)),
+            v = (float)(dl*func(X,Y,Z,1)),
+            w = (float)(dl*func(X,Y,Z,2));
+          if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      } break;
+      case 2 : { // Second order interpolation.
+        cimg_forX(coordinates,l) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          float
+            u0 = (float)(dl2*func(X,Y,Z,0)),
+            v0 = (float)(dl2*func(X,Y,Z,1)),
+            w0 = (float)(dl2*func(X,Y,Z,2));
+          if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; }
+          float
+            u = (float)(dl*func(X+u0,Y+v0,Z+w0,0)),
+            v = (float)(dl*func(X+u0,Y+v0,Z+w0,1)),
+            w = (float)(dl*func(X+u0,Y+v0,Z+w0,2));
+          if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      } break;
+      default : { // Fourth order interpolation.
+        cimg_forX(coordinates,x) {
+          *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+          float
+            u0 = (float)(dl2*func(X,Y,Z,0)),
+            v0 = (float)(dl2*func(X,Y,Z,1)),
+            w0 = (float)(dl2*func(X,Y,Z,2));
+          if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; }
+          float
+            u1 = (float)(dl2*func(X+u0,Y+v0,Z+w0,0)),
+            v1 = (float)(dl2*func(X+u0,Y+v0,Z+w0,1)),
+            w1 = (float)(dl2*func(X+u0,Y+v0,Z+w0,2));
+          if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; }
+          float
+            u2 = (float)(dl2*func(X+u1,Y+v1,Z+w1,0)),
+            v2 = (float)(dl2*func(X+u1,Y+v1,Z+w1,1)),
+            w2 = (float)(dl2*func(X+u1,Y+v1,Z+w1,2));
+          if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; }
+          float
+            u3 = (float)(dl2*func(X+u2,Y+v2,Z+w2,0)),
+            v3 = (float)(dl2*func(X+u2,Y+v2,Z+w2,1)),
+            w3 = (float)(dl2*func(X+u2,Y+v2,Z+w2,2));
+          if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; }
+          const float
+            u = (u0 + u3)/3 + (u1 + u2)/1.5f,
+            v = (v0 + v3)/3 + (v1 + v2)/1.5f,
+            w = (w0 + w3)/3 + (w1 + w2)/1.5f;
+          if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+          if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+        }
+      }
+      }
+      if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0);
+      return coordinates;
+    }
+
+    //! Return stream line of a vector field.
+    static CImg<floatT> streamline(const char *const expression,
+                                   const float x, const float y, const float z,
+                                   const float L=256, const float dl=0.1f,
+                                   const unsigned int interpolation_type=2, const bool is_backward_tracking=true,
+                                   const bool is_oriented_only=false,
+                                   const float x0=0, const float y0=0, const float z0=0,
+                                   const float x1=0, const float y1=0, const float z1=0) {
+      _functor4d_streamline_expr func(expression);
+      return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1);
+    }
+
+    struct _functor4d_streamline2d_directed {
+      const CImg<T>& ref;
+      _functor4d_streamline2d_directed(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0;
+      }
+    };
+
+    struct _functor4d_streamline3d_directed {
+      const CImg<T>& ref;
+      _functor4d_streamline3d_directed(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return (float)ref._linear_atXYZ(x,y,z,c);
+      }
+    };
+
+    struct _functor4d_streamline2d_oriented {
+      const CImg<T>& ref;
+      CImg<floatT> *pI;
+      _functor4d_streamline2d_oriented(const CImg<T>& pref):ref(pref),pI(0) { pI = new CImg<floatT>(2,2,1,2); }
+      ~_functor4d_streamline2d_oriented() { if (pI) delete pI; }
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+#define _cimg_vecalign2d(i,j) if (I(i,j,0)*I(0,0,0)+I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); }
+        int
+          xi = (int)x - (x>=0?0:1), nxi = xi + 1,
+          yi = (int)y - (y>=0?0:1), nyi = yi + 1,
+          zi = (int)z;
+        const float
+          dx = x - xi,
+          dy = y - yi;
+        if (c==0) {
+          CImg<floatT>& I = *pI;
+          if (xi<0) xi = 0; if (nxi<0) nxi = 0;
+          if (xi>=ref.width()) xi = ref.width()-1; if (nxi>=ref.width()) nxi = ref.width()-1;
+          if (yi<0) yi = 0; if (nyi<0) nyi = 0;
+          if (yi>=ref.height()) yi = ref.height()-1; if (nyi>=ref.height()) nyi = ref.height()-1;
+          I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1);
+          I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1);
+          I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1);
+          I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1);
+          _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1);
+        }
+        return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0;
+      }
+    };
+
+    struct _functor4d_streamline3d_oriented {
+      const CImg<T>& ref;
+      CImg<floatT> *pI;
+      _functor4d_streamline3d_oriented(const CImg<T>& pref):ref(pref),pI(0) { pI = new CImg<floatT>(2,2,2,3); }
+      ~_functor4d_streamline3d_oriented() { if (pI) delete pI; }
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0)+I(i,j,k,1)*I(0,0,0,1)+I(i,j,k,2)*I(0,0,0,2)<0) { \
+  I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); }
+        int
+          xi = (int)x - (x>=0?0:1), nxi = xi + 1,
+          yi = (int)y - (y>=0?0:1), nyi = yi + 1,
+          zi = (int)z - (z>=0?0:1), nzi = zi + 1;
+        const float
+          dx = x - xi,
+          dy = y - yi,
+          dz = z - zi;
+        if (c==0) {
+          CImg<floatT>& I = *pI;
+          if (xi<0) xi = 0; if (nxi<0) nxi = 0;
+          if (xi>=ref.width()) xi = ref.width()-1; if (nxi>=ref.width()) nxi = ref.width()-1;
+          if (yi<0) yi = 0; if (nyi<0) nyi = 0;
+          if (yi>=ref.height()) yi = ref.height()-1; if (nyi>=ref.height()) nyi = ref.height()-1;
+          if (zi<0) zi = 0; if (nzi<0) nzi = 0;
+          if (zi>=ref.depth()) zi = ref.depth()-1; if (nzi>=ref.depth()) nzi = ref.depth()-1;
+          I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1); I(0,0,0,2) = (float)ref(xi,yi,zi,2);
+          I(1,0,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2);
+          I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1); I(1,1,0,2) = (float)ref(nxi,nyi,zi,2);
+          I(0,1,0,0) = (float)ref(xi,nyi,zi,0); I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,yi,zi,2);
+          I(0,0,0,1) = (float)ref(xi,yi,nzi,0); I(0,0,0,1) = (float)ref(xi,yi,nzi,1); I(0,0,0,2) = (float)ref(xi,yi,nzi,2);
+          I(1,0,0,1) = (float)ref(nxi,yi,nzi,0); I(1,0,0,1) = (float)ref(nxi,yi,nzi,1); I(1,0,0,2) = (float)ref(nxi,yi,nzi,2);
+          I(1,1,0,1) = (float)ref(nxi,nyi,nzi,0); I(1,1,0,1) = (float)ref(nxi,nyi,nzi,1); I(1,1,0,2) = (float)ref(nxi,nyi,nzi,2);
+          I(0,1,0,1) = (float)ref(xi,nyi,nzi,0); I(0,1,0,1) = (float)ref(xi,nyi,nzi,1); I(0,1,0,2) = (float)ref(xi,yi,nzi,2);
+          _cimg_vecalign3d(1,0,0); _cimg_vecalign3d(1,1,0); _cimg_vecalign3d(0,1,0);
+          _cimg_vecalign3d(0,0,1); _cimg_vecalign3d(1,0,1); _cimg_vecalign3d(1,1,1); _cimg_vecalign3d(0,1,1);
+        }
+        return (float)pI->_linear_atXYZ(dx,dy,dz,c);
+      }
+    };
+
+    struct _functor4d_streamline_expr {
+      _cimg_math_parser *mp;
+      ~_functor4d_streamline_expr() { if (mp) delete mp; }
+      _functor4d_streamline_expr(const char *const expr):mp(0) { mp = new _cimg_math_parser(CImg<T>::empty(),expr,"streamline"); }
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return (float)mp->eval(x,y,z,c);
+      }
+    };
+
+    //! Return an image containing the specified string.
+    static CImg<T> string(const char *const str) {
+      if (!str) return CImg<T>();
+      return CImg<T>(str,std::strlen(str)+1);
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0) {
+      static CImg<T> r(1,1); r[0] = a0;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1) {
+      static CImg<T> r(1,2); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2) {
+      static CImg<T> r(1,3); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3) {
+      static CImg<T> r(1,4); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
+      static CImg<T> r(1,5); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) {
+      static CImg<T> r(1,6); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6) {
+      static CImg<T> r(1,7); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7) {
+      static CImg<T> r(1,8); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8) {
+      static CImg<T> r(1,9); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9) {
+      static CImg<T> r(1,10); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9, const T& a10) {
+      static CImg<T> r(1,11); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9, const T& a10, const T& a11) {
+      static CImg<T> r(1,12); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9, const T& a10, const T& a11,
+                          const T& a12) {
+      static CImg<T> r(1,13); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+      *(ptr++) = a12;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9, const T& a10, const T& a11,
+                          const T& a12, const T& a13) {
+      static CImg<T> r(1,14); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+      *(ptr++) = a12; *(ptr++) = a13;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9, const T& a10, const T& a11,
+                          const T& a12, const T& a13, const T& a14) {
+      static CImg<T> r(1,15); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+      *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
+      return r;
+    }
+
+    //! Return a vector with specified coefficients.
+    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9, const T& a10, const T& a11,
+                          const T& a12, const T& a13, const T& a14, const T& a15) {
+      static CImg<T> r(1,16); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+      *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
+      return r;
+    }
+
+    //! Return a 1x1 square matrix with specified coefficients.
+    static CImg<T> matrix(const T& a0) {
+      return vector(a0);
+    }
+
+    //! Return a 2x2 square matrix with specified coefficients.
+    static CImg<T> matrix(const T& a0, const T& a1,
+                          const T& a2, const T& a3) {
+      static CImg<T> r(2,2); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1;
+      *(ptr++) = a2; *(ptr++) = a3;
+      return r;
+    }
+
+    //! Return a 3x3 square matrix with specified coefficients.
+    static CImg<T> matrix(const T& a0, const T& a1, const T& a2,
+                          const T& a3, const T& a4, const T& a5,
+                          const T& a6, const T& a7, const T& a8) {
+      static CImg<T> r(3,3); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
+      *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
+      *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8;
+      return r;
+    }
+
+    //! Return a 4x4 square matrix with specified coefficients.
+    static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3,
+                          const T& a4, const T& a5, const T& a6, const T& a7,
+                          const T& a8, const T& a9, const T& a10, const T& a11,
+                          const T& a12, const T& a13, const T& a14, const T& a15) {
+      static CImg<T> r(4,4); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+      *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
+      *(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
+      *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
+      return r;
+    }
+
+    //! Return a 5x5 square matrix with specified coefficients.
+    static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4,
+                          const T& a5, const T& a6, const T& a7, const T& a8, const T& a9,
+                          const T& a10, const T& a11, const T& a12, const T& a13, const T& a14,
+                          const T& a15, const T& a16, const T& a17, const T& a18, const T& a19,
+                          const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) {
+      static CImg<T> r(5,5); T *ptr = r._data;
+      *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
+      *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9;
+      *(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
+      *(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19;
+      *(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24;
+      return r;
+    }
+
+    //! Return a 1x1 symmetric matrix with specified coefficients.
+    static CImg<T> tensor(const T& a1) {
+      return matrix(a1);
+    }
+
+    //! Return a 2x2 symmetric matrix tensor with specified coefficients.
+    static CImg<T> tensor(const T& a1, const T& a2, const T& a3) {
+      return matrix(a1,a2,a2,a3);
+    }
+
+    //! Return a 3x3 symmetric matrix with specified coefficients.
+    static CImg<T> tensor(const T& a1, const T& a2, const T& a3, const T& a4, const T& a5, const T& a6) {
+      return matrix(a1,a2,a3,a2,a4,a5,a3,a5,a6);
+    }
+
+    //! Return a 1x1 diagonal matrix with specified coefficients.
+    static CImg<T> diagonal(const T& a0) {
+      return matrix(a0);
+    }
+
+    //! Return a 2x2 diagonal matrix with specified coefficients.
+    static CImg<T> diagonal(const T& a0, const T& a1) {
+      return matrix(a0,0,0,a1);
+    }
+
+    //! Return a 3x3 diagonal matrix with specified coefficients.
+    static CImg<T> diagonal(const T& a0, const T& a1, const T& a2) {
+      return matrix(a0,0,0,0,a1,0,0,0,a2);
+    }
+
+    //! Return a 4x4 diagonal matrix with specified coefficients.
+    static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3) {
+      return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3);
+    }
+
+    //! Return a 5x5 diagonal matrix with specified coefficients.
+    static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
+      return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4);
+    }
+
+    //! Return a NxN identity matrix.
+    static CImg<T> identity_matrix(const unsigned int N) {
+      CImg<T> res(N,N,1,1,0);
+      cimg_forX(res,x) res(x,x) = 1;
+      return res;
+    }
+
+    //! Return a N-numbered sequence vector from \p a0 to \p a1.
+    static CImg<T> sequence(const unsigned int N, const T a0, const T a1) {
+      if (N) return CImg<T>(1,N).sequence(a0,a1);
+      return CImg<T>();
+    }
+
+    //! Return a 3x3 rotation matrix along the (x,y,z)-axis with an angle w.
+    static CImg<T> rotation_matrix(const float x, const float y, const float z, const float w, const bool quaternion_data=false) {
+      float X,Y,Z,W;
+      if (!quaternion_data) {
+        const float norm = (float)std::sqrt(x*x + y*y + z*z),
+          nx = norm>0?x/norm:0,
+          ny = norm>0?y/norm:0,
+          nz = norm>0?z/norm:1,
+          nw = norm>0?w:0,
+          sina = (float)std::sin(nw/2),
+          cosa = (float)std::cos(nw/2);
+        X = nx*sina;
+        Y = ny*sina;
+        Z = nz*sina;
+        W = cosa;
+      } else {
+        const float norm = (float)std::sqrt(x*x + y*y + z*z + w*w);
+        if (norm>0) { X = x/norm; Y = y/norm; Z = z/norm; W = w/norm; }
+        else { X = Y = Z = 0; W = 1; }
+      }
+      const float xx = X*X, xy = X*Y, xz = X*Z, xw = X*W, yy = Y*Y, yz = Y*Z, yw = Y*W, zz = Z*Z, zw = Z*W;
+      return CImg<T>::matrix((T)(1-2*(yy+zz)), (T)(2*(xy+zw)),   (T)(2*(xz-yw)),
+                             (T)(2*(xy-zw)),   (T)(1-2*(xx+zz)), (T)(2*(yz+xw)),
+                             (T)(2*(xz+yw)),   (T)(2*(yz-xw)),   (T)(1-2*(xx+yy)));
+    }
+
+    //@}
+    //-----------------------------------
+    //
+    //! \name Value Manipulation
+    //@{
+    //-----------------------------------
+
+    //! Fill an image by a value \p val.
+    /**
+       \param val = fill value
+       \note All pixel values of the instance image will be initialized by \p val.
+    **/
+    CImg<T>& fill(const T val) {
+      if (is_empty()) return *this;
+      if (val && sizeof(T)!=1) cimg_for(*this,ptrd,T) *ptrd = val;
+      else std::memset(_data,(int)val,size()*sizeof(T));
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val);
+    }
+
+    //! Fill sequentially all pixel values with values \a val0 and \a val1 respectively.
+    CImg<T>& fill(const T val0, const T val1) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-1;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; }
+      if (ptrd!=ptre+1) *(ptrd++) = val0;
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1);
+    }
+
+    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2.
+    CImg<T>& fill(const T val0, const T val1, const T val2) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-2;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; }
+      ptre+=2;
+      switch (ptre - ptrd) {
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2);
+    }
+
+    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-3;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; }
+      ptre+=3;
+      switch (ptre - ptrd) {
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3);
+    }
+
+    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-4;
+      for (ptrd = _data; ptrd<ptre; ) { *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; }
+      ptre+=4;
+      switch (ptre - ptrd) {
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4);
+    }
+
+    //! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4 and \a val5.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-5;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+      }
+      ptre+=5;
+      switch (ptre - ptrd) {
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-6;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5; *(ptrd++) = val6;
+      }
+      ptre+=6;
+      switch (ptre - ptrd) {
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-7;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3;
+        *(ptrd++) = val4; *(ptrd++) = val5; *(ptrd++) = val6; *(ptrd++) = val7;
+      }
+      ptre+=7;
+      switch (ptre - ptrd) {
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-8;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2;
+        *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8;
+      }
+      ptre+=8;
+      switch (ptre - ptrd) {
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8, const T val9) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-9;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4;
+        *(ptrd++) = val5; *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9;
+      }
+      ptre+=9;
+      switch (ptre - ptrd) {
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8, const T val9) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8, const T val9, const T val10) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-10;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4;
+        *(ptrd++) = val5; *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9;
+        *(ptrd++) = val10;
+      }
+      ptre+=10;
+      switch (ptre - ptrd) {
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8, const T val9, const T val10) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8, const T val9, const T val10, const T val11) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-11;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+      }
+      ptre+=11;
+      switch (ptre - ptrd) {
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8, const T val9, const T val10, const T val11) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-12;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12;
+      }
+      ptre+=12;
+      switch (ptre - ptrd) {
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+                  const T val13) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-13;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12; *(ptrd++) = val13;
+      }
+      ptre+=13;
+      switch (ptre - ptrd) {
+      case 13 : *(--ptre) = val12;
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+                     const T val13) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
+                                                  val13);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+                  const T val13, const T val14) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-14;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12; *(ptrd++) = val13; *(ptrd++) = val14;
+      }
+      ptre+=14;
+      switch (ptre - ptrd) {
+      case 14 : *(--ptre) = val13;
+      case 13 : *(--ptre) = val12;
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+                     const T val13, const T val14) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
+                                                  val13,val14);
+    }
+
+    //! Fill sequentially pixel values.
+    CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                  const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+                  const T val13, const T val14, const T val15) {
+      if (is_empty()) return *this;
+      T *ptrd, *ptre = end()-15;
+      for (ptrd = _data; ptrd<ptre; ) {
+        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
+        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
+        *(ptrd++) = val12; *(ptrd++) = val13; *(ptrd++) = val14; *(ptrd++) = val15;
+      }
+      ptre+=15;
+      switch (ptre - ptrd) {
+      case 15 : *(--ptre) = val14;
+      case 14 : *(--ptre) = val13;
+      case 13 : *(--ptre) = val12;
+      case 12 : *(--ptre) = val11;
+      case 11 : *(--ptre) = val10;
+      case 10 : *(--ptre) = val9;
+      case 9 : *(--ptre) = val8;
+      case 8 : *(--ptre) = val7;
+      case 7 : *(--ptre) = val6;
+      case 6 : *(--ptre) = val5;
+      case 5 : *(--ptre) = val4;
+      case 4 : *(--ptre) = val3;
+      case 3 : *(--ptre) = val2;
+      case 2 : *(--ptre) = val1;
+      case 1 : *(--ptre) = val0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
+                     const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
+                     const T val13, const T val14, const T val15) const {
+      return CImg<T>(_width,_height,_depth,_spectrum).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
+                                                  val13,val14,val15);
+    }
+
+    //! Fill image values according to the given expression, which can be a formula or a list of values.
+    CImg<T>& fill(const char *const expression, const bool repeat_flag) {
+      if (is_empty() || !expression || !*expression) return *this;
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try { // Try to fill values according to a formula.
+        const CImg<T> _base = std::strstr(expression,"i(")?+*this:CImg<T>(), &base = _base?_base:*this;
+        _cimg_math_parser mp(base,expression,"fill");
+        T *ptrd = _data;
+        cimg_forXYZC(*this,x,y,z,c) *(ptrd++) = (T)mp.eval((double)x,(double)y,(double)z,(double)c);
+      } catch (CImgException& e) { // If failed, try to recognize a list of values.
+        char item[16384] = { 0 }, sep = 0;
+        const char *nexpression = expression;
+        unsigned int nb = 0; const unsigned int siz = size();
+        T *ptrd = _data;
+        for (double val = 0; *nexpression && nb<siz; ++nb) {
+          sep = 0;
+          const int err = std::sscanf(nexpression,"%4095[ \n\t0-9.e+-]%c",item,&sep);
+          if (err>0 && std::sscanf(item,"%lf",&val)==1) {
+            nexpression+=std::strlen(item) + (err>1?1:0);
+            *(ptrd++) = (T)val;
+          } else break;
+        }
+        cimg::exception_mode() = omode;
+        if (nb<siz && (sep || *nexpression))
+          throw CImgArgumentException(e.what(),pixel_type(),expression);
+        if (repeat_flag && nb && nb<siz) for (T *ptrs = _data, *const ptre = _data + siz; ptrd<ptre; ++ptrs) *(ptrd++) = *ptrs;
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    CImg<T> get_fill(const char *const values, const bool repeat_values) const {
+      return (+*this).fill(values,repeat_values);
+    }
+
+    //! Fill image values according to the values found in the specified image.
+    template<typename t>
+    CImg<T>& fill(const CImg<t>& values, const bool repeat_values=true) {
+      if (is_empty() || !values) return *this;
+      T *ptrd = _data, *ptre = ptrd + size();
+      for (t *ptrs = values._data, *ptrs_end = ptrs + values.size(); ptrs<ptrs_end && ptrd<ptre; ++ptrs) *(ptrd++) = (T)*ptrs;
+      if (repeat_values && ptrd<ptre) for (T *ptrs = _data; ptrd<ptre; ++ptrs) *(ptrd++) = *ptrs;
+      return *this;
+    }
+
+    template<typename t>
+    CImg<T> get_fill(const CImg<t>& values, const bool repeat_values=true) const {
+      return repeat_values?CImg<T>(_width,_height,_depth,_spectrum).fill(values,repeat_values):(+*this).fill(values,repeat_values);
+    }
+
+    //! Fill image values along the X-axis at the specified pixel position (y,z,c).
+    CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const int a0, ...) {
+#define _cimg_fill1(x,y,z,c,off,siz,t) { \
+    va_list ap; va_start(ap,a0); T *ptrd = data(x,y,z,c); *ptrd = (T)a0; \
+    for (unsigned int k = 1; k<siz; ++k) { ptrd+=off; *ptrd = (T)va_arg(ap,t); } \
+    va_end(ap); }
+      if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,int);
+      return *this;
+    }
+
+    CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int c, const double a0, ...) {
+      if (y<_height && z<_depth && c<_spectrum) _cimg_fill1(0,y,z,c,1,_width,double);
+      return *this;
+    }
+
+    //! Fill image values along the Y-axis at the specified pixel position (x,z,c).
+    CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const int a0, ...) {
+      if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,int);
+      return *this;
+    }
+
+    CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) {
+      if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double);
+      return *this;
+    }
+
+    //! Fill image values along the Z-axis at the specified pixel position (x,y,c).
+    CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) {
+      const unsigned int wh = _width*_height;
+      if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int);
+      return *this;
+    }
+
+    CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) {
+      const unsigned int wh = _width*_height;
+      if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double);
+      return *this;
+    }
+
+    //! Fill image values along the C-axis at the specified pixel position (x,y,z).
+    CImg<T>& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {
+      const unsigned int whd = _width*_height*_depth;
+      if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int);
+      return *this;
+    }
+
+    CImg<T>& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) {
+      const unsigned int whd = _width*_height*_depth;
+      if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double);
+      return *this;
+    }
+
+    //! Invert endianness of the image buffer.
+    CImg<T>& invert_endianness() {
+      cimg::invert_endianness(_data,size());
+      return *this;
+    }
+
+    CImg<T> get_invert_endianness() const {
+      return (+*this).invert_endianness();
+    }
+
+    //! Fill the instance image with random values between specified range.
+    CImg<T>& rand(const T val_min, const T val_max) {
+      const float delta = (float)val_max - (float)val_min;
+      cimg_for(*this,ptrd,T) *ptrd = (T)(val_min + cimg::rand()*delta);
+      return *this;
+    }
+
+    CImg<T> get_rand(const T val_min, const T val_max) const {
+      return (+*this).rand(val_min,val_max);
+    }
+
+    //! Compute image with rounded pixel values.
+    /**
+       \param x Rounding precision.
+       \param rounding_type Roundin type, can be 0 (nearest), 1 (forward), -1(backward).
+    **/
+    CImg<T>& round(const float x, const int rounding_type=0) {
+      if (x>0) cimg_for(*this,ptrd,T) *ptrd = (T)cimg::round(*ptrd,x,rounding_type);
+      return *this;
+    }
+
+    CImg<T> get_round(const float x, const unsigned int rounding_type=0) const {
+      return (+*this).round(x,rounding_type);
+    }
+
+    //! Add random noise to the values of the instance image.
+    /**
+       \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the global value range.
+       \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper, \p 3=Poisson or \p 4=Rician).
+       \return A reference to the modified instance image.
+       \note
+       - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on the image value itself.
+       - Function \p CImg<T>::get_noise() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_noise(40);
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_noise.jpg
+    **/
+    CImg<T>& noise(const double sigma, const unsigned int noise_type=0) {
+      if (!is_empty()) {
+        double nsigma = sigma, max = (double)cimg::type<T>::max(), min = (double)cimg::type<T>::min();
+        Tfloat m = 0, M = 0;
+        if (nsigma==0 && noise_type!=3) return *this;
+        if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M);
+        if (nsigma<0) nsigma = -nsigma*(M-m)/100.0;
+        switch (noise_type) {
+        case 0 : { // Gaussian noise
+          cimg_for(*this,ptrd,T) {
+            double val = *ptrd + nsigma*cimg::grand();
+            if (val>max) val = max;
+            if (val<min) val = min;
+            *ptrd = (T)val;
+          }
+        } break;
+        case 1 : { // Uniform noise
+          cimg_for(*this,ptrd,T) {
+            double val = *ptrd + nsigma*cimg::crand();
+            if (val>max) val = max;
+            if (val<min) val = min;
+            *ptrd = (T)val;
+          }
+        } break;
+        case 2 : { // Salt & Pepper noise
+          if (nsigma<0) nsigma = -nsigma;
+          if (M==m) { m = 0; M = (float)(cimg::type<T>::is_float()?1:cimg::type<T>::max()); }
+          cimg_for(*this,ptrd,T) if (cimg::rand()*100<nsigma) *ptrd = (T)(cimg::rand()<0.5?M:m);
+        } break;
+
+        case 3 : { // Poisson Noise
+          cimg_for(*this,ptrd,T) *ptrd = (T)cimg::prand(*ptrd);
+        } break;
+
+        case 4 : { // Rice noise
+          const double sqrt2 = (double)std::sqrt(2.0);
+          cimg_for(*this,ptrd,T) {
+            const double
+              val0 = (double)*ptrd/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;
+            *ptrd = (T)val;
+          }
+        } break;
+        default :
+          throw CImgArgumentException(_cimg_instance
+                                      "noise() : Invalid specified noise type %d "
+                                      "(should be { 0=gaussian | 1=uniform | 2=salt&Pepper | 3=poisson }).",
+                                      cimg_instance,
+                                      noise_type);
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_noise(const double sigma, const unsigned int noise_type=0) const {
+      return (+*this).noise(sigma,noise_type);
+    }
+
+    //! Linearly normalize values of the instance image between \p value_min and \p value_max.
+    /**
+       \param value_min Minimum desired value of the resulting image.
+       \param value_max Maximum desired value of the resulting image.
+       \return A reference to the modified instance image.
+       \note
+       - Function \p CImg<T>::get_normalize() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_normalize(160,220);
+       (img,res).display();
+       \endcode
+       \image html ref_normalize2.jpg
+    **/
+    CImg<T>& normalize(const T value_min, const T value_max) {
+      if (is_empty()) return *this;
+      const T a = value_min<value_max?value_min:value_max, b = value_min<value_max?value_max:value_min;
+      T m, M = max_min(m);
+      const Tfloat fm = (Tfloat)m, fM = (Tfloat)M;
+      if (m==M) return fill(0);
+      if (m!=a || M!=b) cimg_for(*this,ptrd,T) *ptrd = (T)((*ptrd-fm)/(fM-fm)*(b-a)+a);
+      return *this;
+    }
+
+    CImg<Tfloat> get_normalize(const T value_min, const T value_max) const {
+      return CImg<Tfloat>(*this,false).normalize((Tfloat)value_min,(Tfloat)value_max);
+    }
+
+    //! Normalize multi-valued pixels of the instance image, with respect to their L2-norm.
+    /**
+       \return A reference to the modified instance image.
+       \note
+       - Function \p CImg<T>::get_normalize() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_normalize();
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_normalize.jpg
+    **/
+    CImg<T>& normalize() {
+      T *ptrd = _data;
+      const unsigned int whd = _width*_height*_depth;
+      cimg_forXYZ(*this,x,y,z) {
+        const T *ptrs = ptrd;
+        float n = 0;
+        cimg_forC(*this,c) { n+=cimg::sqr((float)*ptrs); ptrs+=whd; }
+        n = (float)std::sqrt(n);
+        T *_ptrd = ptrd++;
+        if (n>0) cimg_forC(*this,c) { *_ptrd = (T)(*_ptrd/n); _ptrd+=whd; }
+        else cimg_forC(*this,c) { *_ptrd = (T)0; _ptrd+=whd; }
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_normalize() const {
+      return CImg<Tfloat>(*this,false).normalize();
+    }
+
+    //! Compute L2-norm of each multi-valued pixel of the instance image.
+    /**
+       \param norm_type Type of computed vector norm (can be \p 0=Linf, \p 1=L1 or \p 2=L2).
+       \return A reference to the modified instance image.
+       \note
+       - Function \p CImg<T>::get_norm() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_norm();
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_norm.jpg
+    **/
+    CImg<T>& norm(const int norm_type=2) {
+      if (_spectrum==1) return abs();
+      return get_norm(norm_type).move_to(*this);
+    }
+
+    CImg<Tfloat> get_norm(const int norm_type=2) const {
+      if (is_empty()) return *this;
+      if (_spectrum==1) return get_abs();
+      const T *ptrs = _data;
+      const unsigned int whd = _width*_height*_depth;
+      CImg<Tfloat> res(_width,_height,_depth);
+      Tfloat *ptrd = res._data;
+      switch (norm_type) {
+      case -1 : {             // Linf norm
+        cimg_forXYZ(*this,x,y,z) {
+          Tfloat n = 0;
+          const T *_ptrs = ptrs++;
+          cimg_forC(*this,c) { const Tfloat val = (Tfloat)cimg::abs(*_ptrs); if (val>n) n = val; _ptrs+=whd; }
+          *(ptrd++) = n;
+        }
+      } break;
+      case 1 : {              // L1 norm
+        cimg_forXYZ(*this,x,y,z) {
+          Tfloat n = 0;
+          const T *_ptrs = ptrs++;
+          cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; }
+          *(ptrd++) = n;
+        }
+      } break;
+      default : {             // L2 norm
+        cimg_forXYZ(*this,x,y,z) {
+          Tfloat n = 0;
+          const T *_ptrs = ptrs++;
+          cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; }
+          *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n);
+        }
+      }
+      }
+      return res;
+    }
+
+    //! Cut values of the instance image between \p value_min and \p value_max.
+    /**
+       \param value_min Minimum desired value of the resulting image.
+       \param value_max Maximum desired value of the resulting image.
+       \return A reference to the modified instance image.
+       \note
+       - Function \p CImg<T>::get_cut() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_cut(160,220);
+       (img,res).display();
+       \endcode
+       \image html ref_cut.jpg
+    **/
+    CImg<T>& cut(const T value_min, const T value_max) {
+      if (is_empty()) return *this;
+      const T a = value_min<value_max?value_min:value_max, b = value_min<value_max?value_max:value_min;
+      cimg_for(*this,ptrd,T) *ptrd = (*ptrd<a)?a:((*ptrd>b)?b:*ptrd);
+      return *this;
+    }
+
+    CImg<T> get_cut(const T value_min, const T value_max) const {
+      return (+*this).cut(value_min,value_max);
+    }
+
+    //! Uniformly quantize values of the instance image into \p nb_levels levels.
+    /**
+       \param nb_levels Number of quantization levels.
+       \param keep_range Tells if resulting values keep the same range as the original ones.
+       \return A reference to the modified instance image.
+       \note
+       - Function \p CImg<T>::get_quantize() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_quantize(4);
+       (img,res).display();
+       \endcode
+       \image html ref_quantize.jpg
+    **/
+    CImg<T>& quantize(const unsigned int nb_levels, const bool keep_range=true) {
+      if (!nb_levels)
+        throw CImgArgumentException(_cimg_instance
+                                    "quantize() : Invalid quantization request with 0 values.",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      Tfloat m, M = (Tfloat)max_min(m), range = M - m;
+      if (range>0) {
+        if (keep_range) cimg_for(*this,ptrd,T) {
+          const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range);
+          *ptrd = (T)(m + cimg::min(val,nb_levels-1)*range/nb_levels);
+        } else cimg_for(*this,ptrd,T) {
+          const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range);
+          *ptrd = (T)cimg::min(val,nb_levels-1);
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_quantize(const unsigned int n, const bool keep_range=true) const {
+      return (+*this).quantize(n,keep_range);
+    }
+
+    //! Threshold values of the instance image.
+    /**
+       \param value Threshold value
+       \param soft_threshold Tells if soft thresholding must be applied (instead of hard one).
+       \param strict_threshold Tells if threshold value is strict.
+       \return A reference to the modified instance image. Resulting pixel values are either equal to 0 or 1.
+       \note
+       - Function \p CImg<T>::get_threshold() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_threshold(128);
+       (img,res.normalize(0,255)).display();
+       \endcode
+       \image html ref_threshold.jpg
+    **/
+    CImg<T>& threshold(const T value, const bool soft_threshold=false, const bool strict_threshold=false) {
+      if (is_empty()) return *this;
+      if (strict_threshold) {
+        if (soft_threshold) cimg_for(*this,ptrd,T) { const T v = *ptrd; *ptrd = v>value?(T)(v-value):v<-(float)value?(T)(v+value):(T)0; }
+        else cimg_for(*this,ptrd,T) *ptrd = *ptrd>value?(T)1:(T)0;
+      } else {
+        if (soft_threshold) cimg_for(*this,ptrd,T) { const T v = *ptrd; *ptrd = v>=value?(T)(v-value):v<=-(float)value?(T)(v+value):(T)0; }
+        else cimg_for(*this,ptrd,T) *ptrd = *ptrd>=value?(T)1:(T)0;
+      }
+      return *this;
+    }
+
+    CImg<T> get_threshold(const T value, const bool soft_threshold=false, const bool strict_threshold=false) const {
+      return (+*this).threshold(value,soft_threshold,strict_threshold);
+    }
+
+    //! Compute the histogram of the instance image.
+    /**
+       \param nb_levels Number of desired histogram levels.
+       \param value_min Minimum pixel value considered for the histogram computation. All pixel values lower than \p value_min will not be counted.
+       \param value_max Maximum pixel value considered for the histogram computation. All pixel values higher than \p value_max will not be counted.
+       \return Instance image is replaced by its histogram, defined as a \p CImg<T>(nb_levels) image.
+       \note
+       - The histogram H of an image I is the 1d function where H(x) counts the number of occurences of the value x in the image I.
+       - If \p value_min==value_max==0 (default behavior), the function first estimates the whole range of pixel values
+       then uses it to compute the histogram.
+       - The resulting histogram is always defined in 1d. Histograms of multi-valued images are not multi-dimensional.
+       - Function \p CImg<T>::get_histogram() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img = CImg<float>("reference.jpg").histogram(256);
+       img.display_graph(0,3);
+       \endcode
+       \image html ref_histogram.jpg
+    **/
+    CImg<T>& histogram(const unsigned int nb_levels, const T value_min=(T)0, const T value_max=(T)0) {
+      return get_histogram(nb_levels,value_min,value_max).move_to(*this);
+    }
+
+    CImg<floatT> get_histogram(const unsigned int nb_levels, const T value_min=(T)0, const T value_max=(T)0) const {
+      if (!nb_levels)
+        throw CImgArgumentException(_cimg_instance
+                                    "histogram() : Invalid histogram request with 0 levels.",
+                                    cimg_instance);
+
+      if (is_empty()) return CImg<floatT>();
+      T vmin = value_min, vmax = value_max;
+      CImg<floatT> res(nb_levels,1,1,1,0);
+      if (vmin>=vmax && vmin==0) vmin = min_max(vmax);
+      if (vmin<vmax) cimg_for(*this,ptrs,T) {
+        const T val = *ptrs;
+        if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels-1:(int)((val-vmin)*nb_levels/(vmax-vmin))];
+      } else res[0]+=size();
+      return res;
+    }
+
+    //! Compute the histogram-equalized version of the instance image.
+    /**
+       \param nb_levels Number of histogram levels used for the equalization.
+       \param value_min Minimum pixel value considered for the histogram computation. All pixel values lower than \p value_min will not be counted.
+       \param value_max Maximum pixel value considered for the histogram computation. All pixel values higher than \p value_max will not be counted.
+       \return A reference to the modified instance image.
+       \note
+       - If \p value_min==value_max==0 (default behavior), the function first estimates the whole range of pixel values
+       then uses it to equalize the histogram.
+       - Function \p CImg<T>::get_equalize() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), res = img.get_equalize(256);
+       (img,res).display();
+       \endcode
+       \image html ref_equalize.jpg
+    **/
+    CImg<T>& equalize(const unsigned int nb_levels, const T value_min=(T)0, const T value_max=(T)0) {
+      if (is_empty()) return *this;
+      T vmin = value_min, vmax = value_max;
+      if (vmin==vmax && vmin==0) vmin = min_max(vmax);
+      if (vmin<vmax) {
+        CImg<floatT> hist = get_histogram(nb_levels,vmin,vmax);
+        float cumul = 0;
+        cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; }
+        cimg_for(*this,ptrd,T) {
+          const int pos = (unsigned int)((*ptrd-vmin)*(nb_levels-1)/(vmax-vmin));
+          if (pos>=0 && pos<(int)nb_levels) *ptrd = (T)(vmin + (vmax-vmin)*hist[pos]/size());
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const {
+      return (+*this).equalize(nblevels,val_min,val_max);
+    }
+
+    //! Index multi-valued pixels of the instance image, regarding to a predefined palette.
+    /**
+       \param palette Multi-valued palette used as the basis for multi-valued pixel indexing.
+       \param dithering Tells if Floyd-Steinberg dithering is activated or not.
+       \param map_indexes Tell if the values of the resulting image are the palette indices or the palette vectors.
+       \return A reference to the modified instance image.
+       \note
+       - \p img.index(palette,dithering,1) is equivalent to <tt>img.index(palette,dithering,0).map(palette)</tt>.
+       - Function \p CImg<T>::get_index() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"), palette(3,1,1,3, 0,128,255, 0,128,255, 0,128,255);
+       const CImg<float> res = img.get_index(palette,true,true);
+       (img,res).display();
+       \endcode
+       \image html ref_index.jpg
+    **/
+    template<typename t>
+    CImg<T>& index(const CImg<t>& palette, const bool dithering=false, const bool map_indexes=false) {
+      return get_index(palette,dithering,map_indexes).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<typename CImg<t>::Tuint>
+    get_index(const CImg<t>& palette, const bool dithering=false, const bool map_indexes=true) const {
+      if (palette._spectrum!=_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "index() : Instance and specified palette (%u,%u,%u,%u,%p) "
+                                    "have incompatible dimensions.",
+                                    cimg_instance,
+                                    palette._width,palette._height,palette._depth,palette._spectrum,palette._data);
+
+      typedef typename CImg<t>::Tuint tuint;
+      if (is_empty()) return CImg<tuint>();
+      const unsigned int whd = _width*_height*_depth, pwhd = palette._width*palette._height*palette._depth;
+      CImg<tuint> res(_width,_height,_depth,map_indexes?_spectrum:1);
+      tuint *ptrd = res._data;
+      if (dithering) { // Dithered versions.
+        Tfloat valm = 0, valM = (Tfloat)max_min(valm);
+        if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; }
+        CImg<Tfloat> cache = get_crop(-1,0,0,0,_width,1,0,_spectrum-1);
+        Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0);
+        const unsigned int cwhd = cache._width*cache._height*cache._depth;
+        switch (_spectrum) {
+        case 1 : { // Optimized for scalars.
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc0 = cache_next; const T *ptrs0 = data(0,y+1,z,0);
+              cimg_forX(*this,x) *(ptrc0++) = (Tfloat)*(ptrs0++);
+            }
+            Tfloat *ptrs0 = cache_current, *ptrsn0 = cache_next;
+            cimg_forX(*this,x) {
+              const Tfloat _val0 = (Tfloat)*ptrs0, val0 = _val0<valm?valm:_val0>valM?valM:_val0;
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+              for (const t *ptrp0 = palette._data, *ptrp_end = ptrp0 + pwhd; ptrp0<ptrp_end; ) {
+                const Tfloat pval0 = (Tfloat)*(ptrp0++) - val0, dist = pval0*pval0;
+                if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+              }
+              const Tfloat err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)/16;
+              *ptrs0+=7*err0; *(ptrsn0-1)+=3*err0; *(ptrsn0++)+=5*err0; *ptrsn0+=err0;
+              if (map_indexes) *(ptrd++) = (tuint)*ptrmin0; else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        } break;
+        case 2 : { // Optimized for 2d vectors.
+          tuint *ptrd1 = ptrd + whd;
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc0 = cache_next, *ptrc1 = ptrc0 + cwhd;
+              const T *ptrs0 = data(0,y+1,z,0), *ptrs1 = ptrs0 + whd;
+              cimg_forX(*this,x) { *(ptrc0++) = (Tfloat)*(ptrs0++); *(ptrc1++) = (Tfloat)*(ptrs1++); }
+            }
+            Tfloat
+              *ptrs0 = cache_current, *ptrs1 = ptrs0 + cwhd,
+              *ptrsn0 = cache_next, *ptrsn1 = ptrsn0 + cwhd;
+            cimg_forX(*this,x) {
+              const Tfloat
+                _val0 = (Tfloat)*ptrs0, val0 = _val0<valm?valm:_val0>valM?valM:_val0,
+                _val1 = (Tfloat)*ptrs1, val1 = _val1<valm?valm:_val1>valM?valM:_val1;
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+              for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+                const Tfloat
+                  pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1,
+                  dist = pval0*pval0 + pval1*pval1;
+                if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+              }
+              const t *const ptrmin1 = ptrmin0 + pwhd;
+              const Tfloat
+                err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)/16,
+                err1 = ((*(ptrs1++)=val1) - (Tfloat)*ptrmin1)/16;
+              *ptrs0+=7*err0; *ptrs1+=7*err1;
+              *(ptrsn0-1)+=3*err0; *(ptrsn1-1)+=3*err1;
+              *(ptrsn0++)+=5*err0; *(ptrsn1++)+=5*err1;
+              *ptrsn0+=err0; *ptrsn1+=err1;
+              if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*ptrmin1; }
+              else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        } break;
+        case 3 : { // Optimized for 3d vectors (colors).
+          tuint *ptrd1 = ptrd + whd, *ptrd2 = ptrd1 + whd;
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc0 = cache_next, *ptrc1 = ptrc0 + cwhd, *ptrc2 = ptrc1 + cwhd;
+              const T *ptrs0 = data(0,y+1,z,0), *ptrs1 = ptrs0 + whd, *ptrs2 = ptrs1 + whd;
+              cimg_forX(*this,x) { *(ptrc0++) = (Tfloat)*(ptrs0++); *(ptrc1++) = (Tfloat)*(ptrs1++); *(ptrc2++) = (Tfloat)*(ptrs2++); }
+            }
+            Tfloat
+              *ptrs0 = cache_current, *ptrs1 = ptrs0 + cwhd, *ptrs2 = ptrs1 + cwhd,
+              *ptrsn0 = cache_next, *ptrsn1 = ptrsn0 + cwhd, *ptrsn2 = ptrsn1 + cwhd;
+            cimg_forX(*this,x) {
+              const Tfloat
+                _val0 = (Tfloat)*ptrs0, val0 = _val0<valm?valm:_val0>valM?valM:_val0,
+                _val1 = (Tfloat)*ptrs1, val1 = _val1<valm?valm:_val1>valM?valM:_val1,
+                _val2 = (Tfloat)*ptrs2, val2 = _val2<valm?valm:_val2>valM?valM:_val2;
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+              for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+                const Tfloat
+                  pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1, pval2 = (Tfloat)*(ptrp2++) - val2,
+                  dist = pval0*pval0 + pval1*pval1 + pval2*pval2;
+                if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+              }
+              const t *const ptrmin1 = ptrmin0 + pwhd, *const ptrmin2 = ptrmin1 + pwhd;
+              const Tfloat
+                err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)/16,
+                err1 = ((*(ptrs1++)=val1) - (Tfloat)*ptrmin1)/16,
+                err2 = ((*(ptrs2++)=val2) - (Tfloat)*ptrmin2)/16;
+              *ptrs0+=7*err0; *ptrs1+=7*err1; *ptrs2+=7*err2;
+              *(ptrsn0-1)+=3*err0; *(ptrsn1-1)+=3*err1; *(ptrsn2-1)+=3*err2;
+              *(ptrsn0++)+=5*err0; *(ptrsn1++)+=5*err1; *(ptrsn2++)+=5*err2;
+              *ptrsn0+=err0; *ptrsn1+=err1; *ptrsn2+=err2;
+              if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*ptrmin1; *(ptrd2++) = (tuint)*ptrmin2; }
+              else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        } break;
+        default : // Generic version
+          cimg_forYZ(*this,y,z) {
+            if (y<height()-2) {
+              Tfloat *ptrc = cache_next;
+              cimg_forC(*this,c) {
+                Tfloat *_ptrc = ptrc; const T *_ptrs = data(0,y+1,z,c);
+                cimg_forX(*this,x) *(_ptrc++) = (Tfloat)*(_ptrs++);
+                ptrc+=cwhd;
+              }
+            }
+            Tfloat *ptrs = cache_current, *ptrsn = cache_next;
+            cimg_forX(*this,x) {
+              Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin = palette._data;
+              for (const t *ptrp = palette._data, *ptrp_end = ptrp + pwhd; ptrp<ptrp_end; ++ptrp) {
+                Tfloat dist = 0; Tfloat *_ptrs = ptrs; const t *_ptrp = ptrp;
+                cimg_forC(*this,c) {
+                  const Tfloat _val = *_ptrs, val = _val<valm?valm:_val>valM?valM:_val;
+                  dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd;
+                }
+                if (dist<distmin) { ptrmin = ptrp; distmin = dist; }
+              }
+              const t *_ptrmin = ptrmin; Tfloat *_ptrs = ptrs++, *_ptrsn = (ptrsn++)-1;
+              cimg_forC(*this,c) {
+                const Tfloat err = (*(_ptrs++) - (Tfloat)*_ptrmin)/16;
+                *_ptrs+=7*err; *(_ptrsn++)+=3*err; *(_ptrsn++)+=5*err; *_ptrsn+=err;
+                _ptrmin+=pwhd; _ptrs+=cwhd-1; _ptrsn+=cwhd-2;
+              }
+              if (map_indexes) {
+                tuint *_ptrd = ptrd++;
+                cimg_forC(*this,c) { *_ptrd = (tuint)*ptrmin; _ptrd+=whd; ptrmin+=pwhd; }
+              }
+              else *(ptrd++) = (tuint)(ptrmin - palette._data);
+            }
+            cimg::swap(cache_current,cache_next);
+          }
+        }
+      } else { // Non-dithered versions
+        switch (_spectrum) {
+        case 1 : { // Optimized for scalars.
+          for (const T *ptrs0 = _data, *ptrs_end = ptrs0 + whd; ptrs0<ptrs_end; ) {
+            const Tfloat val0 = (Tfloat)*(ptrs0++);
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+            for (const t *ptrp0 = palette._data, *ptrp_end = ptrp0 + pwhd; ptrp0<ptrp_end; ) {
+              const Tfloat pval0 = (Tfloat)*(ptrp0++) - val0, dist = pval0*pval0;
+              if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+            }
+            if (map_indexes) *(ptrd++) = (tuint)*ptrmin0; else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+          }
+        } break;
+        case 2 : { // Optimized for 2d vectors.
+          tuint *ptrd1 = ptrd + whd;
+          for (const T *ptrs0 = _data, *ptrs1 = ptrs0 + whd, *ptrs_end = ptrs1; ptrs0<ptrs_end; ) {
+            const Tfloat val0 = (Tfloat)*(ptrs0++), val1 = (Tfloat)*(ptrs1++);
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+            for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+              const Tfloat
+                pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1,
+                dist = pval0*pval0 + pval1*pval1;
+              if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+            }
+            if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*(ptrmin0 + pwhd); }
+            else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+          }
+        } break;
+        case 3 : { // Optimized for 3d vectors (colors).
+          tuint *ptrd1 = ptrd + whd, *ptrd2 = ptrd1 + whd;
+          for (const T *ptrs0 = _data, *ptrs1 = ptrs0 + whd, *ptrs2 = ptrs1 + whd, *ptrs_end = ptrs1; ptrs0<ptrs_end; ) {
+            const Tfloat val0 = (Tfloat)*(ptrs0++), val1 = (Tfloat)*(ptrs1++), val2 = (Tfloat)*(ptrs2++);
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = palette._data;
+            for (const t *ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+              const Tfloat
+                pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1, pval2 = (Tfloat)*(ptrp2++) - val2,
+                dist = pval0*pval0 + pval1*pval1 + pval2*pval2;
+              if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+            }
+            if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*(ptrmin0 + pwhd); *(ptrd2++) = (tuint)*(ptrmin0 + 2*pwhd); }
+            else *(ptrd++) = (tuint)(ptrmin0 - palette._data);
+          }
+        } break;
+        default : // Generic version.
+          for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ++ptrs) {
+            Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin = palette._data;
+            for (const t *ptrp = palette._data, *ptrp_end = ptrp + pwhd; ptrp<ptrp_end; ++ptrp) {
+              Tfloat dist = 0; const T *_ptrs = ptrs; const t *_ptrp = ptrp;
+              cimg_forC(*this,c) { dist+=cimg::sqr((Tfloat)*_ptrs - (Tfloat)*_ptrp); _ptrs+=whd; _ptrp+=pwhd; }
+              if (dist<distmin) { ptrmin = ptrp; distmin = dist; }
+            }
+            if (map_indexes) {
+              tuint *_ptrd = ptrd++;
+              cimg_forC(*this,c) { *_ptrd = (tuint)*ptrmin; _ptrd+=whd; ptrmin+=pwhd; }
+            }
+            else *(ptrd++) = (tuint)(ptrmin - palette._data);
+          }
+        }
+      }
+      return res;
+    }
+
+    //! Map predefined palette on the scalar (indexed) instance image.
+    /**
+       \param palette Multi-valued palette used for mapping the indexes.
+       \return A reference to the modified instance image.
+       \note
+       - Function \p CImg<T>::get_map() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg"),
+                         palette1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255),
+                         palette2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255),
+                         res = img.get_index(palette1,false).map(palette2);
+       (img,res).display();
+       \endcode
+       \image html ref_map.jpg
+    **/
+    template<typename t>
+    CImg<T>& map(const CImg<t>& palette) {
+      return get_map(palette).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<t> get_map(const CImg<t>& palette) const {
+      if (_spectrum!=1 && palette._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "map() : Instance and specified palette (%u,%u,%u,%u,%p) "
+                                    "have incompatible dimensions.",
+                                    cimg_instance,
+                                    palette._width,palette._height,palette._depth,palette._spectrum,palette._data);
+
+      const unsigned int whd = _width*_height*_depth, pwhd = palette._width*palette._height*palette._depth;
+      CImg<t> res(_width,_height,_depth,palette._spectrum==1?_spectrum:palette._spectrum);
+      switch (palette._spectrum) {
+      case 1 : { // Optimized for scalars.
+        const T *ptrs = _data + whd*_spectrum;
+        cimg_for(res,ptrd,t) {
+          const unsigned int _ind = (unsigned int)*(--ptrs), ind = _ind<pwhd?_ind:0;
+          *ptrd = palette[ind];
+        }
+      } break;
+      case 2 : { // Optimized for 2d vectors.
+        const t *const ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd;
+        t *ptrd0 = res._data, *ptrd1 = ptrd0 + whd;
+        for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+          const unsigned int _ind = (unsigned int)*(ptrs++), ind = _ind<pwhd?_ind:0;
+          *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind];
+        }
+      } break;
+      case 3 : { // Optimized for 3d vectors (colors).
+        const t *const ptrp0 = palette._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd;
+        t *ptrd0 = res._data, *ptrd1 = ptrd0 + whd, *ptrd2 = ptrd1 + whd;
+        for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+          const unsigned int _ind = (unsigned int)*(ptrs++), ind = _ind<pwhd?_ind:0;
+          *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind]; *(ptrd2++) = ptrp2[ind];
+        }
+      } break;
+      default : { // Generic version.
+        t *ptrd = res._data;
+        for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+          const unsigned int _ind = (unsigned int)*(ptrs++), ind = _ind<pwhd?_ind:0;
+          const t *ptrp = palette._data + ind;
+          t *_ptrd = ptrd++; cimg_forC(res,c) { *_ptrd = *ptrp; _ptrd+=whd; ptrp+=pwhd; }
+        }
+      }
+      }
+      return res;
+    }
+
+    //! Create a map of indexed labels counting disconnected regions with same intensities.
+    /**
+       \return A reference to the modified instance image.
+       \note
+       - Function \p CImg<T>::get_label_regions() is also defined. It returns a non-shared modified copy of the instance image.
+       \par Sample code :
+       \code
+       const CImg<float> img = CImg<float>("reference.jpg").norm().quantize(4),
+                         palette = CImg<float>::default_LUT256(),
+                         res = img.get_label_regions().normalize(0,255).map(palette);
+       (img,res).display();
+       \endcode
+       \image html ref_label_regions.jpg
+    **/
+    CImg<T>& label_regions() {
+      return get_label_regions().move_to(*this);
+    }
+
+    CImg<uintT> get_label_regions() const {
+#define _cimg_get_label_test(p,q) { \
+  flag = true; \
+  const T *ptr1 = data(x,y) + siz, *ptr2 = data(p,q) + siz; \
+  for (unsigned int i = _spectrum; flag && i; --i) { ptr1-=wh; ptr2-=wh; flag = (*ptr1==*ptr2); } \
+}
+      if (_depth>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "label_regions() : Instance is not a 2d image.",
+                                    cimg_instance);
+
+      CImg<uintT> res(_width,_height,_depth,1,0);
+      unsigned int label = 1;
+      const unsigned int wh = _width*_height, siz = _width*_height*_spectrum;
+      const int W1 = width()-1, H1 = height()-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.data(x,y);
+                for (unsigned int *ptr = res._data; 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.data(x,y);
+                for (unsigned int *ptr = res._data+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,ptrd,unsigned int) if (*ptrd==lab) *ptrd = label; ++label; }
+      }
+      return (res-=lab0);
+    }
+
+    //@}
+    //---------------------------------
+    //
+    //! \name Color Base Management
+    //@{
+    //---------------------------------
+
+    //! Return a default indexed color palette with 256 (R,G,B) entries.
+    /**
+       \return An instance of a default color palette with 256 (R,G,B) colors.
+       \note This color palette is particularly 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).
+       \code
+       CImg<float>::default_LUT256().display();
+       \endcode
+       \image html ref_default_LUT256.jpg
+    **/
+    static CImg<Tuchar> default_LUT256() {
+      static CImg<Tuchar> palette;
+      if (!palette) {
+        palette.assign(1,256,1,3);
+        for (unsigned int index = 0, r = 16; r<256; r+=32)
+          for (unsigned int g = 16; g<256; g+=32)
+            for (unsigned int b = 32; b<256; b+=64) {
+              palette(0,index,0) = (Tuchar)r;
+              palette(0,index,1) = (Tuchar)g;
+              palette(0,index++,2) = (Tuchar)b;
+            }
+      }
+      return palette;
+    }
+
+    //! Return a rainbow indexed color palette with 256 (R,G,B) entries.
+    /**
+       \return An instance of a rainbow color palette with 256 (R,G,B) colors.
+       \code
+       CImg<float>::rainbow_LUT256().display();
+       \endcode
+       \image html ref_rainbow_LUT256.jpg
+    **/
+    static CImg<Tuchar> rainbow_LUT256() {
+      static CImg<Tuchar> palette;
+      if (!palette) {
+        CImg<Tint> tmp(1,256,1,3,1);
+        tmp.get_shared_channel(0).sequence(0,359);
+        palette = tmp.HSVtoRGB();
+      }
+      return palette;
+    }
+
+    //! Return a contrasted indexed color palette with 256 (R,G,B) entries.
+    /**
+       \return An instance of a contrasted color palette with 256 (R,G,B) colors.
+       \code
+       CImg<float>::contrast_LUT256().display();
+       \endcode
+       \image html ref_contrast_LUT256.jpg
+    **/
+    static CImg<Tuchar> contrast_LUT256() {
+      static const unsigned char pal[] = {
+        217,62,88,75,1,237,240,12,56,160,165,116,1,1,204,2,15,248,148,185,133,141,46,246,222,116,16,5,207,226,
+        17,114,247,1,214,53,238,0,95,55,233,235,109,0,17,54,33,0,90,30,3,0,94,27,19,0,68,212,166,130,0,15,7,119,
+        238,2,246,198,0,3,16,10,13,2,25,28,12,6,2,99,18,141,30,4,3,140,12,4,30,233,7,10,0,136,35,160,168,184,20,
+        233,0,1,242,83,90,56,180,44,41,0,6,19,207,5,31,214,4,35,153,180,75,21,76,16,202,218,22,17,2,136,71,74,
+        81,251,244,148,222,17,0,234,24,0,200,16,239,15,225,102,230,186,58,230,110,12,0,7,129,249,22,241,37,219,
+        1,3,254,210,3,212,113,131,197,162,123,252,90,96,209,60,0,17,0,180,249,12,112,165,43,27,229,77,40,195,12,
+        87,1,210,148,47,80,5,9,1,137,2,40,57,205,244,40,8,252,98,0,40,43,206,31,187,0,180,1,69,70,227,131,108,0,
+        223,94,228,35,248,243,4,16,0,34,24,2,9,35,73,91,12,199,51,1,249,12,103,131,20,224,2,70,32,
+        233,1,165,3,8,154,246,233,196,5,0,6,183,227,247,195,208,36,0,0,226,160,210,198,69,153,210,1,23,8,192,2,4,
+        137,1,0,52,2,249,241,129,0,0,234,7,238,71,7,32,15,157,157,252,158,2,250,6,13,30,11,162,0,199,21,11,27,224,
+        4,157,20,181,111,187,218,3,0,11,158,230,196,34,223,22,248,135,254,210,157,219,0,117,239,3,255,4,227,5,247,
+        11,4,3,188,111,11,105,195,2,0,14,1,21,219,192,0,183,191,113,241,1,12,17,248,0,48,7,19,1,254,212,0,239,246,
+        0,23,0,250,165,194,194,17,3,253,0,24,6,0,141,167,221,24,212,2,235,243,0,0,205,1,251,133,204,28,4,6,1,10,
+        141,21,74,12,236,254,228,19,1,0,214,1,186,13,13,6,13,16,27,209,6,216,11,207,251,59,32,9,155,23,19,235,143,
+        116,6,213,6,75,159,23,6,0,228,4,10,245,249,1,7,44,234,4,102,174,0,19,239,103,16,15,18,8,214,22,4,47,244,
+        255,8,0,251,173,1,212,252,250,251,252,6,0,29,29,222,233,246,5,149,0,182,180,13,151,0,203,183,0,35,149,0,
+        235,246,254,78,9,17,203,73,11,195,0,3,5,44,0,0,237,5,106,6,130,16,214,20,168,247,168,4,207,11,5,1,232,251,
+        129,210,116,231,217,223,214,27,45,38,4,177,186,249,7,215,172,16,214,27,249,230,236,2,34,216,217,0,175,30,
+        243,225,244,182,20,212,2,226,21,255,20,0,2,13,62,13,191,14,76,64,20,121,4,118,0,216,1,147,0,2,210,1,215,
+        95,210,236,225,184,46,0,248,24,11,1,9,141,250,243,9,221,233,160,11,147,2,55,8,23,12,253,9,0,54,0,231,6,3,
+        141,8,2,246,9,180,5,11,8,227,8,43,110,242,1,130,5,97,36,10,6,219,86,133,11,108,6,1,5,244,67,19,28,0,174,
+        154,16,127,149,252,188,196,196,228,244,9,249,0,0,0,37,170,32,250,0,73,255,23,3,224,234,38,195,198,0,255,87,
+        33,221,174,31,3,0,189,228,6,153,14,144,14,108,197,0,9,206,245,254,3,16,253,178,248,0,95,125,8,0,3,168,21,
+        23,168,19,50,240,244,185,0,1,144,10,168,31,82,1,13 };
+      static const CImg<Tuchar> palette(pal,1,256,1,3,false);
+      return palette;
+    }
+
+    //! Convert color pixels from (R,G,B) to (H,S,V).
+    CImg<T>& RGBtoHSV() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoHSV() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1,
+          G = (Tfloat)*p2,
+          B = (Tfloat)*p3,
+          nR = (R<0?0:(R>255?255:R))/255,
+          nG = (G<0?0:(G>255?255:G))/255,
+          nB = (B<0?0:(B>255?255:B))/255,
+          m = cimg::min(nR,nG,nB),
+          M = cimg::max(nR,nG,nB);
+        Tfloat H = 0, S = 0;
+        if (M!=m) {
+          const Tfloat
+            f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
+            i = (Tfloat)((nR==m)?3:((nG==m)?5:1));
+          H = (i-f/(M-m));
+          if (H>=6) H-=6;
+          H*=60;
+          S = (M-m)/M;
+        }
+        *(p1++) = (T)H;
+        *(p2++) = (T)S;
+        *(p3++) = (T)M;
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_RGBtoHSV() const {
+      return CImg<Tfloat>(*this,false).RGBtoHSV();
+    }
+
+    //! Convert color pixels from (H,S,V) to (R,G,B).
+    CImg<T>& HSVtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "HSVtoRGB() : Instance is not a HSV image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        Tfloat
+          H = (Tfloat)*p1,
+          S = (Tfloat)*p2,
+          V = (Tfloat)*p3,
+          R = 0, G = 0, B = 0;
+        if (H==0 && S==0) R = G = B = V;
+        else {
+          H/=60;
+          const int i = (int)std::floor(H);
+          const Tfloat
+            f = (i&1)?(H - i):(1 - H + i),
+            m = V*(1 - S),
+            n = V*(1 - S*f);
+          switch (i) {
+          case 6 :
+          case 0 : R = V; G = n; B = m; break;
+          case 1 : R = n; G = V; B = m; break;
+          case 2 : R = m; G = V; B = n; break;
+          case 3 : R = m; G = n; B = V; break;
+          case 4 : R = n; G = m; B = V; break;
+          case 5 : R = V; G = m; B = n; break;
+          }
+        }
+        R*=255; G*=255; B*=255;
+        *(p1++) = (T)(R<0?0:(R>255?255:R));
+        *(p2++) = (T)(G<0?0:(G>255?255:G));
+        *(p3++) = (T)(B<0?0:(B>255?255:B));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_HSVtoRGB() const {
+      return CImg<Tuchar>(*this,false).HSVtoRGB();
+    }
+
+    //! Convert color pixels from (R,G,B) to (H,S,L).
+    CImg<T>& RGBtoHSL() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoHSL() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1,
+          G = (Tfloat)*p2,
+          B = (Tfloat)*p3,
+          nR = (R<0?0:(R>255?255:R))/255,
+          nG = (G<0?0:(G>255?255:G))/255,
+          nB = (B<0?0:(B>255?255:B))/255,
+          m = cimg::min(nR,nG,nB),
+          M = cimg::max(nR,nG,nB),
+          L = (m + M)/2;
+        Tfloat H = 0, S = 0;
+        if (M==m) H = S = 0;
+        else {
+          const Tfloat
+            f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
+            i = (nR==m)?3.0f:((nG==m)?5.0f:1.0f);
+          H = (i-f/(M-m));
+          if (H>=6) H-=6;
+          H*=60;
+          S = (2*L<=1)?((M-m)/(M+m)):((M-m)/(2-M-m));
+        }
+        *(p1++) = (T)H;
+        *(p2++) = (T)S;
+        *(p3++) = (T)L;
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_RGBtoHSL() const {
+      return CImg< Tfloat>(*this,false).RGBtoHSL();
+    }
+
+    //! Convert color pixels from (H,S,L) to (R,G,B).
+    CImg<T>& HSLtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "HSLtoRGB() : Instance is not a HSL image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          H = (Tfloat)*p1,
+          S = (Tfloat)*p2,
+          L = (Tfloat)*p3,
+          q = 2*L<1?L*(1+S):(L+S-L*S),
+          p = 2*L-q,
+          h = H/360,
+          tr = h + 1.0f/3,
+          tg = h,
+          tb = h - 1.0f/3,
+          ntr = tr<0?tr+1:(tr>1?tr-1:tr),
+          ntg = tg<0?tg+1:(tg>1?tg-1:tg),
+          ntb = tb<0?tb+1:(tb>1?tb-1:tb),
+          R = 255*(6*ntr<1?p+(q-p)*6*ntr:(2*ntr<1?q:(3*ntr<2?p+(q-p)*6*(2.0f/3-ntr):p))),
+          G = 255*(6*ntg<1?p+(q-p)*6*ntg:(2*ntg<1?q:(3*ntg<2?p+(q-p)*6*(2.0f/3-ntg):p))),
+          B = 255*(6*ntb<1?p+(q-p)*6*ntb:(2*ntb<1?q:(3*ntb<2?p+(q-p)*6*(2.0f/3-ntb):p)));
+        *(p1++) = (T)(R<0?0:(R>255?255:R));
+        *(p2++) = (T)(G<0?0:(G>255?255:G));
+        *(p3++) = (T)(B<0?0:(B>255?255:B));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_HSLtoRGB() const {
+      return CImg<Tuchar>(*this,false).HSLtoRGB();
+    }
+
+    //! Convert color pixels from (R,G,B) to (H,S,I).
+    //! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002.
+    CImg<T>& RGBtoHSI() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoHSI() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1,
+          G = (Tfloat)*p2,
+          B = (Tfloat)*p3,
+          nR = (R<0?0:(R>255?255:R))/255,
+          nG = (G<0?0:(G>255?255:G))/255,
+          nB = (B<0?0:(B>255?255:B))/255,
+          m = cimg::min(nR,nG,nB),
+          theta = (Tfloat)(std::acos(0.5f*((nR-nG)+(nR-nB))/std::sqrt(std::pow(nR-nG,2)+(nR-nB)*(nG-nB)))*180/cimg::PI),
+          sum = nR + nG + nB;
+        Tfloat H = 0, S = 0, I = 0;
+        if (theta>0) H = (nB<=nG)?theta:360-theta;
+        if (sum>0) S = 1 - 3/sum*m;
+        I = sum/3;
+        *(p1++) = (T)H;
+        *(p2++) = (T)S;
+        *(p3++) = (T)I;
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_RGBtoHSI() const {
+      return CImg<Tfloat>(*this,false).RGBtoHSI();
+    }
+
+    //! Convert color pixels from (H,S,I) to (R,G,B).
+    CImg<T>& HSItoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "HSItoRGB() : Instance is not a HSI image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        Tfloat
+          H = (Tfloat)*p1,
+          S = (Tfloat)*p2,
+          I = (Tfloat)*p3,
+          a = I*(1-S),
+          R = 0, G = 0, B = 0;
+        if (H<120) {
+          B = a;
+          R = (Tfloat)(I*(1+S*std::cos(H*cimg::PI/180)/std::cos((60-H)*cimg::PI/180)));
+          G = 3*I-(R+B);
+        } else if (H<240) {
+          H-=120;
+          R = a;
+          G = (Tfloat)(I*(1+S*std::cos(H*cimg::PI/180)/std::cos((60-H)*cimg::PI/180)));
+          B = 3*I-(R+G);
+        } else {
+          H-=240;
+          G = a;
+          B = (Tfloat)(I*(1+S*std::cos(H*cimg::PI/180)/std::cos((60-H)*cimg::PI/180)));
+          R = 3*I-(G+B);
+        }
+        R*=255; G*=255; B*=255;
+        *(p1++) = (T)(R<0?0:(R>255?255:R));
+        *(p2++) = (T)(G<0?0:(G>255?255:G));
+        *(p3++) = (T)(B<0?0:(B>255?255:B));
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_HSItoRGB() const {
+      return CImg< Tuchar>(*this,false).HSItoRGB();
+    }
+
+    //! Convert color pixels from (R,G,B) to (Y,Cb,Cr).
+    CImg<T>& RGBtoYCbCr() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoYCbCr() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1,
+          G = (Tfloat)*p2,
+          B = (Tfloat)*p3,
+          Y = (66*R + 129*G + 25*B + 128)/256 + 16,
+          Cb = (-38*R - 74*G + 112*B + 128)/256 + 128,
+          Cr = (112*R - 94*G - 18*B + 128)/256 + 128;
+        *(p1++) = (T)(Y<0?0:(Y>255?255:Y));
+        *(p2++) = (T)(Cb<0?0:(Cb>255?255:Cb));
+        *(p3++) = (T)(Cr<0?0:(Cr>255?255:Cr));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_RGBtoYCbCr() const {
+      return CImg<Tuchar>(*this,false).RGBtoYCbCr();
+    }
+
+    //! Convert color pixels from (R,G,B) to (Y,Cb,Cr).
+    CImg<T>& YCbCrtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "YCbCrtoRGB() : Instance is not a YCbCr image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          Y = (Tfloat)*p1 - 16,
+          Cb = (Tfloat)*p2 - 128,
+          Cr = (Tfloat)*p3 - 128,
+          R = (298*Y + 409*Cr + 128)/256,
+          G = (298*Y - 100*Cb - 208*Cr + 128)/256,
+          B = (298*Y + 516*Cb + 128)/256;
+        *(p1++) = (T)(R<0?0:(R>255?255:R));
+        *(p2++) = (T)(G<0?0:(G>255?255:G));
+        *(p3++) = (T)(B<0?0:(B>255?255:B));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_YCbCrtoRGB() const {
+      return CImg<Tuchar>(*this,false).YCbCrtoRGB();
+    }
+
+    //! Convert color pixels from (R,G,B) to (Y,U,V).
+    CImg<T>& RGBtoYUV() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoYUV() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1/255,
+          G = (Tfloat)*p2/255,
+          B = (Tfloat)*p3/255,
+          Y = 0.299f*R + 0.587f*G + 0.114f*B;
+        *(p1++) = (T)Y;
+        *(p2++) = (T)(0.492f*(B-Y));
+        *(p3++) = (T)(0.877*(R-Y));
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_RGBtoYUV() const {
+      return CImg<Tfloat>(*this,false).RGBtoYUV();
+    }
+
+    //! Convert color pixels from (Y,U,V) to (R,G,B).
+    CImg<T>& YUVtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "YUVtoRGB() : Instance is not a YUV image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          Y = (Tfloat)*p1,
+          U = (Tfloat)*p2,
+          V = (Tfloat)*p3,
+          R = (Y + 1.140f*V)*255,
+          G = (Y - 0.395f*U - 0.581f*V)*255,
+          B = (Y + 2.032f*U)*255;
+        *(p1++) = (T)(R<0?0:(R>255?255:R));
+        *(p2++) = (T)(G<0?0:(G>255?255:G));
+        *(p3++) = (T)(B<0?0:(B>255?255:B));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_YUVtoRGB() const {
+      return CImg< Tuchar>(*this,false).YUVtoRGB();
+    }
+
+    //! Convert color pixels from (R,G,B) to (C,M,Y).
+    CImg<T>& RGBtoCMY() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoCMY() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1,
+          G = (Tfloat)*p2,
+          B = (Tfloat)*p3,
+          C = 255 - R,
+          M = 255 - G,
+          Y = 255 - B;
+        *(p1++) = (T)(C<0?0:(C>255?255:C));
+        *(p2++) = (T)(M<0?0:(M>255?255:M));
+        *(p3++) = (T)(Y<0?0:(Y>255?255:Y));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_RGBtoCMY() const {
+      return CImg<Tfloat>(*this,false).RGBtoCMY();
+    }
+
+    //! Convert (C,M,Y) pixels of a color image into the (R,G,B) color space.
+    CImg<T>& CMYtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "CMYtoRGB() : Instance is not a CMY image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          C = (Tfloat)*p1,
+          M = (Tfloat)*p2,
+          Y = (Tfloat)*p3,
+          R = 255 - C,
+          G = 255 - M,
+          B = 255 - Y;
+        *(p1++) = (T)(R<0?0:(R>255?255:R));
+        *(p2++) = (T)(G<0?0:(G>255?255:G));
+        *(p3++) = (T)(B<0?0:(B>255?255:B));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_CMYtoRGB() const {
+      return CImg<Tuchar>(*this,false).CMYtoRGB();
+    }
+
+    //! Convert color pixels from (C,M,Y) to (C,M,Y,K).
+    CImg<T>& CMYtoCMYK() {
+      return get_CMYtoCMYK().move_to(*this);
+    }
+
+    CImg<Tuchar> get_CMYtoCMYK() const {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "CMYtoCMYK() : Instance is not a CMY image.",
+                                    cimg_instance);
+
+      CImg<Tfloat> res(_width,_height,_depth,4);
+      const T *ps1 = data(0,0,0,0), *ps2 = data(0,0,0,1), *ps3 = data(0,0,0,2);
+      Tfloat *pd1 = res.data(0,0,0,0), *pd2 = res.data(0,0,0,1), *pd3 = res.data(0,0,0,2), *pd4 = res.data(0,0,0,3);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        Tfloat
+          C = (Tfloat)*(ps1++),
+          M = (Tfloat)*(ps2++),
+          Y = (Tfloat)*(ps3++),
+          K = cimg::min(C,M,Y);
+        if (K>=255) C = M = Y = 0;
+        else { const Tfloat K1 = 255 - K; C = 255*(C - K)/K1; M = 255*(M - K)/K1; Y = 255*(Y - K)/K1; }
+        *(pd1++) = (Tfloat)(C<0?0:(C>255?255:C));
+        *(pd2++) = (Tfloat)(M<0?0:(M>255?255:M));
+        *(pd3++) = (Tfloat)(Y<0?0:(Y>255?255:Y));
+        *(pd4++) = (Tfloat)(K<0?0:(K>255?255:K));
+      }
+      return res;
+    }
+
+    //! Convert (C,M,Y,K) pixels of a color image into the (C,M,Y) color space.
+    CImg<T>& CMYKtoCMY() {
+      return get_CMYKtoCMY().move_to(*this);
+    }
+
+    CImg<Tfloat> get_CMYKtoCMY() const {
+      if (_spectrum!=4)
+        throw CImgInstanceException(_cimg_instance
+                                    "CMYKtoCMY() : Instance is not a CMYK image.",
+                                    cimg_instance);
+
+      CImg<Tfloat> res(_width,_height,_depth,3);
+      const T *ps1 = data(0,0,0,0), *ps2 = data(0,0,0,1), *ps3 = data(0,0,0,2), *ps4 = data(0,0,0,3);
+      Tfloat *pd1 = res.data(0,0,0,0), *pd2 = res.data(0,0,0,1), *pd3 = res.data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          C = (Tfloat)*(ps1++),
+          M = (Tfloat)*(ps2++),
+          Y = (Tfloat)*(ps3++),
+          K = (Tfloat)*(ps4++),
+          K1 = 1 - K/255,
+          nC = C*K1 + K,
+          nM = M*K1 + K,
+          nY = Y*K1 + K;
+        *(pd1++) = (Tfloat)(nC<0?0:(nC>255?255:nC));
+        *(pd2++) = (Tfloat)(nM<0?0:(nM>255?255:nM));
+        *(pd3++) = (Tfloat)(nY<0?0:(nY>255?255:nY));
+      }
+      return res;
+    }
+
+    //! Convert color pixels from (R,G,B) to (X,Y,Z)_709.
+    CImg<T>& RGBtoXYZ() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoXYZ() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          R = (Tfloat)*p1/255,
+          G = (Tfloat)*p2/255,
+          B = (Tfloat)*p3/255;
+        *(p1++) = (T)(0.412453f*R + 0.357580f*G + 0.180423f*B);
+        *(p2++) = (T)(0.212671f*R + 0.715160f*G + 0.072169f*B);
+        *(p3++) = (T)(0.019334f*R + 0.119193f*G + 0.950227f*B);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_RGBtoXYZ() const {
+      return CImg<Tfloat>(*this,false).RGBtoXYZ();
+    }
+
+    //! Convert (X,Y,Z)_709 pixels of a color image into the (R,G,B) color space.
+    CImg<T>& XYZtoRGB() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "XYZtoRGB() : Instance is not a XYZ image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          X = (Tfloat)*p1*255,
+          Y = (Tfloat)*p2*255,
+          Z = (Tfloat)*p3*255,
+          R = 3.240479f*X  - 1.537150f*Y - 0.498535f*Z,
+          G = -0.969256f*X + 1.875992f*Y + 0.041556f*Z,
+          B = 0.055648f*X  - 0.204043f*Y + 1.057311f*Z;
+        *(p1++) = (T)(R<0?0:(R>255?255:R));
+        *(p2++) = (T)(G<0?0:(G>255?255:G));
+        *(p3++) = (T)(B<0?0:(B>255?255:B));
+      }
+      return *this;
+    }
+
+    CImg<Tuchar> get_XYZtoRGB() const {
+      return CImg<Tuchar>(*this,false).XYZtoRGB();
+    }
+
+    //! Convert (X,Y,Z)_709 pixels of a color image into the (L*,a*,b*) color space.
+    CImg<T>& XYZtoLab() {
+#define _cimg_Labf(x) ((x)>=0.008856f?(std::pow(x,(Tfloat)1/3)):(7.787f*(x)+16.0f/116))
+
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "XYZtoLab() : Instance is not a XYZ image.",
+                                    cimg_instance);
+
+      const Tfloat
+        Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
+        Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
+        Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          X = (Tfloat)*p1,
+          Y = (Tfloat)*p2,
+          Z = (Tfloat)*p3,
+          XXn = X/Xn, YYn = Y/Yn, ZZn = Z/Zn,
+          fX = (Tfloat)_cimg_Labf(XXn),
+          fY = (Tfloat)_cimg_Labf(YYn),
+          fZ = (Tfloat)_cimg_Labf(ZZn);
+        *(p1++) = (T)(116*fY - 16);
+        *(p2++) = (T)(500*(fX - fY));
+        *(p3++) = (T)(200*(fY - fZ));
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_XYZtoLab() const {
+      return CImg<Tfloat>(*this,false).XYZtoLab();
+    }
+
+    //! Convert (L,a,b) pixels of a color image into the (X,Y,Z) color space.
+    CImg<T>& LabtoXYZ() {
+#define _cimg_Labfi(x) ((x)>=0.206893f?((x)*(x)*(x)):(((x)-16.0f/116)/7.787f))
+
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "LabtoXYZ() : Instance is not a Lab image.",
+                                    cimg_instance);
+
+      const Tfloat
+        Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
+        Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
+        Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          L = (Tfloat)*p1,
+          a = (Tfloat)*p2,
+          b = (Tfloat)*p3,
+          cY = (L + 16)/116,
+          Y = (Tfloat)(Yn*_cimg_Labfi(cY)),
+          pY = (Tfloat)std::pow(Y/Yn,(Tfloat)1/3),
+          cX = a/500 + pY,
+          X = Xn*cX*cX*cX,
+          cZ = pY - b/200,
+          Z = Zn*cZ*cZ*cZ;
+        *(p1++) = (T)(X);
+        *(p2++) = (T)(Y);
+        *(p3++) = (T)(Z);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_LabtoXYZ() const {
+      return CImg<Tfloat>(*this,false).LabtoXYZ();
+    }
+
+    //! Convert (X,Y,Z)_709 pixels of a color image into the (x,y,Y) color space.
+    CImg<T>& XYZtoxyY() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "XYZtoxyY() : Instance is not a XYZ image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+          X = (Tfloat)*p1,
+          Y = (Tfloat)*p2,
+          Z = (Tfloat)*p3,
+          sum = (X+Y+Z),
+          nsum = sum>0?sum:1;
+        *(p1++) = (T)(X/nsum);
+        *(p2++) = (T)(Y/nsum);
+        *(p3++) = (T)Y;
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_XYZtoxyY() const {
+      return CImg<Tfloat>(*this,false).XYZtoxyY();
+    }
+
+    //! Convert (x,y,Y) pixels of a color image into the (X,Y,Z)_709 color space.
+    CImg<T>& xyYtoXYZ() {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "xyYtoXYZ() : Instance is not a xyY image.",
+                                    cimg_instance);
+
+      T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+      for (unsigned int N = _width*_height*_depth; N; --N) {
+        const Tfloat
+         px = (Tfloat)*p1,
+         py = (Tfloat)*p2,
+         Y = (Tfloat)*p3,
+         ny = py>0?py:1;
+        *(p1++) = (T)(px*Y/ny);
+        *(p2++) = (T)Y;
+        *(p3++) = (T)((1-px-py)*Y/ny);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_xyYtoXYZ() const {
+      return CImg<Tfloat>(*this,false).xyYtoXYZ();
+    }
+
+    //! Convert a (R,G,B) image to a (L,a,b) one.
+    CImg<T>& RGBtoLab() {
+      return RGBtoXYZ().XYZtoLab();
+    }
+
+    CImg<Tfloat> get_RGBtoLab() const {
+      return CImg<Tfloat>(*this,false).RGBtoLab();
+    }
+
+    //! Convert a (L,a,b) image to a (R,G,B) one.
+    CImg<T>& LabtoRGB() {
+      return LabtoXYZ().XYZtoRGB();
+    }
+
+    CImg<Tuchar> get_LabtoRGB() const {
+      return CImg<Tuchar>(*this,false).LabtoRGB();
+    }
+
+    //! Convert a (R,G,B) image to a (x,y,Y) one.
+    CImg<T>& RGBtoxyY() {
+      return RGBtoXYZ().XYZtoxyY();
+    }
+
+    CImg<Tfloat> get_RGBtoxyY() const {
+      return CImg<Tfloat>(*this,false).RGBtoxyY();
+    }
+
+    //! Convert a (x,y,Y) image to a (R,G,B) one.
+    CImg<T>& xyYtoRGB() {
+      return xyYtoXYZ().XYZtoRGB();
+    }
+
+    CImg<Tuchar> get_xyYtoRGB() const {
+      return CImg<Tuchar>(*this,false).xyYtoRGB();
+    }
+
+    //! Convert a (R,G,B) image to a (C,M,Y,K) one.
+    CImg<T>& RGBtoCMYK() {
+      return RGBtoCMY().CMYtoCMYK();
+    }
+
+    CImg<Tfloat> get_RGBtoCMYK() const {
+      return CImg<Tfloat>(*this,false).RGBtoCMYK();
+    }
+
+    //! Convert a (C,M,Y,K) image to a (R,G,B) one.
+    CImg<T>& CMYKtoRGB() {
+      return CMYKtoCMY().CMYtoRGB();
+    }
+
+    CImg<Tuchar> get_CMYKtoRGB() const {
+      return CImg<Tuchar>(*this,false).CMYKtoRGB();
+    }
+
+    //! Convert a (R,G,B) image to a Bayer-coded representation.
+    /**
+       \note First (upper-left) pixel if the red component of the pixel color.
+    **/
+    CImg<T>& RGBtoBayer() {
+      return get_RGBtoBayer().move_to(*this);
+    }
+
+    CImg<T> get_RGBtoBayer() const {
+      if (_spectrum!=3)
+        throw CImgInstanceException(_cimg_instance
+                                    "RGBtoBayer() : Instance is not a RGB image.",
+                                    cimg_instance);
+
+      CImg<T> res(_width,_height,_depth,1);
+      const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+      T *ptrd = res._data;
+      cimg_forXYZ(*this,x,y,z) {
+        if (y%2) {
+          if (x%2) *(ptrd++) = *ptr_b;
+          else *(ptrd++) = *ptr_g;
+        } else {
+          if (x%2) *(ptrd++) = *ptr_g;
+          else *(ptrd++) = *ptr_r;
+        }
+        ++ptr_r; ++ptr_g; ++ptr_b;
+      }
+      return res;
+    }
+
+    //! Convert a Bayer-coded image to a (R,G,B) color image.
+    CImg<T>& BayertoRGB(const unsigned int interpolation_type=3) {
+      return get_BayertoRGB(interpolation_type).move_to(*this);
+    }
+
+    CImg<Tuchar> get_BayertoRGB(const unsigned int interpolation_type=3) const {
+      if (_spectrum!=1)
+        throw CImgInstanceException(_cimg_instance
+                                    "BayertoRGB() : Instance is not a Bayer image.",
+                                    cimg_instance);
+
+      CImg<Tuchar> res(_width,_height,_depth,3);
+      CImg_3x3(I,T);
+      Tuchar *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), *ptr_b = res.data(0,0,0,2);
+      switch (interpolation_type) {
+      case 3 : { // Edge-directed
+        CImg_3x3(R,T);
+        CImg_3x3(G,T);
+        CImg_3x3(B,T);
+        cimg_forXYZ(*this,x,y,z) {
+          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          if (y%2) {
+            if (x%2) {
+              const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
+              *ptr_g = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
+            } else *ptr_g = (Tuchar)Icc;
+          } else {
+            if (x%2) *ptr_g = (Tuchar)Icc;
+            else {
+              const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
+              *ptr_g = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
+            }
+          }
+          ++ptr_g;
+        }
+        cimg_forXYZ(*this,x,y,z) {
+          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          cimg_get3x3(res,x,y,z,1,G,T);
+          if (y%2) {
+            if (x%2) *ptr_b = (Tuchar)Icc;
+            else { *ptr_r = (Tuchar)((Icn+Icp)/2); *ptr_b = (Tuchar)((Inc+Ipc)/2); }
+          } else {
+            if (x%2) { *ptr_r = (Tuchar)((Inc+Ipc)/2); *ptr_b = (Tuchar)((Icn+Icp)/2); }
+            else *ptr_r = (Tuchar)Icc;
+          }
+          ++ptr_r; ++ptr_b;
+        }
+        ptr_r = res.data(0,0,0,0);
+        ptr_g = res.data(0,0,0,1);
+        ptr_b = res.data(0,0,0,2);
+        cimg_forXYZ(*this,x,y,z) {
+          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(res,x,y,z,0,R,T);
+          cimg_get3x3(res,x,y,z,1,G,T);
+          cimg_get3x3(res,x,y,z,2,B,T);
+          if (y%2) {
+            if (x%2) {
+              const float alpha = (float)cimg::sqr(Rnc-Rpc), beta = (float)cimg::sqr(Rcn-Rcp), cx = 1/(1+alpha), cy = 1/(1+beta);
+              *ptr_r = (Tuchar)((cx*(Rnc+Rpc) + cy*(Rcn+Rcp))/(2*(cx+cy)));
+            }
+          } else {
+            if (!(x%2)) {
+              const float alpha = (float)cimg::sqr(Bnc-Bpc), beta = (float)cimg::sqr(Bcn-Bcp), cx = 1/(1+alpha), cy = 1/(1+beta);
+              *ptr_b = (Tuchar)((cx*(Bnc+Bpc) + cy*(Bcn+Bcp))/(2*(cx+cy)));
+            }
+          }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      } break;
+      case 2 : { // Linear interpolation
+        cimg_forXYZ(*this,x,y,z) {
+          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          if (y%2) {
+            if (x%2) { *ptr_r = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); *ptr_g = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *ptr_b = (Tuchar)Icc; }
+            else { *ptr_r = (Tuchar)((Icp+Icn)/2); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)((Inc+Ipc)/2); }
+          } else {
+            if (x%2) { *ptr_r = (Tuchar)((Ipc+Inc)/2); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)((Icn+Icp)/2); }
+            else { *ptr_r = (Tuchar)Icc; *ptr_g = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *ptr_b = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); }
+          }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      } break;
+      case 1 : { // Nearest neighbor interpolation
+        cimg_forXYZ(*this,x,y,z) {
+          const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<width()-1?x+1:x-1, _n1y = y<height()-1?y+1:y-1;
+          cimg_get3x3(*this,x,y,z,0,I,T);
+          if (y%2) {
+            if (x%2) { *ptr_r = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); *ptr_g = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *ptr_b = (Tuchar)Icc; }
+            else { *ptr_r = (Tuchar)cimg::min(Icn,Icp); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)cimg::min(Inc,Ipc); }
+          } else {
+            if (x%2) { *ptr_r = (Tuchar)cimg::min(Inc,Ipc); *ptr_g = (Tuchar)Icc; *ptr_b = (Tuchar)cimg::min(Icn,Icp); }
+            else { *ptr_r = (Tuchar)Icc; *ptr_g = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *ptr_b = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); }
+          }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      } break;
+      default : { // 0-filling interpolation
+        const T *ptrs = _data;
+        res.fill(0);
+        cimg_forXYZ(*this,x,y,z) {
+          const T val = *(ptrs++);
+          if (y%2) { if (x%2) *ptr_b = val; else *ptr_g = val; } else { if (x%2) *ptr_g = val; else *ptr_r = val; }
+          ++ptr_r; ++ptr_g; ++ptr_b;
+        }
+      }
+      }
+      return res;
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Geometric / Spatial Manipulation
+    //@{
+    //------------------------------------------
+
+    static float _cimg_lanczos(const float x) {
+      if (x<=-2 || x>=2) return 0;
+      const float a = (float)cimg::PI*x, b = 0.5f*a;
+      return (float)(x?std::sin(a)*std::sin(b)/(a*b):1);
+    }
+
+    //! Resize an image.
+    /**
+       \param size_x Number of columns (new size along the X-axis).
+       \param size_y Number of rows (new size along the Y-axis).
+       \param size_z Number of slices (new size along the Z-axis).
+       \param size_c Number of vector-channels (new size along the C-axis).
+       \param interpolation_type Method of interpolation :
+       - -1 = no interpolation : raw memory resizing.
+       - 0 = no interpolation : additional space is filled according to \p border_condition.
+       - 1 = nearest-neighbor interpolation.
+       - 2 = moving average interpolation.
+       - 3 = linear interpolation.
+       - 4 = grid interpolation.
+       - 5 = bicubic interpolation.
+       - 6 = lanczos interpolation.
+       \param border_conditions Border condition type.
+       \param centering_x Set centering type (only if \p interpolation_type=0).
+       \param centering_y Set centering type (only if \p interpolation_type=0).
+       \param centering_z Set centering type (only if \p interpolation_type=0).
+       \param centering_c Set centering type (only if \p interpolation_type=0).
+       \note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
+    **/
+    CImg<T>& resize(const int size_x, const int size_y=-100,
+                    const int size_z=-100, const int size_c=-100,
+                    const int interpolation_type=1, const unsigned int border_conditions=0,
+                    const float centering_x = 0, const float centering_y = 0,
+                    const float centering_z = 0, const float centering_c = 0) {
+      if (!size_x || !size_y || !size_z || !size_c) return assign();
+      const unsigned int
+        _sx = (unsigned int)(size_x<0?-size_x*_width/100:size_x),
+        _sy = (unsigned int)(size_y<0?-size_y*_height/100:size_y),
+        _sz = (unsigned int)(size_z<0?-size_z*_depth/100:size_z),
+        _sc = (unsigned int)(size_c<0?-size_c*_spectrum/100:size_c),
+        sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1;
+      if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return *this;
+      if (is_empty()) return assign(sx,sy,sz,sc,0);
+      if (interpolation_type==-1 && sx*sy*sz*sc==size()) {
+        _width = sx; _height = sy; _depth = sz; _spectrum = sc;
+        return *this;
+      }
+      return get_resize(sx,sy,sz,sc,interpolation_type,border_conditions,centering_x,centering_y,centering_z,centering_c).move_to(*this);
+    }
+
+    CImg<T> get_resize(const int size_x, const int size_y = -100,
+                       const int size_z = -100, const int size_c = -100,
+                       const int interpolation_type=1, const unsigned int border_conditions=0,
+                       const float centering_x = 0, const float centering_y = 0,
+                       const float centering_z = 0, const float centering_c = 0) const {
+      if (centering_x<0 || centering_x>1 || centering_y<0 || centering_y>1 ||
+          centering_z<0 || centering_z>1 || centering_c<0 || centering_c>1)
+        throw CImgArgumentException(_cimg_instance
+                                    "resize() : Specified centering arguments (%g,%g,%g,%g) are outside range [0,1].",
+                                    cimg_instance,
+                                    centering_x,centering_y,centering_z,centering_c);
+
+      if (!size_x || !size_y || !size_z || !size_c) return CImg<T>();
+      const unsigned int
+        _sx = (unsigned int)(size_x<0?-size_x*_width/100:size_x),
+        _sy = (unsigned int)(size_y<0?-size_y*_height/100:size_y),
+        _sz = (unsigned int)(size_z<0?-size_z*_depth/100:size_z),
+        _sc = (unsigned int)(size_c<0?-size_c*_spectrum/100:size_c),
+        sx = _sx?_sx:1, sy = _sy?_sy:1, sz = _sz?_sz:1, sc = _sc?_sc:1;
+      if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this;
+      if (is_empty()) return CImg<T>(sx,sy,sz,sc,0);
+
+      CImg<T> res;
+      switch (interpolation_type) {
+
+        // Raw resizing.
+        //
+      case -1 :
+        std::memcpy(res.assign(sx,sy,sz,sc,0)._data,_data,sizeof(T)*cimg::min(size(),sx*sy*sz*sc));
+        break;
+
+        // No interpolation.
+        //
+      case 0 : {
+        const int
+          xc = (int)(centering_x*((int)sx - width())),
+          yc = (int)(centering_y*((int)sy - height())),
+          zc = (int)(centering_z*((int)sz - depth())),
+          cc = (int)(centering_c*((int)sc - spectrum()));
+
+        switch (border_conditions) {
+        case 2 : { // Cyclic borders.
+          res.assign(sx,sy,sz,sc);
+          const int
+            x0 = ((int)xc%width()) - width(),
+            y0 = ((int)yc%height()) - height(),
+            z0 = ((int)zc%depth()) - depth(),
+            c0 = ((int)cc%spectrum()) - spectrum();
+          for (int c = c0; c<(int)sc; c+=spectrum())
+            for (int z = z0; z<(int)sz; z+=depth())
+              for (int y = y0; y<(int)sy; y+=height())
+                for (int x = x0; x<(int)sx; x+=width())
+                  res.draw_image(x,y,z,c,*this);
+        } break;
+        case 1 : { // Neumann borders.
+          res.assign(sx,sy,sz,sc).draw_image(xc,yc,zc,cc,*this);
+          CImg<T> sprite;
+          if (xc>0) {  // X-backward
+            res.get_crop(xc,yc,zc,cc,xc,yc+height()-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int x = xc-1; x>=0; --x) res.draw_image(x,yc,zc,cc,sprite);
+          }
+          if (xc+width()<(int)sx) { // X-forward
+            res.get_crop(xc+width()-1,yc,zc,cc,xc+width()-1,yc+height()-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int x = xc+width(); x<(int)sx; ++x) res.draw_image(x,yc,zc,cc,sprite);
+          }
+          if (yc>0) {  // Y-backward
+            res.get_crop(0,yc,zc,cc,sx-1,yc,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int y = yc-1; y>=0; --y) res.draw_image(0,y,zc,cc,sprite);
+          }
+          if (yc+height()<(int)sy) { // Y-forward
+            res.get_crop(0,yc+height()-1,zc,cc,sx-1,yc+height()-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int y = yc+height(); y<(int)sy; ++y) res.draw_image(0,y,zc,cc,sprite);
+          }
+          if (zc>0) {  // Z-backward
+            res.get_crop(0,0,zc,cc,sx-1,sy-1,zc,cc+spectrum()-1).move_to(sprite);
+            for (int z = zc-1; z>=0; --z) res.draw_image(0,0,z,cc,sprite);
+          }
+          if (zc+depth()<(int)sz) { // Z-forward
+            res.get_crop(0,0,zc+depth()-1,cc,sx-1,sy-1,zc+depth()-1,cc+spectrum()-1).move_to(sprite);
+            for (int z = zc+depth(); z<(int)sz; ++z) res.draw_image(0,0,z,cc,sprite);
+          }
+          if (cc>0) {  // C-backward
+            res.get_crop(0,0,0,cc,sx-1,sy-1,sz-1,cc).move_to(sprite);
+            for (int c = cc-1; c>=0; --c) res.draw_image(0,0,0,c,sprite);
+          }
+          if (cc+spectrum()<(int)sc) { // C-forward
+            res.get_crop(0,0,0,cc+spectrum()-1,sx-1,sy-1,sz-1,cc+spectrum()-1).move_to(sprite);
+            for (int c = cc+spectrum(); c<(int)sc; ++c) res.draw_image(0,0,0,c,sprite);
+          }
+        } break;
+        default : // Dirichlet borders.
+          res.assign(sx,sy,sz,sc,0).draw_image(xc,yc,zc,cc,*this);
+        }
+        break;
+      } break;
+
+        // Nearest neighbor interpolation.
+        //
+      case 1 : {
+        res.assign(sx,sy,sz,sc);
+        CImg<uintT> off_x(sx), off_y(sy+1), off_z(sz+1), off_c(sc+1);
+        unsigned int *poff_x, *poff_y, *poff_z, *poff_c, curr, old;
+        const unsigned int wh = _width*_height, whd = _width*_height*_depth, sxy = sx*sy, sxyz = sx*sy*sz;
+        poff_x = off_x._data; curr = 0;
+        cimg_forX(res,x) { old = curr; curr = (x+1)*_width/sx; *(poff_x++) = (unsigned int)curr - (unsigned int)old; }
+        poff_y = off_y._data; curr = 0;
+        cimg_forY(res,y) { old = curr; curr = (y+1)*_height/sy; *(poff_y++) = _width*((unsigned int)curr - (unsigned int)old); } *poff_y = 0;
+        poff_z = off_z._data; curr = 0;
+        cimg_forZ(res,z) { old = curr; curr = (z+1)*_depth/sz; *(poff_z++) = wh*((unsigned int)curr - (unsigned int)old); } *poff_z = 0;
+        poff_c = off_c._data; curr = 0;
+        cimg_forC(res,c) { old = curr; curr = (c+1)*_spectrum/sc; *(poff_c++) = whd*((unsigned int)curr - (unsigned int)old); } *poff_c = 0;
+        T *ptrd = res._data;
+        const T* ptrv = _data;
+        poff_c = off_c._data;
+        for (unsigned int c = 0; c<sc; ) {
+          const T *ptrz = ptrv;
+          poff_z = off_z._data;
+          for (unsigned int z = 0; z<sz; ) {
+            const T *ptry = ptrz;
+            poff_y = off_y._data;
+            for (unsigned int y = 0; y<sy; ) {
+              const T *ptrx = ptry;
+              poff_x = off_x._data;
+              cimg_forX(res,x) { *(ptrd++) = *ptrx; ptrx+=*(poff_x++); }
+              ++y;
+              unsigned int dy = *(poff_y++);
+              for (;!dy && y<dy; std::memcpy(ptrd,ptrd - sx,sizeof(T)*sx), ++y, ptrd+=sx, dy = *(poff_y++)) {}
+              ptry+=dy;
+            }
+            ++z;
+            unsigned int dz = *(poff_z++);
+            for (;!dz && z<dz; std::memcpy(ptrd,ptrd-sxy,sizeof(T)*sxy), ++z, ptrd+=sxy, dz = *(poff_z++)) {}
+            ptrz+=dz;
+          }
+          ++c;
+          unsigned int dc = *(poff_c++);
+          for (;!dc && c<dc; std::memcpy(ptrd,ptrd-sxyz,sizeof(T)*sxyz), ++c, ptrd+=sxyz, dc = *(poff_c++)) {}
+          ptrv+=dc;
+        }
+      } break;
+
+        // Moving average.
+        //
+      case 2 : {
+        bool instance_first = true;
+        if (sx!=_width) {
+          CImg<Tfloat> tmp(sx,_height,_depth,_spectrum,0);
+          for (unsigned int a = _width*sx, b = _width, c = sx, s = 0, t = 0; a; ) {
+            const unsigned int d = cimg::min(b,c);
+            a-=d; b-=d; c-=d;
+            cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d;
+            if (!b) { cimg_forYZC(tmp,y,z,v) tmp(t,y,z,v)/=_width; ++t; b = _width; }
+            if (!c) { ++s; c = sx; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+        if (sy!=_height) {
+          CImg<Tfloat> tmp(sx,sy,_depth,_spectrum,0);
+          for (unsigned int a = _height*sy, b = _height, c = sy, s = 0, t = 0; a; ) {
+            const unsigned int d = cimg::min(b,c);
+            a-=d; b-=d; c-=d;
+            if (instance_first) cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d;
+            else cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d;
+            if (!b) { cimg_forXZC(tmp,x,z,v) tmp(x,t,z,v)/=_height; ++t; b = _height; }
+            if (!c) { ++s; c = sy; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+        if (sz!=_depth) {
+          CImg<Tfloat> tmp(sx,sy,sz,_spectrum,0);
+          for (unsigned int a = _depth*sz, b = _depth, c = sz, s = 0, t = 0; a; ) {
+            const unsigned int d = cimg::min(b,c);
+            a-=d; b-=d; c-=d;
+            if (instance_first) cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d;
+            else cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d;
+            if (!b) { cimg_forXYC(tmp,x,y,v) tmp(x,y,t,v)/=_depth; ++t; b = _depth; }
+            if (!c) { ++s; c = sz; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+        if (sc!=_spectrum) {
+          CImg<Tfloat> tmp(sx,sy,sz,sc,0);
+          for (unsigned int a = _spectrum*sc, b = _spectrum, c = sc, s = 0, t = 0; a; ) {
+            const unsigned int d = cimg::min(b,c);
+            a-=d; b-=d; c-=d;
+            if (instance_first) cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d;
+            else cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d;
+            if (!b) { cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=_spectrum; ++t; b = _spectrum; }
+            if (!c) { ++s; c = sc; }
+          }
+          tmp.move_to(res);
+          instance_first = false;
+        }
+      } break;
+
+        // Linear interpolation.
+        //
+      case 3 : {
+        CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+        CImg<floatT> foff(off._width);
+        unsigned int *poff;
+        float *pfoff, old, curr;
+        CImg<T> resx, resy, resz, resc;
+        T *ptrd;
+
+        if (sx!=_width) {
+          if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            const float fx = (!border_conditions && sx>_width)?(sx>1?(_width-1.0f)/(sx-1):0):(float)_width/sx;
+            resx.assign(sx,_height,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forX(resx,x) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fx; *(poff++) = (unsigned int)curr - (unsigned int)old; }
+            ptrd = resx._data;
+            const T *ptrs0 = _data;
+            cimg_forYZC(resx,y,z,c) {
+              poff = off._data; pfoff = foff._data;
+              const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (_width-1);
+              cimg_forX(resx,x) {
+                const float alpha = *(pfoff++);
+                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+1):val1;
+                *(ptrd++) = (T)((1-alpha)*val1 + alpha*val2);
+                ptrs+=*(poff++);
+              }
+              ptrs0+=_width;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            const float fy = (!border_conditions && sy>_height)?(sy>1?(_height-1.0f)/(sy-1):0):(float)_height/sy;
+            resy.assign(sx,sy,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forY(resy,y) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fy; *(poff++) = sx*((unsigned int)curr-(unsigned int)old); }
+            cimg_forXZC(resy,x,z,c) {
+              ptrd = resy.data(x,0,z,c);
+              const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height-1)*sx;
+              poff = off._data; pfoff = foff._data;
+              cimg_forY(resy,y) {
+                const float alpha = *(pfoff++);
+                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+sx):val1;
+                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+                ptrd+=sx;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            const float fz = (!border_conditions && sz>_depth)?(sz>1?(_depth-1.0f)/(sz-1):0):(float)_depth/sz;
+            const unsigned int sxy = sx*sy;
+            resz.assign(sx,sy,sz,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forZ(resz,z) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fz; *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYC(resz,x,y,c) {
+              ptrd = resz.data(x,y,0,c);
+              const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth-1)*sxy;
+              poff = off._data; pfoff = foff._data;
+              cimg_forZ(resz,z) {
+                const float alpha = *(pfoff++);
+                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+sxy):val1;
+                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+                ptrd+=sxy;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            const float fc = (!border_conditions && sc>_spectrum)?(sc>1?(_spectrum-1.0f)/(sc-1):0):(float)_spectrum/sc;
+            const unsigned int sxyz = sx*sy*sz;
+            resc.assign(sx,sy,sz,sc);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forC(resc,c) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fc; *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYZ(resc,x,y,z) {
+              ptrd = resc.data(x,y,z,0);
+              const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum-1)*sxyz;
+              poff = off._data; pfoff = foff._data;
+              cimg_forC(resc,c) {
+                const float alpha = *(pfoff++);
+                const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+sxyz):val1;
+                *ptrd = (T)((1-alpha)*val1 + alpha*val2);
+                ptrd+=sxyz;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Grid interpolation.
+        //
+      case 4 : {
+        CImg<T> resx, resy, resz, resc;
+        const unsigned int sxy = sx*sy, sxyz = sx*sy*sz;
+        if (sx!=_width) {
+          if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            resx.assign(sx,_height,_depth,_spectrum,0);
+            const T *ptrs = _data;
+            T *ptrd = resx._data + (int)(centering_x*(sx-1)/_width);
+            cimg_forYZC(*this,y,z,c) {
+              cimg_forX(*this,x) ptrd[x*sx/_width] = *(ptrs++);
+              ptrd+=sx;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (sy<_height) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            resy.assign(sx,sy,_depth,_spectrum,0);
+            const T *ptrs = resx._data;
+            T *ptrd = resy._data + (int)(centering_y*(sy-1)/_height)*sx;
+            cimg_forZC(*this,z,c) {
+              cimg_forY(*this,y) { std::memcpy(ptrd + (y*sy/_height)*sx,ptrs,sizeof(T)*sx); ptrs+=sx; }
+              ptrd+=sxy;
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (sz<_depth) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            resz.assign(sx,sy,sz,_spectrum,0);
+            const T *ptrs = resy._data;
+            T *ptrd = resz._data + (int)(centering_z*(sz-1)/_depth)*sxy;
+            cimg_forC(*this,c) {
+              cimg_forZ(*this,z) { std::memcpy(ptrd + (z*sz/_depth)*sxy,ptrs,sizeof(T)*sxy); ptrs+=sxy; }
+              ptrd+=sxyz;
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (sc<_spectrum) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            resc.assign(sx,sy,sz,sc,0);
+            const T *ptrs = resz._data;
+            T *ptrd = resc._data + (int)(centering_c*(sc-1)/_spectrum)*sxyz;
+            cimg_forC(*this,c) { std::memcpy(ptrd + (c*sc/_spectrum)*sxyz,ptrs,sizeof(T)*sxyz); ptrs+=sxyz; }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Bicubic interpolation.
+        //
+      case 5 : {
+        CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+        CImg<floatT> foff(off._width);
+        unsigned int *poff;
+        float *pfoff, old, curr;
+        CImg<T> resx, resy, resz, resc;
+        T *ptrd, m, M = max_min(m);
+
+        if (sx!=_width) {
+          if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            const float fx = (!border_conditions && sx>_width)?(sx>1?(_width-1.0f)/(sx-1):0):(float)_width/sx;
+            resx.assign(sx,_height,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forX(resx,x) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fx; *(poff++) = (unsigned int)curr - (unsigned int)old; }
+            ptrd = resx._data;
+            const T *ptrs0 = _data;
+            cimg_forYZC(resx,y,z,c) {
+              poff = off._data; pfoff = foff._data;
+              const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (_width-2);
+              cimg_forX(resx,x) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-1):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+1):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *(ptrd++) = (T)(val<m?m:val>M?M:val);
+                ptrs+=*(poff++);
+              }
+              ptrs0+=_width;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            const float fy = (!border_conditions && sy>_height)?(sy>1?(_height-1.0f)/(sy-1):0):(float)_height/sy;
+            resy.assign(sx,sy,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forY(resy,y) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fy; *(poff++) = sx*((unsigned int)curr-(unsigned int)old); }
+            cimg_forXZC(resy,x,z,c) {
+              ptrd = resy.data(x,0,z,c);
+              const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs0 + (_height-2)*sx;
+              poff = off._data; pfoff = foff._data;
+              cimg_forY(resy,y) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-sx):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sx):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sx):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *ptrd = (T)(val<m?m:val>M?M:val);
+                ptrd+=sx;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            const float fz = (!border_conditions && sz>_depth)?(sz>1?(_depth-1.0f)/(sz-1):0):(float)_depth/sz;
+            const unsigned int sxy = sx*sy;
+            resz.assign(sx,sy,sz,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forZ(resz,z) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fz; *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYC(resz,x,y,c) {
+              ptrd = resz.data(x,y,0,c);
+              const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs0 + (_depth-2)*sxy;
+              poff = off._data; pfoff = foff._data;
+              cimg_forZ(resz,z) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-sxy):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxy):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxy):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *ptrd = (T)(val<m?m:val>M?M:val);
+                ptrd+=sxy;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            const float fc = (!border_conditions && sc>_spectrum)?(sc>1?(_spectrum-1.0f)/(sc-1):0):(float)_spectrum/sc;
+            const unsigned int sxyz = sx*sy*sz;
+            resc.assign(sx,sy,sz,sc);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forC(resc,c) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fc; *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYZ(resc,x,y,z) {
+              ptrd = resc.data(x,y,z,0);
+              const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum-2)*sxyz;
+              poff = off._data; pfoff = foff._data;
+              cimg_forC(resc,c) {
+                const float t = *(pfoff++);
+                const Tfloat
+                  val1 = (Tfloat)*ptrs,
+                  val0 = ptrs>ptrs0?(Tfloat)*(ptrs-sxyz):val1,
+                  val2 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxyz):val1,
+                  val3 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxyz):val2,
+                  val = val1 + 0.5f*(t*(-val0+val2) + t*t*(2*val0-5*val1+4*val2-val3) + t*t*t*(-val0+3*val1-3*val2+val3));
+                *ptrd = (T)(val<m?m:val>M?M:val);
+                ptrd+=sxyz;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Lanczos interpolation.
+        //
+      case 6 : {
+        CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+        CImg<floatT> foff(off._width);
+        unsigned int *poff;
+        float *pfoff, old, curr;
+        CImg<T> resx, resy, resz, resc;
+        T *ptrd, m, M = max_min(m);
+
+        if (sx!=_width) {
+          if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+          else {
+            const float fx = (!border_conditions && sx>_width)?(sx>1?(_width-1.0f)/(sx-1):0):(float)_width/sx;
+            resx.assign(sx,_height,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forX(resx,x) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fx; *(poff++) = (unsigned int)curr - (unsigned int)old; }
+            ptrd = resx._data;
+            const T *ptrs0 = _data;
+            cimg_forYZC(resx,y,z,c) {
+              poff = off._data; pfoff = foff._data;
+              const T *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1, *const ptrsmax = ptrs0 + (_width-2);
+              cimg_forX(resx,x) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-1):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+1):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *(ptrd++) = (T)(val<m?m:val>M?M:val);
+                ptrs+=*(poff++);
+              }
+              ptrs0+=_width;
+            }
+          }
+        } else resx.assign(*this,true);
+
+        if (sy!=_height) {
+          if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+          else {
+            const float fy = (!border_conditions && sy>_height)?(sy>1?(_height-1.0f)/(sy-1):0):(float)_height/sy;
+            resy.assign(sx,sy,_depth,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forY(resy,y) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fy; *(poff++) = sx*((unsigned int)curr-(unsigned int)old); }
+            cimg_forXZC(resy,x,z,c) {
+              ptrd = resy.data(x,0,z,c);
+              const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx, *const ptrsmax = ptrs0 + (_height-2)*sx;
+              poff = off._data; pfoff = foff._data;
+              cimg_forY(resy,y) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-sx):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2*sx):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sx):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sx):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *ptrd = (T)(val<m?m:val>M?M:val);
+                ptrd+=sx;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resx.assign();
+        } else resy.assign(resx,true);
+
+        if (sz!=_depth) {
+          if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+          else {
+            const float fz = (!border_conditions && sz>_depth)?(sz>1?(_depth-1.0f)/(sz-1):0):(float)_depth/sz;
+            const unsigned int sxy = sx*sy;
+            resz.assign(sx,sy,sz,_spectrum);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forZ(resz,z) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fz; *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYC(resz,x,y,c) {
+              ptrd = resz.data(x,y,0,c);
+              const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy, *const ptrsmax = ptrs0 + (_depth-2)*sxy;
+              poff = off._data; pfoff = foff._data;
+              cimg_forZ(resz,z) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-sxy):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2*sxy):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxy):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxy):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *ptrd = (T)(val<m?m:val>M?M:val);
+                ptrd+=sxy;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resy.assign();
+        } else resz.assign(resy,true);
+
+        if (sc!=_spectrum) {
+          if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+          else {
+            const float fc = (!border_conditions && sc>_spectrum)?(sc>1?(_spectrum-1.0f)/(sc-1):0):(float)_spectrum/sc;
+            const unsigned int sxyz = sx*sy*sz;
+            resc.assign(sx,sy,sz,sc);
+            curr = old = 0; poff = off._data; pfoff = foff._data;
+            cimg_forC(resc,c) { *(pfoff++) = curr - (unsigned int)curr; old = curr; curr+=fc; *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); }
+            cimg_forXYZ(resc,x,y,z) {
+              ptrd = resc.data(x,y,z,0);
+              const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz, *const ptrsmax = ptrs + (_spectrum-2)*sxyz;
+              poff = off._data; pfoff = foff._data;
+              cimg_forC(resc,c) {
+                const float
+                  t = *(pfoff++),
+                  w0 = _cimg_lanczos(t+2),
+                  w1 = _cimg_lanczos(t+1),
+                  w2 = _cimg_lanczos(t),
+                  w3 = _cimg_lanczos(t-1),
+                  w4 = _cimg_lanczos(t-2);
+                const Tfloat
+                  val2 = (Tfloat)*ptrs,
+                  val1 = ptrs>=ptrsmin?(Tfloat)*(ptrs-sxyz):val2,
+                  val0 = ptrs>ptrsmin?(Tfloat)*(ptrs-2*sxyz):val1,
+                  val3 = ptrs<=ptrsmax?(Tfloat)*(ptrs+sxyz):val2,
+                  val4 = ptrs<ptrsmax?(Tfloat)*(ptrs+2*sxyz):val3,
+                  val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+                *ptrd = (T)(val<m?m:val>M?M:val);
+                ptrd+=sxyz;
+                ptrs+=*(poff++);
+              }
+            }
+          }
+          resz.assign();
+        } else resc.assign(resz,true);
+
+        return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+      } break;
+
+        // Unknow interpolation.
+        //
+      default :
+        throw CImgArgumentException(_cimg_instance
+                                    "resize() : Invalid specified interpolation %d "
+                                    "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | 5=bicubic | 6=lanczos }).",
+                                    cimg_instance,
+                                    interpolation_type);
+      }
+      return res;
+    }
+
+    //! Resize an image.
+    template<typename t>
+    CImg<T>& resize(const CImg<t>& src,
+                    const int interpolation_type=1, const unsigned int border_conditions=0,
+                    const float centering_x = 0, const float centering_y = 0,
+                    const float centering_z = 0, const float centering_c = 0) {
+      return resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,border_conditions,
+                    centering_x,centering_y,centering_z,centering_c);
+    }
+
+    template<typename t>
+    CImg<T> get_resize(const CImg<t>& src,
+                       const int interpolation_type=1, const unsigned int border_conditions=0,
+                       const float centering_x = 0, const float centering_y = 0,
+                       const float centering_z = 0, const float centering_c = 0) const {
+      return get_resize(src._width,src._height,src._depth,src._spectrum,interpolation_type,border_conditions,
+                        centering_x,centering_y,centering_z,centering_c);
+    }
+
+    //! Resize an image.
+    CImg<T>& resize(const CImgDisplay& disp,
+                    const int interpolation_type=1, const unsigned int border_conditions=0,
+                    const float centering_x = 0, const float centering_y = 0,
+                    const float centering_z = 0, const float centering_c = 0) {
+      return resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,border_conditions,
+                    centering_x,centering_y,centering_z,centering_c);
+    }
+
+    CImg<T> get_resize(const CImgDisplay& disp,
+                       const int interpolation_type=1, const unsigned int border_conditions=0,
+                       const float centering_x = 0, const float centering_y = 0,
+                       const float centering_z = 0, const float centering_c = 0) const {
+      return get_resize(disp.width(),disp.height(),_depth,_spectrum,interpolation_type,border_conditions,
+                        centering_x,centering_y,centering_z,centering_c);
+    }
+
+    //! Half-resize an image, using a special optimized filter.
+    CImg<T>& resize_halfXY() {
+      return get_resize_halfXY().move_to(*this);
+    }
+
+    CImg<T> get_resize_halfXY() const {
+      if (is_empty()) return *this;
+      const Tfloat mask[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f,
+                              0.1231940459f,  0.1935127547f, 0.1231940459f,
+                              0.07842776544f, 0.1231940459f, 0.07842776544f };
+      T I[9] = { 0 };
+      CImg<T> res(_width/2,_height/2,_depth,_spectrum);
+      T *ptrd = res._data;
+      cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T)
+        if (x%2 && y%2) *(ptrd++) = (T)
+                          (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
+                           I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
+                           I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
+      return res;
+    }
+
+    //! Upscale an image by a factor 2x.
+    /**
+       Use anisotropic upscaling algorithm described at
+       http://scale2x.sourceforge.net/algorithm.html
+    **/
+    CImg<T>& resize_doubleXY() {
+      return get_resize_doubleXY().move_to(*this);
+    }
+
+    CImg<T> get_resize_doubleXY() const {
+#define _cimg_gs2x_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+      _n1##i<(int)(bound) || i==--_n1##i; \
+      _p1##i = i++, ++_n1##i, ptrd1+=(res)._width, ptrd2+=(res)._width)
+
+#define _cimg_gs2x_for3x3(img,x,y,z,c,I,T) \
+  _cimg_gs2x_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[1] = (T)(img)(0,_p1##y,z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+   (I[7] = (T)(img)(0,_n1##y,z,c)),     \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+   x==--_n1##x; \
+   I[1] = I[2], \
+   I[3] = I[4], I[4] = I[5], \
+   I[7] = I[8], \
+   _p1##x = x++, ++_n1##x)
+
+      if (is_empty()) return *this;
+      CImg<T> res(_width<<1,_height<<1,_depth,_spectrum);
+      CImg_3x3(I,T);
+      cimg_forZC(*this,z,c) {
+        T
+          *ptrd1 = res.data(0,0,0,c),
+          *ptrd2 = ptrd1 + res._width;
+        _cimg_gs2x_for3x3(*this,x,y,0,c,I,T) {
+          if (Icp!=Icn && Ipc!=Inc) {
+            *(ptrd1++) = Ipc==Icp?Ipc:Icc;
+            *(ptrd1++) = Icp==Inc?Inc:Icc;
+            *(ptrd2++) = Ipc==Icn?Ipc:Icc;
+            *(ptrd2++) = Icn==Inc?Inc:Icc;
+          } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; }
+        }
+      }
+      return res;
+    }
+
+    //! Upscale an image by a factor 3x.
+    /**
+       Use anisotropic upscaling algorithm described at
+       http://scale2x.sourceforge.net/algorithm.html
+    **/
+    CImg<T>& resize_tripleXY() {
+      return get_resize_tripleXY().move_to(*this);
+    }
+
+    CImg<T> get_resize_tripleXY() const {
+#define _cimg_gs3x_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+      _n1##i = 1>=(bound)?(int)(bound)-1:1; \
+      _n1##i<(int)(bound) || i==--_n1##i; \
+      _p1##i = i++, ++_n1##i, ptrd1+=2*(res)._width, ptrd2+=2*(res)._width, ptrd3+=2*(res)._width)
+
+#define _cimg_gs3x_for3x3(img,x,y,z,c,I,T) \
+  _cimg_gs3x_for3((img)._height,y) for (int x = 0, \
+   _p1##x = 0, \
+   _n1##x = (int)( \
+   (I[0] = I[1] = (T)(img)(0,_p1##y,z,c)), \
+   (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+   (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)),      \
+   1>=(img)._width?(img).width()-1:1); \
+   (_n1##x<(img).width() && ( \
+   (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+   (I[5] = (T)(img)(_n1##x,y,z,c)), \
+   (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+   x==--_n1##x; \
+   I[0] = I[1], I[1] = I[2], \
+   I[3] = I[4], I[4] = I[5], \
+   I[6] = I[7], I[7] = I[8], \
+   _p1##x = x++, ++_n1##x)
+
+      if (is_empty()) return *this;
+      CImg<T> res(3*_width,3*_height,_depth,_spectrum);
+      CImg_3x3(I,T);
+      cimg_forZC(*this,z,c) {
+        T
+          *ptrd1 = res.data(0,0,0,c),
+          *ptrd2 = ptrd1 + res._width,
+          *ptrd3 = ptrd2 + res._width;
+        _cimg_gs3x_for3x3(*this,x,y,0,c,I,T) {
+          if (Icp != Icn && Ipc != Inc) {
+            *(ptrd1++) = Ipc==Icp?Ipc:Icc;
+            *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc;
+            *(ptrd1++) = Icp==Inc?Inc:Icc;
+            *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc;
+            *(ptrd2++) = Icc;
+            *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc;
+            *(ptrd3++) = Ipc==Icn?Ipc:Icc;
+            *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc;
+            *(ptrd3++) = Icn==Inc?Inc:Icc;
+          } else {
+            *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc;
+            *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc;
+            *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc;
+          }
+        }
+      }
+      return res;
+    }
+
+    //! Mirror an image along the specified axis.
+    CImg<T>& mirror(const char axis) {
+      if (is_empty()) return *this;
+      T *pf, *pb, *buf = 0;
+      switch (cimg::uncase(axis)) {
+      case 'x' : {
+        pf = _data; pb = data(_width-1);
+        const unsigned int width2 = _width/2;
+        for (unsigned int yzv = 0; yzv<_height*_depth*_spectrum; ++yzv) {
+          for (unsigned int x = 0; x<width2; ++x) { const T val = *pf; *(pf++) = *pb; *(pb--) = val; }
+          pf+=_width - width2;
+          pb+=_width + width2;
+        }
+      } break;
+      case 'y' : {
+        buf = new T[_width];
+        pf = _data; pb = data(0,_height-1);
+        const unsigned int height2 = _height/2;
+        for (unsigned int zv = 0; zv<_depth*_spectrum; ++zv) {
+          for (unsigned int y = 0; y<height2; ++y) {
+            std::memcpy(buf,pf,_width*sizeof(T));
+            std::memcpy(pf,pb,_width*sizeof(T));
+            std::memcpy(pb,buf,_width*sizeof(T));
+            pf+=_width;
+            pb-=_width;
+          }
+          pf+=_width*(_height - height2);
+          pb+=_width*(_height + height2);
+        }
+      } break;
+      case 'z' : {
+        buf = new T[_width*_height];
+        pf = _data; pb = data(0,0,_depth-1);
+        const unsigned int depth2 = _depth/2;
+        cimg_forC(*this,c) {
+          for (unsigned int z = 0; z<depth2; ++z) {
+            std::memcpy(buf,pf,_width*_height*sizeof(T));
+            std::memcpy(pf,pb,_width*_height*sizeof(T));
+            std::memcpy(pb,buf,_width*_height*sizeof(T));
+            pf+=_width*_height;
+            pb-=_width*_height;
+          }
+          pf+=_width*_height*(_depth - depth2);
+          pb+=_width*_height*(_depth + depth2);
+        }
+      } break;
+      default : {
+        buf = new T[_width*_height*_depth];
+        pf = _data; pb = data(0,0,0,_spectrum-1);
+        const unsigned int _spectrum2 = _spectrum/2;
+        for (unsigned int v = 0; v<_spectrum2; ++v) {
+          std::memcpy(buf,pf,_width*_height*_depth*sizeof(T));
+          std::memcpy(pf,pb,_width*_height*_depth*sizeof(T));
+          std::memcpy(pb,buf,_width*_height*_depth*sizeof(T));
+          pf+=_width*_height*_depth;
+          pb-=_width*_height*_depth;
+        }
+      }
+      }
+      if (buf) delete[] buf;
+      return *this;
+    }
+
+    CImg<T> get_mirror(const char axis) const {
+      return (+*this).mirror(axis);
+    }
+
+    //! Shift the image.
+    /**
+       \param deltax Amount of displacement along the X-axis.
+       \param deltay Amount of displacement along the Y-axis.
+       \param deltaz Amount of displacement along the Z-axis.
+       \param deltac Amount of displacement along the C-axis.
+       \param border_condition Border condition.
+
+       - \c border_condition can be :
+          - 0 : Zero border condition (Dirichlet).
+          - 1 : Nearest neighbors (Neumann).
+          - 2 : Repeat Pattern (Fourier style).
+    **/
+    CImg<T>& shift(const int deltax, const int deltay=0, const int deltaz=0, const int deltac=0,
+                       const int border_condition=0) {
+      if (is_empty()) return *this;
+      if (deltax) // Shift along X-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltax)>=width()) return fill(0);
+          if (deltax<0) cimg_forYZC(*this,y,z,c) {
+            std::memmove(data(0,y,z,c),data(-deltax,y,z,c),(_width+deltax)*sizeof(T));
+            std::memset(data(_width+deltax,y,z,c),0,-deltax*sizeof(T));
+          } else cimg_forYZC(*this,y,z,c) {
+            std::memmove(data(deltax,y,z,c),data(0,y,z,c),(_width-deltax)*sizeof(T));
+            std::memset(data(0,y,z,c),0,deltax*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltax<0) {
+            const int ndeltax = (-deltax>=width())?_width-1:-deltax;
+            if (!ndeltax) return *this;
+            cimg_forYZC(*this,y,z,c) {
+              std::memmove(data(0,y,z,c),data(ndeltax,y,z,c),(_width-ndeltax)*sizeof(T));
+              T *ptrd = data(_width-1,y,z,c);
+              const T val = *ptrd;
+              for (int l = 0; l<ndeltax-1; ++l) *(--ptrd) = val;
+            }
+          } else {
+            const int ndeltax = (deltax>=width())?_width-1:deltax;
+            if (!ndeltax) return *this;
+            cimg_forYZC(*this,y,z,c) {
+              std::memmove(data(ndeltax,y,z,c),data(0,y,z,c),(_width-ndeltax)*sizeof(T));
+              T *ptrd = data(0,y,z,c);
+              const T val = *ptrd;
+              for (int l = 0; l<ndeltax-1; ++l) *(++ptrd) = val;
+            }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltax,width()), ndeltax = (ml<=width()/2)?ml:(ml-width());
+          if (!ndeltax) return *this;
+          T *const buf = new T[cimg::abs(ndeltax)];
+          if (ndeltax>0) cimg_forYZC(*this,y,z,c) {
+            std::memcpy(buf,data(0,y,z,c),ndeltax*sizeof(T));
+            std::memmove(data(0,y,z,c),data(ndeltax,y,z,c),(_width-ndeltax)*sizeof(T));
+            std::memcpy(data(_width-ndeltax,y,z,c),buf,ndeltax*sizeof(T));
+          } else cimg_forYZC(*this,y,z,c) {
+            std::memcpy(buf,data(_width+ndeltax,y,z,c),-ndeltax*sizeof(T));
+            std::memmove(data(-ndeltax,y,z,c),data(0,y,z,c),(_width+ndeltax)*sizeof(T));
+            std::memcpy(data(0,y,z,c),buf,-ndeltax*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+
+      if (deltay) // Shift along Y-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltay)>=height()) return fill(0);
+          if (deltay<0) cimg_forZC(*this,z,c) {
+            std::memmove(data(0,0,z,c),data(0,-deltay,z,c),_width*(_height+deltay)*sizeof(T));
+            std::memset(data(0,_height+deltay,z,c),0,-deltay*_width*sizeof(T));
+          } else cimg_forZC(*this,z,c) {
+            std::memmove(data(0,deltay,z,c),data(0,0,z,c),_width*(_height-deltay)*sizeof(T));
+            std::memset(data(0,0,z,c),0,deltay*_width*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltay<0) {
+            const int ndeltay = (-deltay>=height())?_height-1:-deltay;
+            if (!ndeltay) return *this;
+            cimg_forZC(*this,z,c) {
+              std::memmove(data(0,0,z,c),data(0,ndeltay,z,c),_width*(_height-ndeltay)*sizeof(T));
+              T *ptrd = data(0,_height-ndeltay,z,c), *ptrs = data(0,_height-1,z,c);
+              for (int l = 0; l<ndeltay-1; ++l) { std::memcpy(ptrd,ptrs,_width*sizeof(T)); ptrd+=_width; }
+            }
+          } else {
+            const int ndeltay = (deltay>=height())?_height-1:deltay;
+            if (!ndeltay) return *this;
+            cimg_forZC(*this,z,c) {
+              std::memmove(data(0,ndeltay,z,c),data(0,0,z,c),_width*(_height-ndeltay)*sizeof(T));
+              T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c);
+              for (int l = 0; l<ndeltay-1; ++l) { std::memcpy(ptrd,ptrs,_width*sizeof(T)); ptrd+=_width; }
+            }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltay,height()), ndeltay = (ml<=height()/2)?ml:(ml-height());
+          if (!ndeltay) return *this;
+          T *const buf = new T[_width*cimg::abs(ndeltay)];
+          if (ndeltay>0) cimg_forZC(*this,z,c) {
+            std::memcpy(buf,data(0,0,z,c),_width*ndeltay*sizeof(T));
+            std::memmove(data(0,0,z,c),data(0,ndeltay,z,c),_width*(_height-ndeltay)*sizeof(T));
+            std::memcpy(data(0,_height-ndeltay,z,c),buf,_width*ndeltay*sizeof(T));
+          } else cimg_forZC(*this,z,c) {
+            std::memcpy(buf,data(0,_height+ndeltay,z,c),-ndeltay*_width*sizeof(T));
+            std::memmove(data(0,-ndeltay,z,c),data(0,0,z,c),_width*(_height+ndeltay)*sizeof(T));
+            std::memcpy(data(0,0,z,c),buf,-ndeltay*_width*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+
+      if (deltaz) // Shift along Z-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltaz)>=depth()) return fill(0);
+          if (deltaz<0) cimg_forC(*this,c) {
+            std::memmove(data(0,0,0,c),data(0,0,-deltaz,c),_width*_height*(_depth+deltaz)*sizeof(T));
+            std::memset(data(0,0,_depth+deltaz,c),0,_width*_height*(-deltaz)*sizeof(T));
+          } else cimg_forC(*this,c) {
+            std::memmove(data(0,0,deltaz,c),data(0,0,0,c),_width*_height*(_depth-deltaz)*sizeof(T));
+            std::memset(data(0,0,0,c),0,deltaz*_width*_height*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltaz<0) {
+            const int ndeltaz = (-deltaz>=depth())?_depth-1:-deltaz;
+            if (!ndeltaz) return *this;
+            cimg_forC(*this,c) {
+              std::memmove(data(0,0,0,c),data(0,0,ndeltaz,c),_width*_height*(_depth-ndeltaz)*sizeof(T));
+              T *ptrd = data(0,0,_depth-ndeltaz,c), *ptrs = data(0,0,_depth-1,c);
+              for (int l = 0; l<ndeltaz-1; ++l) { std::memcpy(ptrd,ptrs,_width*_height*sizeof(T)); ptrd+=_width*_height; }
+            }
+          } else {
+            const int ndeltaz = (deltaz>=depth())?_depth-1:deltaz;
+            if (!ndeltaz) return *this;
+            cimg_forC(*this,c) {
+              std::memmove(data(0,0,ndeltaz,c),data(0,0,0,c),_width*_height*(_depth-ndeltaz)*sizeof(T));
+              T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c);
+              for (int l = 0; l<ndeltaz-1; ++l) { std::memcpy(ptrd,ptrs,_width*_height*sizeof(T)); ptrd+=_width*_height; }
+            }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltaz,depth()), ndeltaz = (ml<=depth()/2)?ml:(ml-depth());
+          if (!ndeltaz) return *this;
+          T *const buf = new T[_width*_height*cimg::abs(ndeltaz)];
+          if (ndeltaz>0) cimg_forC(*this,c) {
+            std::memcpy(buf,data(0,0,0,c),_width*_height*ndeltaz*sizeof(T));
+            std::memmove(data(0,0,0,c),data(0,0,ndeltaz,c),_width*_height*(_depth-ndeltaz)*sizeof(T));
+            std::memcpy(data(0,0,_depth-ndeltaz,c),buf,_width*_height*ndeltaz*sizeof(T));
+          } else cimg_forC(*this,c) {
+            std::memcpy(buf,data(0,0,_depth+ndeltaz,c),-ndeltaz*_width*_height*sizeof(T));
+            std::memmove(data(0,0,-ndeltaz,c),data(0,0,0,c),_width*_height*(_depth+ndeltaz)*sizeof(T));
+            std::memcpy(data(0,0,0,c),buf,-ndeltaz*_width*_height*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+
+      if (deltac) // Shift along C-axis
+        switch (border_condition) {
+        case 0 :
+          if (cimg::abs(deltac)>=spectrum()) return fill(0);
+          if (-deltac>0) {
+            std::memmove(_data,data(0,0,0,-deltac),_width*_height*_depth*(_spectrum+deltac)*sizeof(T));
+            std::memset(data(0,0,0,_spectrum+deltac),0,_width*_height*_depth*(-deltac)*sizeof(T));
+          } else cimg_forC(*this,c) {
+            std::memmove(data(0,0,0,deltac),_data,_width*_height*_depth*(_spectrum-deltac)*sizeof(T));
+            std::memset(_data,0,deltac*_width*_height*_depth*sizeof(T));
+          }
+          break;
+        case 1 :
+          if (deltac<0) {
+            const int ndeltac = (-deltac>=spectrum())?_spectrum-1:-deltac;
+            if (!ndeltac) return *this;
+            std::memmove(_data,data(0,0,0,ndeltac),_width*_height*_depth*(_spectrum-ndeltac)*sizeof(T));
+            T *ptrd = data(0,0,0,_spectrum-ndeltac), *ptrs = data(0,0,0,_spectrum-1);
+            for (int l = 0; l<ndeltac-1; ++l) { std::memcpy(ptrd,ptrs,_width*_height*_depth*sizeof(T)); ptrd+=_width*_height*_depth; }
+          } else {
+            const int ndeltac = (deltac>=spectrum())?_spectrum-1:deltac;
+            if (!ndeltac) return *this;
+            std::memmove(data(0,0,0,ndeltac),_data,_width*_height*_depth*(_spectrum-ndeltac)*sizeof(T));
+            T *ptrd = data(0,0,0,1);
+            for (int l = 0; l<ndeltac-1; ++l) { std::memcpy(ptrd,_data,_width*_height*_depth*sizeof(T)); ptrd+=_width*_height*_depth; }
+          }
+          break;
+        default : {
+          const int ml = cimg::mod(-deltac,spectrum()), ndeltac = (ml<=spectrum()/2)?ml:(ml-spectrum());
+          if (!ndeltac) return *this;
+          T *const buf = new T[_width*_height*_depth*cimg::abs(ndeltac)];
+          if (ndeltac>0) {
+            std::memcpy(buf,_data,_width*_height*_depth*ndeltac*sizeof(T));
+            std::memmove(_data,data(0,0,0,ndeltac),_width*_height*_depth*(_spectrum-ndeltac)*sizeof(T));
+            std::memcpy(data(0,0,0,_spectrum-ndeltac),buf,_width*_height*_depth*ndeltac*sizeof(T));
+          } else {
+            std::memcpy(buf,data(0,0,0,_spectrum+ndeltac),-ndeltac*_width*_height*_depth*sizeof(T));
+            std::memmove(data(0,0,0,-ndeltac),_data,_width*_height*_depth*(_spectrum+ndeltac)*sizeof(T));
+            std::memcpy(_data,buf,-ndeltac*_width*_height*_depth*sizeof(T));
+          }
+          delete[] buf;
+        }
+        }
+      return *this;
+    }
+
+    CImg<T> get_shift(const int deltax, const int deltay=0, const int deltaz=0, const int deltac=0,
+                          const int border_condition=0) const {
+      return (+*this).shift(deltax,deltay,deltaz,deltac,border_condition);
+    }
+
+    // Permute axes order (internal).
+    template<typename t>
+    CImg<t> _get_permute_axes(const char *const permut, const t&) const {
+      if (is_empty() || !permut) return CImg<t>(*this,false);
+      CImg<t> res;
+      const T* ptrs = _data;
+      if (!cimg::strncasecmp(permut,"xyzc",4)) return (+*this);
+      if (!cimg::strncasecmp(permut,"xycz",4)) {
+        res.assign(_width,_height,_spectrum,_depth);
+        cimg_forXYZC(*this,x,y,z,c) res(x,y,c,z) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xzyc",4)) {
+        res.assign(_width,_depth,_height,_spectrum);
+        cimg_forXYZC(*this,x,y,z,c) res(x,z,y,c) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xzcy",4)) {
+        res.assign(_width,_depth,_spectrum,_height);
+        cimg_forXYZC(*this,x,y,z,c) res(x,z,c,y) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xcyz",4)) {
+        res.assign(_width,_spectrum,_height,_depth);
+        cimg_forXYZC(*this,x,y,z,c) res(x,c,y,z) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"xczy",4)) {
+        res.assign(_width,_spectrum,_depth,_height);
+        cimg_forXYZC(*this,x,y,z,c) res(x,c,z,y) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yxzc",4)) {
+        res.assign(_height,_width,_depth,_spectrum);
+        cimg_forXYZC(*this,x,y,z,c) res(y,x,z,c) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yxcz",4)) {
+        res.assign(_height,_width,_spectrum,_depth);
+        cimg_forXYZC(*this,x,y,z,c) res(y,x,c,z) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yzxc",4)) {
+        res.assign(_height,_depth,_width,_spectrum);
+        cimg_forXYZC(*this,x,y,z,c) res(y,z,x,c) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yzcx",4)) {
+        res.assign(_height,_depth,_spectrum,_width);
+        switch (_width) {
+        case 1 : {
+          t *ptr_r = res.data(0,0,0,0);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++);
+          }
+        } break;
+        case 2 : {
+          t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++); *(ptr_g++) = (t)*(ptrs++);
+          }
+        } break;
+        case 3 : { // Optimization for the classical conversion from interleaved RGB to planar RGB
+          t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), *ptr_b = res.data(0,0,0,2);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++); *(ptr_g++) = (t)*(ptrs++); *(ptr_b++) = (t)*(ptrs++);
+          }
+        } break;
+        case 4 : { // Optimization for the classical conversion from interleaved RGBA to planar RGBA
+          t *ptr_r = res.data(0,0,0,0), *ptr_g = res.data(0,0,0,1), *ptr_b = res.data(0,0,0,2), *ptr_a = res.data(0,0,0,3);
+          for (unsigned int siz = _height*_depth*_spectrum; siz; --siz) {
+            *(ptr_r++) = (t)*(ptrs++); *(ptr_g++) = (t)*(ptrs++); *(ptr_b++) = (t)*(ptrs++); *(ptr_a++) = (t)*(ptrs++);
+          }
+        } break;
+        default : {
+          cimg_forXYZC(*this,x,y,z,c) res(y,z,c,x) = *(ptrs++);
+          return res;
+        }
+        }
+      }
+      if (!cimg::strncasecmp(permut,"ycxz",4)) {
+        res.assign(_height,_spectrum,_width,_depth);
+        cimg_forXYZC(*this,x,y,z,c) res(y,c,x,z) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"yczx",4)) {
+        res.assign(_height,_spectrum,_depth,_width);
+        cimg_forXYZC(*this,x,y,z,c) res(y,c,z,x) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zxyc",4)) {
+        res.assign(_depth,_width,_height,_spectrum);
+        cimg_forXYZC(*this,x,y,z,c) res(z,x,y,c) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zxcy",4)) {
+        res.assign(_depth,_width,_spectrum,_height);
+        cimg_forXYZC(*this,x,y,z,c) res(z,x,c,y) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zyxc",4)) {
+        res.assign(_depth,_height,_width,_spectrum);
+        cimg_forXYZC(*this,x,y,z,c) res(z,y,x,c) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zycx",4)) {
+        res.assign(_depth,_height,_spectrum,_width);
+        cimg_forXYZC(*this,x,y,z,c) res(z,y,c,x) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zcxy",4)) {
+        res.assign(_depth,_spectrum,_width,_height);
+        cimg_forXYZC(*this,x,y,z,c) res(z,c,x,y) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"zcyx",4)) {
+        res.assign(_depth,_spectrum,_height,_width);
+        cimg_forXYZC(*this,x,y,z,c) res(z,c,y,x) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"cxyz",4)) {
+        res.assign(_spectrum,_width,_height,_depth);
+        switch (_spectrum) {
+        case 1 : {
+          const T *ptr_r = data(0,0,0,0);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++);
+          }
+        } break;
+        case 2 : {
+          const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++); *(ptrd++) = (t)*(ptr_g++);
+          }
+        } break;
+        case 3 : { // Optimization for the classical conversion from planar RGB to interleaved RGB
+          const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++); *(ptrd++) = (t)*(ptr_g++); *(ptrd++) = (t)*(ptr_b++);
+          }
+        } break;
+        case 4 : { // Optimization for the classical conversion from planar RGBA to interleaved RGBA
+          const T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3);
+          t *ptrd = res._data;
+          for (unsigned int siz = _width*_height*_depth; siz; --siz) {
+            *(ptrd++) = (t)*(ptr_r++); *(ptrd++) = (t)*(ptr_g++); *(ptrd++) = (t)*(ptr_b++); *(ptrd++) = (t)*(ptr_a++);
+          }
+        } break;
+        default : {
+          cimg_forXYZC(*this,x,y,z,c) res(c,x,y,z) = (t)*(ptrs++);
+        }
+        }
+      }
+      if (!cimg::strncasecmp(permut,"cxzy",4)) {
+        res.assign(_spectrum,_width,_depth,_height);
+        cimg_forXYZC(*this,x,y,z,c) res(c,x,z,y) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"cyxz",4)) {
+        res.assign(_spectrum,_height,_width,_depth);
+        cimg_forXYZC(*this,x,y,z,c) res(c,y,x,z) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"cyzx",4)) {
+        res.assign(_spectrum,_height,_depth,_width);
+        cimg_forXYZC(*this,x,y,z,c) res(c,y,z,x) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"czxy",4)) {
+        res.assign(_spectrum,_depth,_width,_height);
+        cimg_forXYZC(*this,x,y,z,c) res(c,z,x,y) = (t)*(ptrs++);
+      }
+      if (!cimg::strncasecmp(permut,"czyx",4)) {
+        res.assign(_spectrum,_depth,_height,_width);
+        cimg_forXYZC(*this,x,y,z,c) res(c,z,y,x) = (t)*(ptrs++);
+      }
+      if (!res)
+        throw CImgArgumentException(_cimg_instance
+                                    "permute_axes() : Invalid specified permutation '%s'.",
+                                    cimg_instance,
+                                    permut);
+      return res;
+    }
+
+    //! Permute axes order.
+    /**
+       This function permutes image axes.
+       \param permut = String describing the permutation (4 characters).
+    **/
+    CImg<T>& permute_axes(const char *const order) {
+      return get_permute_axes(order).move_to(*this);
+    }
+
+    CImg<T> get_permute_axes(const char *const order) const {
+      const T foo = (T)0;
+      return _get_permute_axes(order,foo);
+    }
+
+    //! Unroll all images values into specified axis.
+    CImg<T>& unroll(const char axis) {
+      const unsigned int siz = size();
+      if (siz) switch (axis) {
+      case 'x' : _width = siz; _height = _depth = _spectrum = 1; break;
+      case 'y' : _height = siz; _width = _depth = _spectrum = 1; break;
+      case 'z' : _depth = siz; _width = _height = _spectrum = 1; break;
+      default : _spectrum = siz; _width = _height = _depth = 1;
+      }
+      return *this;
+    }
+
+    CImg<T> get_unroll(const char axis) const {
+      return (+*this).unroll(axis);
+    }
+
+    //! Rotate an image.
+    /**
+       \param angle = rotation angle (in degrees).
+       \param cond = rotation type. can be :
+       - 0 = zero-value at borders
+       - 1 = nearest pixel.
+       - 2 = cyclic.
+       \note Returned image will probably have a different size than the instance image *this.
+    **/
+    CImg<T>& rotate(const float angle, const unsigned int border_conditions=0, const unsigned int interpolation=1) {
+      return get_rotate(angle,border_conditions,interpolation).move_to(*this);
+    }
+
+    CImg<T> get_rotate(const float angle, const unsigned int border_conditions=0, const unsigned int interpolation=1) const {
+      if (is_empty()) return *this;
+      CImg<T> res;
+      const float nangle = cimg::mod(angle,360.0f);
+      if (border_conditions!=1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
+        const int wm1 = width() - 1, hm1 = height() - 1;
+        const int iangle = (int)nangle/90;
+        switch (iangle) {
+        case 1 : {
+          res.assign(_height,_width,_depth,_spectrum);
+          T *ptrd = res._data;
+          cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(y,hm1-x,z,c);
+        } break;
+        case 2 : {
+          res.assign(_width,_height,_depth,_spectrum);
+          T *ptrd = res._data;
+          cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1-x,hm1-y,z,c);
+        } break;
+        case 3 : {
+          res.assign(_height,_width,_depth,_spectrum);
+          T *ptrd = res._data;
+          cimg_forXYZC(res,x,y,z,c) *(ptrd++) = (*this)(wm1-y,x,z,c);
+        } break;
+        default :
+          return *this;
+        }
+      } else { // generic version
+        const float
+          rad = (float)(nangle*cimg::PI/180.0),
+          ca = (float)std::cos(rad),
+          sa = (float)std::sin(rad),
+          ux = cimg::abs(_width*ca), uy = cimg::abs(_width*sa),
+          vx = cimg::abs(_height*sa), vy = cimg::abs(_height*ca),
+          w2 = 0.5f*_width, h2 = 0.5f*_height,
+          dw2 = 0.5f*(ux+vx), dh2 = 0.5f*(uy+vy);
+        res.assign((int)(ux+vx),(int)(uy+vy),_depth,_spectrum);
+        switch (border_conditions) {
+        case 0 : {
+          switch (interpolation) {
+          case 2 : {
+            T m, M = max_min(m);
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+              const Tfloat val = cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c,0);
+              res(x,y,z,c) = (T)(val<m?m:val>M?M:val);
+            }
+          } break;
+          case 1 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c,0);
+          } break;
+          default : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,c,0);
+          }
+          }
+        } break;
+        case 1 : {
+          switch (interpolation) {
+          case 2 : {
+            T m, M = max_min(m);
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+              const Tfloat val = _cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c);
+              res(x,y,z,c) = (T)(val<m?m:val>M?M:val);
+            }
+          } break;
+          case 1 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (T)_linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,c);
+          } break;
+          default : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = _atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,c);
+          }
+          }
+        } break;
+        case 2 : {
+          switch (interpolation) {
+          case 2 : {
+            T m, M = max_min(m);
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+              const Tfloat val = _cubic_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)width()),
+                                             cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)height()),z,c);
+              res(x,y,z,c) = (T)(val<m?m:val>M?M:val);
+            }
+          } break;
+          case 1 : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (T)_linear_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)width()),
+                                             cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)height()),z,c);
+          } break;
+          default : {
+            cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+              res(x,y,z,c) = (*this)(cimg::mod((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),width()),
+                                      cimg::mod((int)(h2 - (x-dw2)*sa + (y-dh2)*ca),height()),z,c);
+          }
+          }
+        } break;
+        default :
+          throw CImgArgumentException(_cimg_instance
+                                      "rotate() : Invalid specified border conditions %d "
+                                      "(should be { 0=dirichlet | 1=neumann | 2=cyclic }).",
+                                      cimg_instance,
+                                      border_conditions);
+        }
+      }
+      return res;
+    }
+
+    //! Rotate an image around a center point (\c cx,\c cy).
+    /**
+       \param angle = rotation angle (in degrees).
+       \param cx = X-coordinate of the rotation center.
+       \param cy = Y-coordinate of the rotation center.
+       \param zoom = zoom.
+       \param cond = rotation type. can be :
+       - 0 = zero-value at borders
+       - 1 = repeat image at borders
+       - 2 = zero-value at borders and linear interpolation
+    **/
+    CImg<T>& rotate(const float angle, const float cx, const float cy, const float zoom,
+                    const unsigned int border_conditions=3, const unsigned int interpolation=1) {
+      return get_rotate(angle,cx,cy,zoom,border_conditions,interpolation).move_to(*this);
+    }
+
+    CImg<T> get_rotate(const float angle, const float cx, const float cy, const float zoom,
+                       const unsigned int border_conditions=3, const unsigned int interpolation=1) const {
+      if (interpolation>2)
+        throw CImgArgumentException(_cimg_instance
+                                    "rotate() : Invalid specified interpolation %d "
+                                    "(should be { 0=none | 1=linear | 2=bicubic }).",
+                                    cimg_instance,
+                                    interpolation);
+
+      if (is_empty()) return *this;
+      CImg<T> res(_width,_height,_depth,_spectrum);
+      const float
+        rad = (float)((angle*cimg::PI)/180.0),
+        ca = (float)std::cos(rad)/zoom,
+        sa = (float)std::sin(rad)/zoom;
+      switch (border_conditions) {
+      case 0 : {
+        switch (interpolation) {
+        case 2 : {
+          T m, M = max_min(m);
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+            const Tfloat val = cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c,0);
+            res(x,y,z,c) = (T)(val<m?m:val>M?M:val);
+          }
+        } break;
+        case 1 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c,0);
+        } break;
+        default : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,c,0);
+        }
+        }
+      } break;
+      case 1 : {
+        switch (interpolation) {
+        case 2 : {
+          T m, M = max_min(m);
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+            const Tfloat val = _cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c);
+            res(x,y,z,c) = (T)(val<m?m:val>M?M:val);
+          }
+        } break;
+        case 1 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (T)_linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,c);
+        } break;
+        default : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = _atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,c);
+        }
+        }
+      } break;
+      case 2 : {
+        switch (interpolation) {
+        case 2 : {
+          T m, M = max_min(m);
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c) {
+            const Tfloat val = _cubic_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)width()),
+                                          cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)height()),z,c);
+            res(x,y,z,c) = (T)(val<m?m:val>M?M:val);
+          }
+        } break;
+        case 1 : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (T)_linear_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)width()),
+                                           cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)height()),z,c);
+        } break;
+        default : {
+          cimg_forXY(res,x,y) cimg_forZC(*this,z,c)
+            res(x,y,z,c) = (*this)(cimg::mod((int)(cx + (x-cx)*ca + (y-cy)*sa),width()),
+                                    cimg::mod((int)(cy - (x-cx)*sa + (y-cy)*ca),height()),z,c);
+        }
+        }
+      } break;
+      default :
+        throw CImgArgumentException(_cimg_instance
+                                    "rotate() : Invalid specified border conditions %d "
+                                    "(should be { 0=dirichlet | 1=neumann | 2=cyclic }).",
+                                    cimg_instance,
+                                    border_conditions);
+      }
+      return res;
+    }
+
+    //! Warp an image.
+    template<typename t>
+    CImg<T>& warp(const CImg<t>& warp, const bool relative=false,
+                  const bool interpolation=true, const unsigned int border_conditions=0) {
+      return get_warp(warp,relative,interpolation,border_conditions).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<T> get_warp(const CImg<t>& warp, const bool relative=false,
+                     const bool interpolation=true, const unsigned int border_conditions=0) const {
+      if (is_empty() || !warp) return *this;
+      if (relative && !is_sameXYZ(warp))
+        throw CImgArgumentException(_cimg_instance
+                                    "warp() : Instance and specified relative warping field (%u,%u,%u,%u,%p) "
+                                    "have different XYZ dimensions.",
+                                    cimg_instance,
+                                    warp._width,warp._height,warp._depth,warp._spectrum,warp._data);
+
+      CImg<T> res(warp._width,warp._height,warp._depth,_spectrum);
+      T *ptrd = res._data;
+      switch (warp._spectrum) {
+      case 1 : // 1d warping.
+        if (relative) { // Relative warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atX(cimg::mod(x - (float)*(ptrs0++),(float)_width),y,z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atX(x-(float)*(ptrs0++),y,z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atX(x-(float)*(ptrs0++),y,z,c,0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod(x - (int)*(ptrs0++),(int)_width),y,z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+              *(ptrd++) = _atX(x-(int)*(ptrs0++),y,z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atX(x-(int)*(ptrs0++),y,z,c,0);
+            }
+          }
+          }
+        } else { // Absolute warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atX(cimg::mod((float)*(ptrs0++),(float)_width),y,z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atX((float)*(ptrs0++),y,z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atX((float)*(ptrs0++),y,z,c,0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod((int)*(ptrs0++),(int)_width),y,z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = _atX((int)*(ptrs0++),y,z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp._data;
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atX((int)*(ptrs0++),y,z,c,0);
+            }
+          }
+          }
+        }
+        break;
+
+      case 2 : // 2d warping
+        if (relative) { // Relative warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXY(cimg::mod(x-(float)*(ptrs0++),(float)_width),
+                                            cimg::mod(y-(float)*(ptrs1++),(float)_height),z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXY(x-(float)*(ptrs0++),y-(float)*(ptrs1++),z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atXY(x-(float)*(ptrs0++),y-(float)*(ptrs1++),z,c,0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod(x-(int)*(ptrs0++),(int)_width),
+                                    cimg::mod(y-(int)*(ptrs1++),(int)_height),z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = _atXY(x-(int)*(ptrs0++),y-(int)*(ptrs1++),z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atXY(x-(int)*(ptrs0++),y-(int)*(ptrs1++),z,c,0);
+            }
+          }
+          }
+        } else { // Absolute warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXY(cimg::mod((float)*(ptrs0++),(float)_width),
+                                            cimg::mod((float)*(ptrs1++),(float)_height),z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),z,c,0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod((int)*(ptrs0++),(int)_width),
+                                    cimg::mod((int)*(ptrs1++),(int)_height),z,c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = _atXY((int)*(ptrs0++),(int)*(ptrs1++),z,c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atXY((int)*(ptrs0++),(int)*(ptrs1++),z,c,0);
+            }
+          }
+          }
+        }
+        break;
+
+      case 3 : // 3d warping
+        if (relative) { // Relative warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZ(cimg::mod(x-(float)*(ptrs0++),(float)_width),
+                                             cimg::mod(y-(float)*(ptrs1++),(float)_height),
+                                             cimg::mod(z-(float)*(ptrs2++),(float)_depth),c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZ(x-(float)*(ptrs0++),y-(float)*(ptrs1++),z-(float)*(ptrs2++),c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atXYZ(x-(float)*(ptrs0++),y-(float)*(ptrs1++),z-(float)*(ptrs2++),c,0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod(x-(int)*(ptrs0++),(int)_width),
+                                    cimg::mod(y-(int)*(ptrs1++),(int)_height),
+                                    cimg::mod(z-(int)*(ptrs2++),(int)_depth),c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = _atXYZ(x-(int)*(ptrs0++),y-(int)*(ptrs1++),z-(int)*(ptrs2++),c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atXYZ(x-(int)*(ptrs0++),y-(int)*(ptrs1++),z-(int)*(ptrs2++),c,0);
+            }
+          }
+          }
+        } else { // Absolute warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZ(cimg::mod((float)*(ptrs0++),(float)_width),
+                                             cimg::mod((float)*(ptrs1++),(float)_height),
+                                             cimg::mod((float)*(ptrs2++),(float)_depth),c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c,0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod((int)*(ptrs0++),(int)_width),
+                                    cimg::mod((int)*(ptrs1++),(int)_height),
+                                    cimg::mod((int)*(ptrs2++),(int)_depth),c);
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = _atXYZ((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),c);
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atXYZ((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),c,0);
+            }
+          }
+          }
+        }
+        break;
+
+      default : // 4d warping
+        if (relative) { // Relative warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZC(cimg::mod(x-(float)*(ptrs0++),(float)_width),
+                                              cimg::mod(y-(float)*(ptrs1++),(float)_height),
+                                              cimg::mod(z-(float)*(ptrs2++),(float)_depth),
+                                              cimg::mod(c-(float)*(ptrs3++),(float)_spectrum));
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZC(x-(float)*(ptrs0++),y-(float)*(ptrs1++),z-(float)*(ptrs2++),c-(float)*(ptrs3++));
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atXYZC(x-(float)*(ptrs0++),y-(float)*(ptrs1++),z-(float)*(ptrs2++),c-(float)*(ptrs3++),0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod(x-(int)*(ptrs0++),(int)_width),
+                                    cimg::mod(y-(int)*(ptrs1++),(int)_height),
+                                    cimg::mod(z-(int)*(ptrs2++),(int)_depth),
+                                    cimg::mod(c-(int)*(ptrs3++),(int)_spectrum));
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = _atXYZC(x-(int)*(ptrs0++),y-(int)*(ptrs1++),z-(int)*(ptrs2++),c-(int)*(ptrs3++));
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atXYZ(x-(int)*(ptrs0++),y-(int)*(ptrs1++),z-(int)*(ptrs2++),c-(int)*(ptrs3++),0);
+            }
+          }
+          }
+        } else { // Absolute warp coordinates
+          if (interpolation) switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZC(cimg::mod((float)*(ptrs0++),(float)_width),
+                                              cimg::mod((float)*(ptrs1++),(float)_height),
+                                              cimg::mod((float)*(ptrs2++),(float)_depth),
+                                              cimg::mod((float)*(ptrs3++),(float)_spectrum));
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)_linear_atXYZC((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),(float)*(ptrs3++));
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (T)linear_atXYZC((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),(float)*(ptrs3++),0);
+            }
+          }
+          } else switch (border_conditions) {
+          case 2 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = (*this)(cimg::mod((int)*(ptrs0++),(int)_width),
+                                    cimg::mod((int)*(ptrs1++),(int)_height),
+                                    cimg::mod((int)*(ptrs2++),(int)_depth),
+                                    cimg::mod((int)*(ptrs3++),(int)_spectrum));
+            }
+          } break;
+          case 1 : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = _atXYZC((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),(int)*(ptrs3++));
+            }
+          } break;
+          default : {
+            cimg_forC(res,c) {
+              const t *ptrs0 = warp.data(0,0,0,0), *ptrs1 = warp.data(0,0,0,1), *ptrs2 = warp.data(0,0,0,2), *ptrs3 = warp.data(0,0,0,3);
+              cimg_forXYZ(res,x,y,z)
+                *(ptrd++) = atXYZC((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),(int)*(ptrs3++),0);
+            }
+          }
+          }
+        }
+      }
+      return res;
+    }
+
+    //! Return a 2d representation of a 3d image, with three slices.
+    CImg<T>& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
+                           const int dx=-100, const int dy=-100, const int dz=-100) {
+      if (depth()<2) return *this;
+      return get_projections2d(x0,y0,z0,dx,dy,dz).move_to(*this);
+    }
+
+    CImg<T> get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
+                              const int dx=-100, const int dy=-100, const int dz=-100) const {
+      if (is_empty() || depth()<2) return *this;
+      const unsigned int
+        nx0 = (x0>=_width)?_width - 1:x0,
+        ny0 = (y0>=_height)?_height - 1:y0,
+        nz0 = (z0>=_depth)?_depth - 1:z0;
+      CImg<T>
+        imgxy(_width,_height,1,_spectrum),
+        imgzy(_depth,_height,1,_spectrum),
+        imgxz(_width,_depth,1,_spectrum);
+      cimg_forXYC(*this,x,y,c) imgxy(x,y,c) = (*this)(x,y,nz0,c);
+      cimg_forYZC(*this,y,z,c) imgzy(z,y,c) = (*this)(nx0,y,z,c);
+      cimg_forXZC(*this,x,z,c) imgxz(x,z,c) = (*this)(x,ny0,z,c);
+      imgxy.resize(dx,dy,1,_spectrum,1);
+      imgzy.resize(dz,dy,1,_spectrum,1);
+      imgxz.resize(dx,dz,1,_spectrum,1);
+      return CImg<T>(imgxy._width + imgzy._width,imgxy._height + imgxz._height,1,_spectrum,cimg::min(imgxy.min(),imgzy.min(),imgxz.min())).
+        draw_image(imgxy).draw_image(imgxy._width,imgzy).draw_image(0,imgxy._height,imgxz);
+    }
+
+    //! Get a square region of the image.
+    /**
+       \param x0 = X-coordinate of the upper-left crop rectangle corner.
+       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
+       \param z0 = Z-coordinate of the upper-left crop rectangle corner.
+       \param c0 = C-coordinate of the upper-left crop rectangle corner.
+       \param x1 = X-coordinate of the lower-right crop rectangle corner.
+       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
+       \param z1 = Z-coordinate of the lower-right crop rectangle corner.
+       \param c1 = C-coordinate of the lower-right crop rectangle corner.
+       \param border_condition = Dirichlet (false) or Neumann border conditions.
+    **/
+    CImg<T>& crop(const int x0, const int y0, const int z0, const int c0,
+                  const int x1, const int y1, const int z1, const int c1,
+                  const bool border_condition=false) {
+      return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,border_condition).move_to(*this);
+    }
+
+    CImg<T> get_crop(const int x0, const int y0, const int z0, const int c0,
+                     const int x1, const int y1, const int z1, const int c1,
+                     const bool border_condition=false) const {
+      if (is_empty()) 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,
+        nc0 = c0<c1?c0:c1, nc1 = c0^c1^nc0;
+      CImg<T> res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0);
+      if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum()) {
+        if (border_condition) cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _atXYZC(nx0+x,ny0+y,nz0+z,nc0+c);
+        else res.fill(0).draw_image(-nx0,-ny0,-nz0,-nc0,*this);
+      } else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this);
+      return res;
+    }
+
+    //! Get a rectangular part of the instance image.
+    /**
+       \param x0 = X-coordinate of the upper-left crop rectangle corner.
+       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
+       \param z0 = Z-coordinate of the upper-left crop rectangle corner.
+       \param x1 = X-coordinate of the lower-right crop rectangle corner.
+       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
+       \param z1 = Z-coordinate of the lower-right crop rectangle corner.
+       \param border_condition = determine the type of border condition if
+       some of the desired region is outside the image.
+    **/
+    CImg<T>& crop(const int x0, const int y0, const int z0,
+                  const int x1, const int y1, const int z1,
+                  const bool border_condition=false) {
+      return crop(x0,y0,z0,0,x1,y1,z1,_spectrum-1,border_condition);
+    }
+
+    CImg<T> get_crop(const int x0, const int y0, const int z0,
+                     const int x1, const int y1, const int z1,
+                     const bool border_condition=false) const {
+      return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum-1,border_condition);
+    }
+
+    //! Get a rectangular part of the instance image.
+    /**
+       \param x0 = X-coordinate of the upper-left crop rectangle corner.
+       \param y0 = Y-coordinate of the upper-left crop rectangle corner.
+       \param x1 = X-coordinate of the lower-right crop rectangle corner.
+       \param y1 = Y-coordinate of the lower-right crop rectangle corner.
+       \param border_condition = determine the type of border condition if
+       some of the desired region is outside the image.
+    **/
+    CImg<T>& crop(const int x0, const int y0,
+                  const int x1, const int y1,
+                  const bool border_condition=false) {
+      return crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,border_condition);
+    }
+
+    CImg<T> get_crop(const int x0, const int y0,
+                     const int x1, const int y1,
+                     const bool border_condition=false) const {
+      return get_crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,border_condition);
+    }
+
+    //! Get a rectangular part of the instance image.
+    /**
+       \param x0 = X-coordinate of the upper-left crop rectangle corner.
+       \param x1 = X-coordinate of the lower-right crop rectangle corner.
+       \param border_condition = determine the type of border condition if
+       some of the desired region is outside the image.
+    **/
+    CImg<T>& crop(const int x0, const int x1, const bool border_condition=false) {
+      return crop(x0,0,0,0,x1,_height-1,_depth-1,_spectrum-1,border_condition);
+    }
+
+    CImg<T> get_crop(const int x0, const int x1, const bool border_condition=false) const {
+      return get_crop(x0,0,0,0,x1,_height-1,_depth-1,_spectrum-1,border_condition);
+    }
+
+    //! Autocrop an image, regarding of the specified backround value.
+    CImg<T>& autocrop(const T value, const char *const axes="czyx") {
+      if (is_empty()) return *this;
+      for (const char *s = axes; *s; ++s) {
+        const char axis = cimg::uncase(*s);
+        const CImg<intT> coords = _autocrop(value,axis);
+        switch (axis) {
+        case 'x' : {
+          const int x0 = coords[0], x1 = coords[1];
+          if (x0>=0 && x1>=0) crop(x0,x1);
+        } break;
+        case 'y' : {
+          const int y0 = coords[0], y1 = coords[1];
+          if (y0>=0 && y1>=0) crop(0,y0,_width-1,y1);
+        } break;
+        case 'z' : {
+          const int z0 = coords[0], z1 = coords[1];
+          if (z0>=0 && z1>=0) crop(0,0,z0,_width-1,_height-1,z1);
+        } break;
+        default : {
+          const int c0 = coords[0], c1 = coords[1];
+          if (c0>=0 && c1>=0) crop(0,0,0,c0,_width-1,_height-1,_depth-1,c1);
+        }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_autocrop(const T value, const char *const axes="czyx") const {
+      return (+*this).autocrop(value,axes);
+    }
+
+    //! Autocrop an image, regarding of the specified backround color.
+    CImg<T>& autocrop(const T *const color, const char *const axes="zyx") {
+      if (is_empty()) return *this;
+      for (const char *s = axes; *s; ++s) {
+        const char axis = cimg::uncase(*s);
+        switch (axis) {
+        case 'x' : {
+          int x0 = _width, x1 = -1;
+          cimg_forC(*this,c) {
+            const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'x');
+            const int nx0 = coords[0], nx1 = coords[1];
+            if (nx0>=0 && nx1>=0) { x0 = cimg::min(x0,nx0); x1 = cimg::max(x1,nx1); }
+          }
+          if (x0<=x1) crop(x0,x1);
+        } break;
+        case 'y' : {
+          int y0 = _height, y1 = -1;
+          cimg_forC(*this,c) {
+            const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'y');
+            const int ny0 = coords[0], ny1 = coords[1];
+            if (ny0>=0 && ny1>=0) { y0 = cimg::min(y0,ny0); y1 = cimg::max(y1,ny1); }
+          }
+          if (y0<=y1) crop(0,y0,_width-1,y1);
+        } break;
+        default : {
+          int z0 = _depth, z1 = -1;
+          cimg_forC(*this,c) {
+            const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'z');
+            const int nz0 = coords[0], nz1 = coords[1];
+            if (nz0>=0 && nz1>=0) { z0 = cimg::min(z0,nz0); z1 = cimg::max(z1,nz1); }
+          }
+          if (z0<=z1) crop(0,0,z0,_width-1,_height-1,z1);
+        }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_autocrop(const T *const color, const char *const axes="zyx") const {
+      return (+*this).autocrop(color,axes);
+    }
+
+    //! Autocrop an image, regarding of the specified backround color.
+    template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char *const axes="zyx") {
+      return get_autocrop(color,axes).move_to(*this);
+    }
+
+    template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char *const axes="zyx") const {
+      return get_autocrop(color._data,axes);
+    }
+
+    CImg<intT> _autocrop(const T value, const char axis) const {
+      CImg<intT> res;
+      int x0 = -1, y0 = -1, z0 = -1, c0 = -1, x1 = -1, y1 = -1, z1 = -1, c1 = -1;
+      switch (cimg::uncase(axis)) {
+      case 'x' : {
+        cimg_forX(*this,x) cimg_forYZC(*this,y,z,c)
+          if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (x0>=0) {
+          for (int x = width()-1; x>=0; --x) cimg_forYZC(*this,y,z,c)
+            if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); }
+        }
+        res = CImg<intT>::vector(x0,x1);
+      } break;
+      case 'y' : {
+        cimg_forY(*this,y) cimg_forXZC(*this,x,z,c)
+          if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (y0>=0) {
+          for (int y = height()-1; y>=0; --y) cimg_forXZC(*this,x,z,c)
+            if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); }
+        }
+        res = CImg<intT>::vector(y0,y1);
+      } break;
+      case 'z' : {
+        cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c)
+          if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (z0>=0) {
+          for (int z = depth()-1; z>=0; --z) cimg_forXYC(*this,x,y,c)
+            if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); }
+        }
+        res = CImg<intT>::vector(z0,z1);
+      } break;
+      default : {
+        cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z)
+          if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); }
+        if (c0>=0) {
+          for (int c = spectrum()-1; c>=0; --c) cimg_forXYZ(*this,x,y,z)
+            if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; }
+        }
+        res = CImg<intT>::vector(c0,c1);
+      }
+      }
+      return res;
+    }
+
+    //! Get one column.
+    CImg<T>& column(const unsigned int x0) {
+      return columns(x0,x0);
+    }
+
+    CImg<T> get_column(const unsigned int x0) const {
+      return get_columns(x0,x0);
+    }
+
+    //! Get a set of columns.
+    CImg<T>& columns(const unsigned int x0, const unsigned int x1) {
+      return get_columns(x0,x1).move_to(*this);
+    }
+
+    CImg<T> get_columns(const unsigned int x0, const unsigned int x1) const {
+      return get_crop((int)x0,0,0,0,(int)x1,height()-1,depth()-1,spectrum()-1);
+    }
+
+    //! Get a line.
+    CImg<T>& line(const unsigned int y0) {
+      return lines(y0,y0);
+    }
+
+    CImg<T> get_line(const unsigned int y0) const {
+      return get_lines(y0,y0);
+    }
+
+    //! Get a set of lines.
+    CImg<T>& lines(const unsigned int y0, const unsigned int y1) {
+      return get_lines(y0,y1).move_to(*this);
+    }
+
+    CImg<T> get_lines(const unsigned int y0, const unsigned int y1) const {
+      return get_crop(0,(int)y0,0,0,width()-1,(int)y1,depth()-1,spectrum()-1);
+    }
+
+    //! Get a slice.
+    CImg<T>& slice(const unsigned int z0) {
+      return slices(z0,z0);
+    }
+
+    CImg<T> get_slice(const unsigned int z0) const {
+      return get_slices(z0,z0);
+    }
+
+    //! Get a set of slices.
+    CImg<T>& slices(const unsigned int z0, const unsigned int z1) {
+      return get_slices(z0,z1).move_to(*this);
+    }
+
+    CImg<T> get_slices(const unsigned int z0, const unsigned int z1) const {
+      return get_crop(0,0,(int)z0,0,width()-1,height()-1,(int)z1,spectrum()-1);
+    }
+
+    //! Get a channel.
+    CImg<T>& channel(const unsigned int c0) {
+      return channels(c0,c0);
+    }
+
+    CImg<T> get_channel(const unsigned int c0) const {
+      return get_channels(c0,c0);
+    }
+
+    //! Get a set of channels.
+    CImg<T>& channels(const unsigned int c0, const unsigned int c1) {
+      return get_channels(c0,c1).move_to(*this);
+    }
+
+    CImg<T> get_channels(const unsigned int c0, const unsigned int c1) const {
+      return get_crop(0,0,0,(int)c0,width()-1,height()-1,depth()-1,(int)c1);
+    }
+
+    //! 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 c0=0) {
+      const unsigned int beg = (unsigned int)offset(x0,y0,z0,c0), end = offset(x1,y0,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_points() : Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).",
+                                    cimg_instance,
+                                    x0,x1,y0,z0,c0);
+
+      return CImg<T>(_data+beg,x1-x0+1,1,1,1,true);
+    }
+
+    const CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
+                                    const unsigned int y0=0, const unsigned int z0=0, const unsigned int c0=0) const {
+      const unsigned int beg = (unsigned int)offset(x0,y0,z0,c0), end = offset(x1,y0,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_points() : Invalid request of a shared-memory subset (%u->%u,%u,%u,%u).",
+                                    cimg_instance,
+                                    x0,x1,y0,z0,c0);
+
+      return CImg<T>(_data+beg,x1-x0+1,1,1,1,true);
+    }
+
+    //! Return a shared-memory image referencing a set of lines of the instance image.
+    CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
+                             const unsigned int z0=0, const unsigned int c0=0) {
+      const unsigned int beg = offset(0,y0,z0,c0), end = offset(0,y1,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_lines() : Invalid request of a shared-memory subset (0->%u,%u->%u,%u,%u).",
+                                    cimg_instance,
+                                    _width-1,y0,y1,z0,c0);
+
+      return CImg<T>(_data+beg,_width,y1-y0+1,1,1,true);
+    }
+
+    const CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
+                                   const unsigned int z0=0, const unsigned int c0=0) const {
+      const unsigned int beg = offset(0,y0,z0,c0), end = offset(0,y1,z0,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_lines() : Invalid request of a shared-memory subset (0->%u,%u->%u,%u,%u).",
+                                    cimg_instance,
+                                    _width-1,y0,y1,z0,c0);
+
+      return CImg<T>(_data+beg,_width,y1-y0+1,1,1,true);
+    }
+
+    //! Return a shared-memory image referencing one particular line (y0,z0,c0) of the instance image.
+    CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) {
+      return get_shared_lines(y0,y0,z0,c0);
+    }
+
+    const CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const {
+      return get_shared_lines(y0,y0,z0,c0);
+    }
+
+    //! Return a shared memory image referencing a set of planes (z0->z1,c0) of the instance image.
+    CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) {
+      const unsigned int beg = offset(0,0,z0,c0), end = offset(0,0,z1,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_planes() : Invalid request of a shared-memory subset (0->%u,0->%u,%u->%u,%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,z0,z1,c0);
+
+      return CImg<T>(_data+beg,_width,_height,z1-z0+1,1,true);
+    }
+
+    const CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) const {
+      const unsigned int beg = offset(0,0,z0,c0), end = offset(0,0,z1,c0);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_planes() : Invalid request of a shared-memory subset (0->%u,0->%u,%u->%u,%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,z0,z1,c0);
+
+      return CImg<T>(_data+beg,_width,_height,z1-z0+1,1,true);
+    }
+
+    //! Return a shared-memory image referencing one plane (z0,c0) of the instance image.
+    CImg<T> get_shared_plane(const unsigned int z0, const unsigned int c0=0) {
+      return get_shared_planes(z0,z0,c0);
+    }
+
+    const CImg<T> get_shared_plane(const unsigned int z0, const unsigned int c0=0) const {
+      return get_shared_planes(z0,z0,c0);
+    }
+
+    //! Return a shared-memory image referencing a set of channels (c0->c1) of the instance image.
+    CImg<T> get_shared_channels(const unsigned int c0, const unsigned int c1) {
+      const unsigned int beg = offset(0,0,0,c0), end = offset(0,0,0,c1);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_channels() : Invalid request of a shared-memory subset (0->%u,0->%u,0->%u,%u->%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,_depth-1,c0,c1);
+
+      return CImg<T>(_data+beg,_width,_height,_depth,c1-c0+1,true);
+    }
+
+    const CImg<T> get_shared_channels(const unsigned int c0, const unsigned int c1) const {
+      const unsigned int beg = offset(0,0,0,c0), end = offset(0,0,0,c1);
+      if (beg>end || beg>=size() || end>=size())
+        throw CImgArgumentException(_cimg_instance
+                                    "get_shared_channels() : Invalid request of a shared-memory subset (0->%u,0->%u,0->%u,%u->%u).",
+                                    cimg_instance,
+                                    _width-1,_height-1,_depth-1,c0,c1);
+
+      return CImg<T>(_data+beg,_width,_height,_depth,c1-c0+1,true);
+    }
+
+    //! Return a shared-memory image referencing one channel c0 of the instance image.
+    CImg<T> get_shared_channel(const unsigned int c0) {
+      return get_shared_channels(c0,c0);
+    }
+
+    const CImg<T> get_shared_channel(const unsigned int c0) const {
+      return get_shared_channels(c0,c0);
+    }
+
+    //! Return a shared version of the instance image.
+    CImg<T> get_shared() {
+      return CImg<T>(_data,_width,_height,_depth,_spectrum,true);
+    }
+
+    const CImg<T> get_shared() const {
+      return CImg<T>(_data,_width,_height,_depth,_spectrum,true);
+    }
+
+    //! Split image into a list.
+    CImgList<T> get_split(const char axis, const int nb=0) const {
+      CImgList<T> res;
+      const char naxis = cimg::uncase(axis);
+      const unsigned int nnb = (unsigned int)(nb==0?1:(nb>0?nb:-nb));
+      if (nb<=0) switch (naxis) { // Split by bloc size
+      case 'x': {
+        for (unsigned int p = 0; p<_width; p+=nnb) get_crop(p,0,0,0,cimg::min(p+nnb-1,_width-1),_height-1,_depth-1,_spectrum-1).move_to(res);
+      } break;
+      case 'y': {
+        for (unsigned int p = 0; p<_height; p+=nnb) get_crop(0,p,0,0,_width-1,cimg::min(p+nnb-1,_height-1),_depth-1,_spectrum-1).move_to(res);
+      } break;
+      case 'z': {
+        for (unsigned int p = 0; p<_depth; p+=nnb) get_crop(0,0,p,0,_width-1,_height-1,cimg::min(p+nnb-1,_depth-1),_spectrum-1).move_to(res);
+      } break;
+      default: {
+        for (unsigned int p = 0; p<_spectrum; p+=nnb) get_crop(0,0,0,p,_width-1,_height-1,_depth-1,cimg::min(p+nnb-1,_spectrum-1)).move_to(res);
+      }
+      } else { // Split by number of blocs
+        const unsigned int siz = naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:naxis=='c'?_spectrum:0;
+        if (nnb>siz)
+          throw CImgArgumentException(_cimg_instance
+                                      "get_split() : Instance cannot be split along %c-axis into %u blocs.",
+                                      cimg_instance,
+                                      axis,nnb);
+        res.assign(nnb);
+        switch (naxis) {
+        case 'x' : {
+          cimglist_for(res,p) get_crop(p*siz/nnb,0,0,0,(p+1)*siz/nnb-1,_height-1,_depth-1,_spectrum-1).move_to(res[p]);
+        } break;
+        case 'y' : {
+          cimglist_for(res,p) get_crop(0,p*siz/nnb,0,0,_width-1,(p+1)*siz/nnb-1,_depth-1,_spectrum-1).move_to(res[p]);
+        } break;
+        case 'z' : {
+          cimglist_for(res,p) get_crop(0,0,p*siz/nnb,0,_width-1,_height-1,(p+1)*siz/nnb-1,_spectrum-1).move_to(res[p]);
+        } break;
+        default : {
+          cimglist_for(res,p) get_crop(0,0,0,p*siz/nnb,_width-1,_height-1,_depth-1,(p+1)*siz/nnb-1).move_to(res[p]);
+        }
+        }
+      }
+      return res;
+    }
+
+    // Split image into a list of vectors, according to a given splitting value.
+    CImgList<T> get_split(const T value, const bool keep_values, const bool shared, const char axis='y') const {
+      CImgList<T> res;
+      const T *ptr0 = _data, *const ptre = _data + size();
+      while (ptr0<ptre) {
+        const T *ptr1 = ptr0;
+        while (ptr1<ptre && *ptr1==value) ++ptr1;
+        const unsigned int siz0 = ptr1 - ptr0;
+        if (siz0 && keep_values) res.insert(CImg<T>(ptr0,1,siz0,1,1,shared));
+        ptr0 = ptr1;
+        while (ptr1<ptre && *ptr1!=value) ++ptr1;
+        const unsigned int siz1 = ptr1 - ptr0;
+        if (siz1) res.insert(CImg<T>(ptr0,1,siz1,1,1,shared),~0U,shared);
+        ptr0 = ptr1;
+      }
+      cimglist_apply(res,unroll)(axis);
+      return res;
+    }
+
+    //! Append an image.
+    template<typename t>
+    CImg<T>& append(const CImg<t>& img, const char axis='x', const char align='p') {
+      if (is_empty()) return assign(img,false);
+      if (!img) return *this;
+      return CImgList<T>(*this,img).get_append(axis,align).move_to(*this);
+    }
+
+    CImg<T>& append(const CImg<T>& img, const char axis='x', const char align='p') {
+      if (is_empty()) return assign(img,false);
+      if (!img) return *this;
+      return CImgList<T>(*this,img,true).get_append(axis,align).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_append(const CImg<T>& img, const char axis='x', const char align='p') const {
+      if (is_empty()) return +img;
+      if (!img) return +*this;
+      return CImgList<_cimg_Tt>(*this,img).get_append(axis,align);
+    }
+
+    CImg<T> get_append(const CImg<T>& img, const char axis='x', const char align='p') const {
+      if (is_empty()) return +img;
+      if (!img) return +*this;
+      return CImgList<T>(*this,img,true).get_append(axis,align);
+    }
+
+    //@}
+    //---------------------------------------
+    //
+    //! \name Filtering / Transforms
+    //@{
+    //---------------------------------------
+
+    //! Compute the correlation of the instance image by a mask.
+    /**
+       The correlation of the instance image \p *this by the mask \p mask is defined to be :
+
+       res(x,y,z) = sum_{i,j,k} (*this)(x+i,y+j,z+k)*mask(i,j,k)
+
+       \param mask = the correlation kernel.
+       \param cond = the border condition type (0=zero, 1=dirichlet)
+       \param weighted_correl = enable local normalization.
+    **/
+    template<typename t>
+    CImg<T>& correlate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_correl=false) {
+      return get_correlate(mask,cond,weighted_correl).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Ttfloat> get_correlate(const CImg<t>& mask, const unsigned int cond=1,
+                                      const bool weighted_correl=false) const {
+      if (!mask || mask._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "correlate() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
+                                    cimg_instance,
+                                    mask._width,mask._height,mask._depth,mask._spectrum,mask._data);
+
+      if (is_empty()) return *this;
+      typedef _cimg_Ttfloat Ttfloat;
+      CImg<Ttfloat> res(_width,_height,_depth,_spectrum);
+      Ttfloat *ptrd = res._data;
+      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_forZC(*this,z,c) cimg_for3x3x3(*this,x,y,z,c,I,T) *(ptrd++) = (Ttfloat)
+            (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] +
+             I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
+             I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] +
+             I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
+             I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
+             I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
+             I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] +
+             I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
+             I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26]);
+          ptrd = res._data;
+          if (weighted_correl) cimg_forZC(*this,z,c) cimg_for3x3x3(*this,x,y,z,c,I,T) {
+            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) *ptrd/=(Ttfloat)std::sqrt(weight);
+            ++ptrd;
+          }
+        } break;
+        case 2 : {
+          T I[8] = { 0 };
+          cimg_forZC(*this,z,c) cimg_for2x2x2(*this,x,y,z,c,I,T) *(ptrd++) = (Ttfloat)
+            (I[0]*mask[0] + I[1]*mask[1] +
+             I[2]*mask[2] + I[3]*mask[3] +
+             I[4]*mask[4] + I[5]*mask[5] +
+             I[6]*mask[6] + I[7]*mask[7]);
+          ptrd = res._data;
+          if (weighted_correl) cimg_forZC(*this,z,c) cimg_for2x2x2(*this,x,y,z,c,I,T) {
+            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) *ptrd/=(Ttfloat)std::sqrt(weight);
+            ++ptrd;
+          }
+        } break;
+        default :
+        case 1 :
+          switch (mask._width) {
+          case 6 : {
+            T I[36] = { 0 };
+            cimg_forZC(*this,z,c) cimg_for6x6(*this,x,y,z,c,I,T) *(ptrd++) = (Ttfloat)
+              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
+               I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
+               I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
+               I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
+               I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26] + I[27]*mask[27] + I[28]*mask[28] + I[29]*mask[29] +
+               I[30]*mask[30] + I[31]*mask[31] + I[32]*mask[32] + I[33]*mask[33] + I[34]*mask[34] + I[35]*mask[35]);
+            ptrd = res._data;
+            if (weighted_correl) cimg_forZC(*this,z,c) cimg_for6x6(*this,x,y,z,c,I,T) {
+              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) *ptrd/=(Ttfloat)std::sqrt(weight);
+              ++ptrd;
+            }
+          } break;
+          case 5 : {
+            T I[25] = { 0 };
+            cimg_forZC(*this,z,c) cimg_for5x5(*this,x,y,z,c,I,T) *(ptrd++) = (Ttfloat)
+              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] +
+               I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] +
+               I[10]*mask[10] + I[11]*mask[11] + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
+               I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + I[18]*mask[18] + I[19]*mask[19] +
+               I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + I[24]*mask[24]);
+            ptrd = res._data;
+            if (weighted_correl) cimg_forZC(*this,z,c) cimg_for5x5(*this,x,y,z,c,I,T) {
+              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) *ptrd/=(Ttfloat)std::sqrt(weight);
+              ++ptrd;
+            }
+          } break;
+          case 4 : {
+            T I[16] = { 0 };
+            cimg_forZC(*this,z,c) cimg_for4x4(*this,x,y,z,c,I,T) *(ptrd++) = (Ttfloat)
+              (I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] +
+               I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] +
+               I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
+               I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15]);
+            ptrd = res._data;
+            if (weighted_correl) cimg_forZC(*this,z,c) cimg_for4x4(*this,x,y,z,c,I,T) {
+              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) *ptrd/=(Ttfloat)std::sqrt(weight);
+              ++ptrd;
+            }
+          } break;
+          case 3 : {
+            T I[9] = { 0 };
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T) *(ptrd++) = (Ttfloat)
+              (I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
+               I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
+               I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
+            ptrd = res._data;
+            if (weighted_correl) cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,T) {
+              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) *ptrd/=(Ttfloat)std::sqrt(weight);
+              ++ptrd;
+            }
+          } break;
+          case 2 : {
+            T I[4] = { 0 };
+            cimg_forZC(*this,z,c) cimg_for2x2(*this,x,y,z,c,I,T) *(ptrd++) = (Ttfloat)
+              (I[0]*mask[0] + I[1]*mask[1] +
+               I[2]*mask[2] + I[3]*mask[3]);
+            ptrd = res._data;
+            if (weighted_correl) cimg_forZC(*this,z,c) cimg_for2x2(*this,x,y,z,c,I,T) {
+              const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
+                                             I[2]*I[2] + I[3]*I[3]);
+              if (weight>0) *ptrd/=(Ttfloat)std::sqrt(weight);
+              ++ptrd;
+            }
+          } break;
+          case 1 : (res.assign(*this))*=mask(0); break;
+          }
+        }
+      } else { // Generic version for other masks
+        const int
+          mx2 = mask.width()/2, my2 = mask.height()/2, mz2 = mask.depth()/2,
+          mx1 = mx2 - 1 + (mask.width()%2), my1 = my2 - 1 + (mask.height()%2), mz1 = mz2 - 1 + (mask.depth()%2),
+          mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+        cimg_forC(*this,c)
+          if (!weighted_correl) { // Classical correlation
+            for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+              Ttfloat val = 0;
+              for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
+                val+=(*this)(x+xm,y+ym,z+zm,c)*mask(mx1+xm,my1+ym,mz1+zm);
+              res(x,y,z,c) = (Ttfloat)val;
+            }
+            if (cond)
+              cimg_forYZC(*this,y,z,c)
+                for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                  Ttfloat val = 0;
+                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
+                    val+=_atXYZ(x+xm,y+ym,z+zm,c)*mask(mx1+xm,my1+ym,mz1+zm);
+                  res(x,y,z,c) = (Ttfloat)val;
+                }
+            else
+              cimg_forYZC(*this,y,z,c)
+                for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                  Ttfloat val = 0;
+                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
+                    val+=atXYZ(x+xm,y+ym,z+zm,c,0)*mask(mx1+xm,my1+ym,mz1+zm);
+                  res(x,y,z,c) = (Ttfloat)val;
+                }
+          } else { // Weighted correlation
+            for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+              Ttfloat val = 0, weight = 0;
+              for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                const Ttfloat cval = (Ttfloat)(*this)(x+xm,y+ym,z+zm,c);
+                val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
+                weight+=cval*cval;
+              }
+              res(x,y,z,c) = (weight>(Ttfloat)0)?(Ttfloat)(val/std::sqrt((double)weight)):(Ttfloat)0;
+            }
+            if (cond)
+              cimg_forYZC(*this,y,z,c)
+                for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                  Ttfloat val = 0, weight = 0;
+                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                    const Ttfloat cval = (Ttfloat)_atXYZ(x+xm,y+ym,z+zm,c);
+                    val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
+                    weight+=cval*cval;
+                  }
+                  res(x,y,z,c) = (weight>(Ttfloat)0)?(Ttfloat)(val/std::sqrt((double)weight)):(Ttfloat)0;
+                }
+            else
+              cimg_forYZC(*this,y,z,c)
+                for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                  Ttfloat val = 0, weight = 0;
+                  for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                    const Ttfloat cval = (Ttfloat)atXYZ(x+xm,y+ym,z+zm,c,0);
+                    val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
+                    weight+=cval*cval;
+                  }
+                  res(x,y,z,c) = (weight>(Ttfloat)0)?(Ttfloat)(val/std::sqrt((double)weight)):(Ttfloat)0;
+                }
+          }
+      }
+      return res;
+    }
+
+    //! Compute the convolution of the image by a mask.
+    /**
+       The result \p res of the convolution of an image \p img by a mask \p mask is defined to be :
+
+       res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*mask(i,j,k)
+
+       \param mask = the correlation kernel.
+       \param cond = the border condition type (0=zero, 1=dirichlet)
+       \param weighted_convol = enable local normalization.
+    **/
+    template<typename t>
+    CImg<T>& convolve(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_convol=false) {
+      return get_convolve(mask,cond,weighted_convol).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Ttfloat> get_convolve(const CImg<t>& mask, const unsigned int cond=1,
+                                     const bool weighted_convol=false) const {
+      if (!mask || mask._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "convolve() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
+                                    cimg_instance,
+                                    mask._width,mask._height,mask._depth,mask._spectrum,mask._data);
+
+      if (is_empty()) return *this;
+      return get_correlate(CImg<t>(mask._data,mask.size(),1,1,1,true).get_mirror('x').resize(mask,-1),cond,weighted_convol);
+    }
+
+    //! Return the erosion of the image by a structuring element.
+    template<typename t>
+    CImg<T>& erode(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_erosion=false) {
+      return get_erode(mask,cond,weighted_erosion).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_erode(const CImg<t>& mask, const unsigned int cond=1,
+                             const bool weighted_erosion=false) const {
+      if (!mask || mask._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "erode() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
+                                    cimg_instance,
+                                    mask._width,mask._height,mask._depth,mask._spectrum,mask._data);
+
+      if (is_empty()) return *this;
+      typedef _cimg_Tt Tt;
+      CImg<Tt> res(_width,_height,_depth,_spectrum);
+      const int
+        mx2 = mask.width()/2, my2 = mask.height()/2, mz2 = mask.depth()/2,
+        mx1 = mx2 - 1 + (mask.width()%2), my1 = my2 - 1 + (mask.height()%2), mz1 = mz2 - 1 + (mask.depth()%2),
+        mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+      cimg_forC(*this,c)
+        if (!weighted_erosion) { // Classical erosion
+          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+            Tt min_val = cimg::type<Tt>::max();
+            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+              const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,c);
+              if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+            }
+            res(x,y,z,c) = min_val;
+          }
+          if (cond)
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt min_val = cimg::type<Tt>::max();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,c);
+                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+                }
+                res(x,y,z,c) = min_val;
+              }
+          else
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt min_val = cimg::type<Tt>::max();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,c,0);
+                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
+                }
+                res(x,y,z,c) = min_val;
+              }
+        } else { // Weighted erosion
+          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+            Tt min_val = cimg::type<Tt>::max();
+            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+              const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+              const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,c) + mval);
+              if (mval && cval<min_val) min_val = cval;
+            }
+            res(x,y,z,c) = min_val;
+          }
+          if (cond)
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt min_val = cimg::type<Tt>::max();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+                  const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,c) + mval);
+                  if (mval && cval<min_val) min_val = cval;
+                }
+                res(x,y,z,c) = min_val;
+              }
+          else
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt min_val = cimg::type<Tt>::max();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+                  const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,c,0) + mval);
+                  if (mval && cval<min_val) min_val = cval;
+                }
+                res(x,y,z,c) = min_val;
+              }
+        }
+      return res;
+    }
+
+    //! Erode the image by a rectangular structuring element of size sx,sy,sz.
+    CImg<T>& erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) {
+      if (sx>1 && _width>1) { // Along X-axis.
+        const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forYZC(*this,y,z,c) {
+          const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val<=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval<cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval<cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val<=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val<cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val<cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(_width-1,y,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sy>1 && _height>1) { // Along Y-axis.
+        const int L = height(), off = width(), s = (int)sy, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXZC(*this,x,z,c) {
+          const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val<=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval<cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval<cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val<=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val<cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val<cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,_height-1,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sz>1 && _depth>1) { // Along Z-axis.
+        const int L = depth(), off = width()*height(), s = (int)sz, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXYC(*this,x,y,c) {
+          const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val<=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val<=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval<cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval<cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val<=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val<cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val<cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,y,_depth-1,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const {
+      return (+*this).erode(sx,sy,sz);
+    }
+
+    //! Erode the image by a square structuring element of size sx.
+    CImg<T>& erode(const unsigned int s) {
+      return erode(s,s,s);
+    }
+
+    CImg<T> get_erode(const unsigned int s) const {
+      return (+*this).erode(s);
+    }
+
+    //! Dilate the image by a structuring element.
+    template<typename t>
+    CImg<T>& dilate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_dilatation=false) {
+      return get_dilate(mask,cond,weighted_dilatation).move_to(*this);
+    }
+
+    template<typename t>
+    CImg<_cimg_Tt> get_dilate(const CImg<t>& mask, const unsigned int cond=1,
+                              const bool weighted_dilatation=false) const {
+      if (!mask || mask._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "dilate() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
+                                    cimg_instance,
+                                    mask._width,mask._height,mask._depth,mask._spectrum,mask._data);
+
+      if (is_empty()) return *this;
+      typedef _cimg_Tt Tt;
+      CImg<Tt> res(_width,_height,_depth,_spectrum);
+      const int
+        mx2 = mask.width()/2, my2 = mask.height()/2, mz2 = mask.depth()/2,
+        mx1 = mx2 - 1 + (mask.width()%2), my1 = my2 - 1 + (mask.height()%2), mz1 = mz2 - 1 + (mask.depth()%2),
+        mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+      cimg_forC(*this,c)
+        if (!weighted_dilatation) { // Classical dilatation
+          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+            Tt max_val = cimg::type<Tt>::min();
+            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+              const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,c);
+              if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+            }
+            res(x,y,z,c) = max_val;
+          }
+          if (cond)
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt max_val = cimg::type<Tt>::min();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,c);
+                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+                }
+                res(x,y,z,c) = max_val;
+              }
+          else
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt max_val = cimg::type<Tt>::min();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,c,0);
+                  if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
+                }
+                res(x,y,z,c) = max_val;
+              }
+        } else { // Weighted dilatation
+          for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
+            Tt max_val = cimg::type<Tt>::min();
+            for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+              const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+              const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,c) - mval);
+              if (mval && cval>max_val) max_val = cval;
+            }
+            res(x,y,z,c) = max_val;
+          }
+          if (cond)
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt max_val = cimg::type<Tt>::min();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+                  const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,c) - mval);
+                  if (mval && cval>max_val) max_val = cval;
+                }
+                res(x,y,z,c) = max_val;
+              }
+          else
+            cimg_forYZC(*this,y,z,c)
+              for (int x = 0; x<width(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
+                Tt max_val = cimg::type<Tt>::min();
+                for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
+                  const t mval = mask(mx1+xm,my1+ym,mz1+zm);
+                  const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,c,0) - mval);
+                  if (mval && cval>max_val) max_val = cval;
+                }
+                res(x,y,z,c) = max_val;
+              }
+        }
+      return res;
+    }
+
+    //! Dilate the image by a rectangular structuring element of size sx,sy,sz.
+    CImg<T>& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) {
+      if (sx>1 && _width>1) { // Along X-axis.
+        const int L = width(), off = 1, s = (int)sx, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forYZC(*this,y,z,c) {
+          const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val>=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(_width-1,y,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sy>1 && _height>1) { // Along Y-axis.
+        const int L = height(), off = width(), s = (int)sy, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXZC(*this,x,z,c) {
+          const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val>=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,_height-1,z,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+
+      if (sz>1 && _depth>1) { // Along Z-axis.
+        const int L = depth(), off = width()*height(), s = (int)sz, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+        CImg<T> buf(L);
+        T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+        cimg_forXYC(*this,x,y,c) {
+          const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+          ptrd = buf._data; T cur = *ptrs; ptrs+=off; bool is_first = true;
+          for (int p = s2-1; p>0 && ptrs<=ptrse; --p) { const T val = *ptrs; ptrs+=off; if (val>=cur) { cur = val; is_first = false; }} *(ptrd++) = cur;
+          for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs<ptrse) ptrs+=off; if (val>=cur) { cur = val; is_first = false; } *(ptrd++) = cur; }
+          for (int p = L - s - 1; p>0; --p) {
+            const T val = *ptrs; ptrs+=off;
+            if (is_first) {
+              const T *nptrs = ptrs - off; cur = val;
+              for (int q = s - 2; q>0; --q) { nptrs-=off; const T nval = *nptrs; if (nval>cur) cur = nval; }
+              nptrs-=off; const T nval = *nptrs; if (nval>cur) { cur = nval; is_first = true; } else is_first = false;
+            } else { if (val>=cur) cur = val; else if (cur==*(ptrs-s*off)) is_first = true; }
+            *(ptrd++) = cur;
+          }
+          ptrd = ptrde; ptrs = ptrse; cur = *ptrs; ptrs-=off;
+          for (int p = s1; p>0 && ptrs>=ptrsb; --p) { const T val = *ptrs; ptrs-=off; if (val>cur) cur = val; } *(ptrd--) = cur;
+          for (int p = s2-1; p>0 && ptrd>=ptrdb; --p) { const T val = *ptrs; if (ptrs>ptrsb) ptrs-=off; if (val>cur) cur = val; *(ptrd--) = cur; }
+          T *pd = data(x,y,_depth-1,c) + off; cimg_for(buf,ps,T) { pd-=off; *pd = *ps; }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const {
+      return (+*this).dilate(sx,sy,sz);
+    }
+
+    //! Erode the image by a square structuring element of size sx.
+    CImg<T>& dilate(const unsigned int s) {
+      return dilate(s,s,s);
+    }
+
+    CImg<T> get_dilate(const unsigned int s) const {
+      return (+*this).dilate(s);
+    }
+
+    //! Compute the watershed transform, from an instance image of non-zero labels.
+    template<typename t>
+    CImg<T>& watershed(const CImg<t>& priority, const bool fill_lines=true) {
+      if (is_empty()) return *this;
+      if (!is_sameXYZ(priority))
+        throw CImgArgumentException(_cimg_instance
+                                    "watershed() : Instance image and specified priority (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,priority._width,priority._height,priority._depth,priority._spectrum,priority._data);
+      if (_spectrum!=1) { cimg_forC(*this,c) get_shared_channel(c).watershed(priority.get_shared_channel(c%priority._spectrum),fill_lines); return *this; }
+
+      CImg<boolT> in_queue(_width,_height,_depth,1,0);
+      CImg<typename cimg::superset2<T,t,int>::type> Q;
+      unsigned int sizeQ = 0;
+
+      // Find seed points and insert them in priority queue.
+      const T *ptrs = _data;
+      cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) {
+        if (x-1>=0 && !(*this)(x-1,y,z))       Q._watershed_insert(in_queue,sizeQ,priority(x-1,y,z),x-1,y,z);
+        if (x+1<width() && !(*this)(x+1,y,z))  Q._watershed_insert(in_queue,sizeQ,priority(x+1,y,z),x+1,y,z);
+        if (y-1>=0 && !(*this)(x,y-1,z))       Q._watershed_insert(in_queue,sizeQ,priority(x,y-1,z),x,y-1,z);
+        if (y+1<height() && !(*this)(x,y+1,z)) Q._watershed_insert(in_queue,sizeQ,priority(x,y+1,z),x,y+1,z);
+        if (z-1>=0 && !(*this)(x,y,z-1))       Q._watershed_insert(in_queue,sizeQ,priority(x,y,z-1),x,y,z-1);
+        if (z+1<depth() && !(*this)(x,y,z+1))  Q._watershed_insert(in_queue,sizeQ,priority(x,y,z+1),x,y,z+1);
+      }
+
+      // Start watershed computation.
+      while (sizeQ) {
+
+        // Get and remove point with minimal priority from the queue.
+        const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3);
+        Q._watershed_remove(sizeQ);
+
+        // Check labels of the neighbors.
+        bool is_same_label = true;
+        unsigned int label = 0;
+        if (x-1>=0) {
+          if ((*this)(x-1,y,z)) { if (!label) label = (*this)(x-1,y,z); else if (label!=(*this)(x-1,y,z)) is_same_label = false; }
+          else Q._watershed_insert(in_queue,sizeQ,priority(x-1,y,z),x-1,y,z);
+        }
+        if (x+1<width()) {
+          if ((*this)(x+1,y,z)) { if (!label) label = (*this)(x+1,y,z); else if (label!=(*this)(x+1,y,z)) is_same_label = false; }
+          else Q._watershed_insert(in_queue,sizeQ,priority(x+1,y,z),x+1,y,z);
+        }
+        if (y-1>=0) {
+          if ((*this)(x,y-1,z)) { if (!label) label = (*this)(x,y-1,z); else if (label!=(*this)(x,y-1,z)) is_same_label = false; }
+          else Q._watershed_insert(in_queue,sizeQ,priority(x,y-1,z),x,y-1,z);
+        }
+        if (y+1<height()) {
+          if ((*this)(x,y+1,z)) { if (!label) label = (*this)(x,y+1,z); else if (label!=(*this)(x,y+1,z)) is_same_label = false; }
+          else Q._watershed_insert(in_queue,sizeQ,priority(x,y+1,z),x,y+1,z);
+        }
+        if (z-1>=0) {
+          if ((*this)(x,y,z-1)) { if (!label) label = (*this)(x,y,z-1); else if (label!=(*this)(x,y,z-1)) is_same_label = false; }
+          else Q._watershed_insert(in_queue,sizeQ,priority(x,y,z-1),x,y,z-1);
+        }
+        if (z+1<depth()) {
+          if ((*this)(x,y,z+1)) { if (!label) label = (*this)(x,y,z+1); else if (label!=(*this)(x,y,z+1)) is_same_label = false; }
+          else Q._watershed_insert(in_queue,sizeQ,priority(x,y,z+1),x,y,z+1);
+        }
+        if (is_same_label) (*this)(x,y,z) = label;
+      }
+
+      // Fill lines.
+      if (fill_lines) {
+
+        // Sort all non-labeled pixels with labeled neighbors.
+        in_queue = false;
+        const T *ptrs = _data;
+        cimg_forXYZ(*this,x,y,z) if (!*(ptrs++) &&
+                                     ((x-1>=0 && (*this)(x-1,y,z)) || (x+1<width() && (*this)(x+1,y,z)) ||
+                                      (y-1>=0 && (*this)(x,y-1,z)) || (y+1<height() && (*this)(x,y+1,z)) ||
+                                      (z-1>=0 && (*this)(x,y,z-1)) || (z+1>depth() && (*this)(x,y,z+1))))
+          Q._watershed_insert(in_queue,sizeQ,priority(x,y,z),x,y,z);
+
+        // Start line filling process.
+        while (sizeQ) {
+          const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3);
+          Q._watershed_remove(sizeQ);
+          t pmax = cimg::type<t>::min();
+          int xmax = 0, ymax = 0, zmax = 0;
+          if (x-1>=0) {
+            if ((*this)(x-1,y,z)) { if (priority(x-1,y,z)>pmax) { pmax = priority(x-1,y,z); xmax = x-1; ymax = y; zmax = z; }}
+            else Q._watershed_insert(in_queue,sizeQ,priority(x-1,y,z),x-1,y,z);
+          }
+          if (x+1<width()) {
+            if ((*this)(x+1,y,z)) { if (priority(x+1,y,z)>pmax) { pmax = priority(x+1,y,z); xmax = x+1; ymax = y; zmax = z; }}
+            else Q._watershed_insert(in_queue,sizeQ,priority(x+1,y,z),x+1,y,z);
+          }
+          if (y-1>=0) {
+            if ((*this)(x,y-1,z)) { if (priority(x,y-1,z)>pmax) { pmax = priority(x,y-1,z); xmax = x; ymax = y-1; zmax = z; }}
+            else Q._watershed_insert(in_queue,sizeQ,priority(x,y-1,z),x,y-1,z);
+          }
+          if (y+1<height()) {
+            if ((*this)(x,y+1,z)) { if (priority(x,y+1,z)>pmax) { pmax = priority(x,y+1,z); xmax = x; ymax = y+1; zmax = z; }}
+            else Q._watershed_insert(in_queue,sizeQ,priority(x,y+1,z),x,y+1,z);
+          }
+          if (z-1>=0) {
+            if ((*this)(x,y,z-1)) { if (priority(x,y,z-1)>pmax) { pmax = priority(x,y,z-1); xmax = x; ymax = y; zmax = z-1; }}
+            else Q._watershed_insert(in_queue,sizeQ,priority(x,y,z-1),x,y,z-1);
+          }
+          if (z+1<depth()) {
+            if ((*this)(x,y,z+1)) { if (priority(x,y,z+1)>pmax) { pmax = priority(x,y,z+1); xmax = x; ymax = y; zmax = z+1; }}
+            else Q._watershed_insert(in_queue,sizeQ,priority(x,y,z+1),x,y,z+1);
+          }
+          (*this)(x,y,z) = (*this)(xmax,ymax,zmax);
+        }
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<T> get_watershed(const CImg<t>& priority, const bool fill_lines=true) const {
+      return (+*this).watershed(priority,fill_lines);
+    }
+
+    // Insert/Remove items in priority queue, for watershed transform.
+    template<typename t>
+    CImg<T>& _watershed_insert(CImg<boolT>& in_queue, unsigned int& siz, const t value, const unsigned int x, const unsigned int y, const unsigned int z) {
+      if (in_queue(x,y,z)) return *this;
+      in_queue(x,y,z) = true;
+      if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); }
+      (*this)(siz-1,0) = (T)value; (*this)(siz-1,1) = (T)x; (*this)(siz-1,2) = (T)y; (*this)(siz-1,3) = (T)z;
+      for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos+1)/2-1,0); pos = par) {
+        cimg::swap((*this)(pos,0),(*this)(par,0)); cimg::swap((*this)(pos,1),(*this)(par,1));
+        cimg::swap((*this)(pos,2),(*this)(par,2)); cimg::swap((*this)(pos,3),(*this)(par,3));
+      }
+      return *this;
+    }
+
+    CImg<T>& _watershed_remove(unsigned int& siz) {
+      (*this)(0,0) = (*this)(--siz,0); (*this)(0,1) = (*this)(siz,1); (*this)(0,2) = (*this)(siz,2); (*this)(0,3) = (*this)(siz,3);
+      const float value = (*this)(0,0);
+      for (unsigned int pos = 0, left = 0, right = 0;
+           ((right=2*(pos+1),(left=right-1))<siz && value<(*this)(left,0)) || (right<siz && value<(*this)(right,0));) {
+        if (right<siz) {
+          if ((*this)(left,0)>(*this)(right,0)) {
+            cimg::swap((*this)(pos,0),(*this)(left,0)); cimg::swap((*this)(pos,1),(*this)(left,1));
+            cimg::swap((*this)(pos,2),(*this)(left,2)); cimg::swap((*this)(pos,3),(*this)(left,3));
+            pos = left;
+          } else {
+            cimg::swap((*this)(pos,0),(*this)(right,0)); cimg::swap((*this)(pos,1),(*this)(right,1));
+            cimg::swap((*this)(pos,2),(*this)(right,2)); cimg::swap((*this)(pos,3),(*this)(right,3));
+            pos = right;
+          }
+        } else {
+          cimg::swap((*this)(pos,0),(*this)(left,0)); cimg::swap((*this)(pos,1),(*this)(left,1));
+          cimg::swap((*this)(pos,2),(*this)(left,2)); cimg::swap((*this)(pos,3),(*this)(left,3));
+          pos = left;
+        }
+      }
+      return *this;
+    }
+
+    //! Compute the result of the Deriche filter.
+    /**
+       The Canny-Deriche filter is a recursive algorithm allowing to compute blurred derivatives of
+       order 0,1 or 2 of an image.
+    **/
+    CImg<T>& deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) {
+#define _cimg_deriche2_apply \
+  Tfloat *ptrY = Y._data, yb = 0, yp = 0; \
+  T xp = (T)0; \
+  if (cond) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \
+  for (int m = 0; m<N; ++m) { \
+    const T xc = *ptrX; ptrX+=off; \
+    const Tfloat yc = *(ptrY++) = (Tfloat)(a0*xc + a1*xp - b1*yp - b2*yb); \
+    xp = xc; yb = yp; yp = yc; \
+  } \
+  T xn = (T)0, xa = (T)0; \
+  Tfloat yn = 0, ya = 0; \
+  if (cond) { xn = xa = *(ptrX-off); yn = ya = (Tfloat)coefn*xn; } \
+  for (int n = N-1; n>=0; --n) { \
+    const T xc = *(ptrX-=off); \
+    const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \
+    xa = xn; xn = xc; ya = yn; yn = yc; \
+    *ptrX = (T)(*(--ptrY)+yc); \
+  }
+      const char naxis = cimg::uncase(axis);
+      const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100;
+      if (is_empty() || (nsigma<0.1 && !order)) return *this;
+      const float
+        nnsigma = nsigma<0.1f?0.1f:nsigma,
+        alpha = 1.695f/nnsigma,
+        ema = (float)std::exp(-alpha),
+        ema2 = (float)std::exp(-2*alpha),
+        b1 = -2*ema,
+        b2 = ema2;
+      float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0;
+      switch (order) {
+      case 0 : {
+        const float k = (1-ema)*(1-ema)/(1+2*alpha*ema-ema2);
+        a0 = k;
+        a1 = k*(alpha-1)*ema;
+        a2 = k*(alpha+1)*ema;
+        a3 = -k*ema2;
+      } break;
+      case 1 : {
+        const float k = (1-ema)*(1-ema)/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_instance
+                                    "deriche() : Invalid specified filter order %u "
+                                    "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).",
+                                    cimg_instance,
+                                    order);
+      }
+      coefp = (a0+a1)/(1+b1+b2);
+      coefn = (a2+a3)/(1+b1+b2);
+      switch (naxis) {
+      case 'x' : {
+        const int N = _width, off = 1;
+        CImg<Tfloat> Y(N);
+        cimg_forYZC(*this,y,z,c) { T *ptrX = data(0,y,z,c); _cimg_deriche2_apply; }
+      } break;
+      case 'y' : {
+        const int N = _height, off = _width;
+        CImg<Tfloat> Y(N);
+        cimg_forXZC(*this,x,z,c) { T *ptrX = data(x,0,z,c); _cimg_deriche2_apply; }
+      } break;
+      case 'z' : {
+        const int N = _depth, off = _width*_height;
+        CImg<Tfloat> Y(N);
+        cimg_forXYC(*this,x,y,c) { T *ptrX = data(x,y,0,c); _cimg_deriche2_apply; }
+      } break;
+      default : {
+        const int N = _spectrum, off = _width*_height*_depth;
+        CImg<Tfloat> Y(N);
+        cimg_forXYZ(*this,x,y,z) { T *ptrX = data(x,y,z,0); _cimg_deriche2_apply; }
+      }
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) const {
+      return CImg<Tfloat>(*this,false).deriche(sigma,order,axis,cond);
+    }
+
+    //! Return a blurred version of the image, using a Canny-Deriche filter.
+    /**
+       Blur the image with an anisotropic exponential filter (Deriche filter of order 0).
+    **/
+    CImg<T>& blur(const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) {
+      if (!is_empty()) {
+        if (_width>1) deriche(sigmax,0,'x',cond);
+        if (_height>1) deriche(sigmay,0,'y',cond);
+        if (_depth>1) deriche(sigmaz,0,'z',cond);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_blur(const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) const {
+      return CImg<Tfloat>(*this,false).blur(sigmax,sigmay,sigmaz,cond);
+    }
+
+    //! Return a blurred version of the image, using a Canny-Deriche filter.
+    CImg<T>& blur(const float sigma, const bool cond=true) {
+      const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100;
+      return blur(nsigma,nsigma,nsigma,cond);
+    }
+
+    CImg<Tfloat> get_blur(const float sigma, const bool cond=true) const {
+      return CImg<Tfloat>(*this,false).blur(sigma,cond);
+    }
+
+    //! Blur the image anisotropically following a field of diffusion tensors.
+    /**
+       \param G = Field of square roots of diffusion tensors/vectors used to drive the smoothing.
+       \param amplitude = amplitude of the smoothing.
+       \param dl = spatial discretization.
+       \param da = angular discretization.
+       \param gauss_prec = precision of the gaussian function.
+       \param interpolation Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
+       \param fast_approx = Tell to use the fast approximation or not.
+    **/
+    template<typename t>
+    CImg<T>& blur_anisotropic(const CImg<t>& G,
+                              const float amplitude=60, const float dl=0.8f, const float da=30,
+                              const float gauss_prec=2, const unsigned int interpolation_type=0,
+                              const bool fast_approx=1) {
+
+      // Check arguments and init variables
+      if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6))
+        throw CImgArgumentException(_cimg_instance
+                                    "blur_anisotropic() : Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    G._width,G._height,G._depth,G._spectrum,G._data);
+
+      if (is_empty() || amplitude<=0 || dl<0) return *this;
+      const bool is_threed = (G._spectrum==6);
+      T val_min, val_max = max_min(val_min);
+
+      if (da<=0) {  // Iterated oriented Laplacians
+        CImg<Tfloat> velocity(_width,_height,_depth,_spectrum);
+        for (unsigned int iteration = 0; iteration<(unsigned int)amplitude; ++iteration) {
+          Tfloat *ptrd = velocity._data, veloc_max = 0;
+          if (is_threed) { // 3d version
+            CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixx = Incc + Ipcc - 2*Iccc,
+                ixy = (Innc + Ippc - Inpc - Ipnc)/4,
+                ixz = (Incn + Ipcp - Incp - Ipcn)/4,
+                iyy = Icnc + Icpc - 2*Iccc,
+                iyz = (Icnn + Icpp - Icnp - Icpn)/4,
+                izz = Iccn + Iccp - 2*Iccc,
+                veloc = (Tfloat)(G(x,y,z,0)*ixx + 2*G(x,y,z,1)*ixy + 2*G(x,y,z,2)*ixz + G(x,y,z,3)*iyy + 2*G(x,y,z,4)*iyz + G(x,y,z,5)*izz);
+              *(ptrd++) = veloc;
+              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+            }
+          } else { // 2d version
+            CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixx = Inc + Ipc - 2*Icc,
+                ixy = (Inn + Ipp - Inp - Ipn)/4,
+                iyy = Icn + Icp - 2*Icc,
+                veloc = (Tfloat)(G(x,y,0,0)*ixx + 2*G(x,y,0,1)*ixy + G(x,y,0,2)*iyy);
+              *(ptrd++) = veloc;
+              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+            }
+          }
+          if (veloc_max>0) *this+=(velocity*=dl/veloc_max);
+        }
+      } else { // LIC-based smoothing.
+        const unsigned int whd = _width*_height*_depth;
+        const float sqrt2amplitude = (float)std::sqrt(2*amplitude);
+        const int dx1 = width() - 1, dy1 = height() - 1, dz1 = depth() - 1;
+        CImg<Tfloat> res(_width,_height,_depth,_spectrum,0), W(_width,_height,_depth,is_threed?4:3), val(_spectrum);
+        int N = 0;
+        if (is_threed) { // 3d version
+          for (float phi = (180%(int)da)/2.0f; phi<=180; phi+=da) {
+            const float phir = (float)(phi*cimg::PI/180), datmp = (float)(da/std::cos(phir)), da2 = datmp<1?360.0f:datmp;
+            for (float theta = 0; theta<360; (theta+=da2),++N) {
+              const float
+                thetar = (float)(theta*cimg::PI/180),
+                vx = (float)(std::cos(thetar)*std::cos(phir)),
+                vy = (float)(std::sin(thetar)*std::cos(phir)),
+                vz = (float)std::sin(phir);
+              const t
+                *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2),
+                *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5);
+              Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(0,0,0,3);
+              cimg_forXYZ(G,xg,yg,zg) {
+                const t a = *(pa++), b = *(pb++), c = *(pc++), d = *(pd++), e = *(pe++), f = *(pf++);
+                const float
+                  u = (float)(a*vx + b*vy + c*vz),
+                  v = (float)(b*vx + d*vy + e*vz),
+                  w = (float)(c*vx + e*vy + f*vz),
+                  n = (float)std::sqrt(1e-5+u*u+v*v+w*w),
+                  dln = dl/n;
+                *(pd0++) = (Tfloat)(u*dln);
+                *(pd1++) = (Tfloat)(v*dln);
+                *(pd2++) = (Tfloat)(w*dln);
+                *(pd3++) = (Tfloat)n;
+              }
+
+              Tfloat *ptrd = res._data;
+              cimg_forXYZ(*this,x,y,z) {
+                val.fill(0);
+                const float
+                  n = (float)W(x,y,z,3),
+                  fsigma = (float)(n*sqrt2amplitude),
+                  fsigma2 = 2*fsigma*fsigma,
+                  length = gauss_prec*fsigma;
+                float
+                  S = 0,
+                  X = (float)x,
+                  Y = (float)y,
+                  Z = (float)z;
+                switch (interpolation_type) {
+                case 0 : { // Nearest neighbor
+                  for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
+                    const int
+                      cx = (int)(X+0.5f),
+                      cy = (int)(Y+0.5f),
+                      cz = (int)(Z+0.5f);
+                    const float
+                      u = (float)W(cx,cy,cz,0),
+                      v = (float)W(cx,cy,cz,1),
+                      w = (float)W(cx,cy,cz,2);
+                    if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,cz,c); ++S; }
+                    else {
+                      const float coef = (float)std::exp(-l*l/fsigma2);
+                      cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,cz,c));
+                      S+=coef;
+                    }
+                    X+=u; Y+=v; Z+=w;
+                  }
+                } break;
+                case 1 : { // Linear interpolation
+                  for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
+                    const float
+                      u = (float)(W._linear_atXYZ(X,Y,Z,0)),
+                      v = (float)(W._linear_atXYZ(X,Y,Z,1)),
+                      w = (float)(W._linear_atXYZ(X,Y,Z,2));
+                    if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; }
+                    else {
+                      const float coef = (float)std::exp(-l*l/fsigma2);
+                      cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c));
+                      S+=coef;
+                    }
+                    X+=u; Y+=v; Z+=w;
+                  }
+                } break;
+                default : { // 2nd order Runge Kutta
+                  for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
+                    const float
+                      u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)),
+                      v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)),
+                      w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2)),
+                      u = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,0)),
+                      v = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,1)),
+                      w = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,2));
+                    if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXYZ(X,Y,Z,c); ++S; }
+                    else {
+                      const float coef = (float)std::exp(-l*l/fsigma2);
+                      cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,c));
+                      S+=coef;
+                    }
+                    X+=u; Y+=v; Z+=w;
+                  }
+                } break;
+                }
+                Tfloat *_ptrd = ptrd++;
+                if (S>0) cimg_forC(res,c) { *_ptrd+=val[c]/S; _ptrd+=whd; }
+                else cimg_forC(res,c) { *_ptrd+=(Tfloat)((*this)(x,y,z,c)); _ptrd+=whd; }
+              }
+            }
+          }
+        } else { // 2d LIC algorithm
+          for (float theta = (360%(int)da)/2.0f; theta<360; (theta+=da),++N) {
+            const float thetar = (float)(theta*cimg::PI/180), vx = (float)(std::cos(thetar)), vy = (float)(std::sin(thetar));
+            const t *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2);
+            Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2);
+            cimg_forXY(G,xg,yg) {
+              const t a = *(pa++), b = *(pb++), c = *(pc++);
+              const float
+                u = (float)(a*vx + b*vy),
+                v = (float)(b*vx + c*vy),
+                n = (float)std::sqrt(1e-5+u*u+v*v),
+                dln = dl/n;
+              *(pd0++) = (Tfloat)(u*dln);
+              *(pd1++) = (Tfloat)(v*dln);
+              *(pd2++) = (Tfloat)n;
+            }
+            Tfloat *ptrd = res._data;
+            cimg_forXY(*this,x,y) {
+              val.fill(0);
+              const float
+                n = (float)W(x,y,0,2),
+                fsigma = (float)(n*sqrt2amplitude),
+                fsigma2 = 2*fsigma*fsigma,
+                length = gauss_prec*fsigma;
+              float
+                S = 0,
+                X = (float)x,
+                Y = (float)y;
+              switch (interpolation_type) {
+              case 0 : { // Nearest-neighbor
+                for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+                  const int
+                    cx = (int)(X+0.5f),
+                    cy = (int)(Y+0.5f);
+                  const float
+                    u = (float)W(cx,cy,0,0),
+                    v = (float)W(cx,cy,0,1);
+                  if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)(*this)(cx,cy,0,c); ++S; }
+                  else {
+                    const float coef = (float)std::exp(-l*l/fsigma2);
+                    cimg_forC(*this,c) val[c]+=(Tfloat)(coef*(*this)(cx,cy,0,c));
+                    S+=coef;
+                  }
+                  X+=u; Y+=v;
+                }
+              } break;
+              case 1 : { // Linear interpolation
+                for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+                  const float
+                    u = (float)(W._linear_atXY(X,Y,0,0)),
+                    v = (float)(W._linear_atXY(X,Y,0,1));
+                  if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; }
+                  else {
+                    const float coef = (float)std::exp(-l*l/fsigma2);
+                    cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c));
+                    S+=coef;
+                  }
+                  X+=u; Y+=v;
+                }
+              } break;
+              default : { // 2nd-order Runge-kutta interpolation
+                for (float l = 0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
+                  const float
+                    u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)),
+                    v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1)),
+                    u = (float)(W._linear_atXY(X+u0,Y+v0,0,0)),
+                    v = (float)(W._linear_atXY(X+u0,Y+v0,0,1));
+                  if (fast_approx) { cimg_forC(*this,c) val[c]+=(Tfloat)_linear_atXY(X,Y,0,c); ++S; }
+                  else {
+                    const float coef = (float)std::exp(-l*l/fsigma2);
+                    cimg_forC(*this,c) val[c]+=(Tfloat)(coef*_linear_atXY(X,Y,0,c));
+                    S+=coef;
+                  }
+                  X+=u; Y+=v;
+                }
+              }
+              }
+              Tfloat *_ptrd = ptrd++;
+              if (S>0) cimg_forC(res,c) { *_ptrd+=val[c]/S; _ptrd+=whd; }
+              else cimg_forC(res,c) { *_ptrd+=(Tfloat)((*this)(x,y,0,c)); _ptrd+=whd; }
+            }
+          }
+        }
+        const Tfloat *ptrs = res.end();
+        cimg_for(*this,ptrd,T) { const Tfloat val = *(--ptrs)/N; *ptrd = val<val_min?val_min:(val>val_max?val_max:(T)val); }
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImg<T> get_blur_anisotropic(const CImg<t>& G,
+                                 const float amplitude=60, const float dl=0.8f, const float da=30,
+                                 const float gauss_prec=2, const unsigned int interpolation_type=0,
+                                 const bool fast_approx=true) const {
+      return (+*this).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
+    }
+
+    //! Blur an image following in an anisotropic way.
+    CImg<T>& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f,
+                              const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30,
+                              const float gauss_prec=2, const unsigned int interpolation_type=0,
+                              const bool fast_approx=true) {
+      return blur_anisotropic(get_edge_tensors(sharpness,anisotropy,alpha,sigma,interpolation_type!=3),
+                              amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
+    }
+
+    CImg<T> get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.6f,
+                                 const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
+                                 const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0,
+                                 const bool fast_approx=true) const {
+      return (+*this).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx);
+    }
+
+    //! Blur an image using the bilateral filter.
+    /**
+       \param sigma_x Amount of blur along the X-axis.
+       \param sigma_y Amount of blur along the Y-axis.
+       \param sigma_z Amount of blur along the Z-axis.
+       \param sigma_r Amount of blur along the range axis.
+       \param bgrid_x Size of the bilateral grid along the X-axis.
+       \param bgrid_y Size of the bilateral grid along the Y-axis.
+       \param bgrid_z Size of the bilateral grid along the Z-axis.
+       \param bgrid_r Size of the bilateral grid along the range axis.
+       \param interpolation_type Use interpolation for image slicing.
+       \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006
+       (extended for 3d volumetric images).
+    **/
+    CImg<T>& blur_bilateral(const float sigma_x, const float sigma_y, const float sigma_z, const float sigma_r,
+                            const int bgrid_x, const int bgrid_y, const int bgrid_z, const int bgrid_r,
+                            const bool interpolation_type=true) {
+      if (is_empty()) return *this;
+      T m, M = max_min(m);
+      const float range = (float)(1.0f+M-m);
+      const unsigned int
+        bx0 = bgrid_x>=0?bgrid_x:_width*(-bgrid_x)/100,
+        by0 = bgrid_y>=0?bgrid_y:_height*(-bgrid_y)/100,
+        bz0 = bgrid_z>=0?bgrid_z:_depth*(-bgrid_z)/100,
+        br0 = bgrid_r>=0?bgrid_r:(int)(-range*bgrid_r/100),
+        bx = bx0>0?bx0:1,
+        by = by0>0?by0:1,
+        bz = bz0>0?bz0:1,
+        br = br0>0?br0:1;
+      const float
+        _sigma_x = sigma_x>=0?sigma_x:-sigma_x*_width/100,
+        _sigma_y = sigma_y>=0?sigma_y:-sigma_y*_height/100,
+        _sigma_z = sigma_z>=0?sigma_z:-sigma_z*_depth/100,
+        nsigma_x = _sigma_x*bx/_width,
+        nsigma_y = _sigma_y*by/_height,
+        nsigma_z = _sigma_z*bz/_depth,
+        nsigma_r = sigma_r*br/range;
+      if (nsigma_x>0 || nsigma_y>0 || nsigma_z>0 || nsigma_r>0) {
+        const bool is_threed = (_depth>1);
+        if (is_threed) { // 3d version of the algorithm
+          CImg<floatT> bgrid(bx,by,bz,br), bgridw(bx,by,bz,br);
+          cimg_forC(*this,c) {
+            bgrid.fill(0); bgridw.fill(0);
+            cimg_forXYZ(*this,x,y,z) {
+              const T val = (*this)(x,y,z,c);
+              const int X = x*bx/_width, Y = y*by/_height, Z = z*bz/_depth, R = (int)((val-m)*br/range);
+              bgrid(X,Y,Z,R) = (float)val;
+              bgridw(X,Y,Z,R) = 1;
+            }
+            bgrid.blur(nsigma_x,nsigma_y,nsigma_z,true).deriche(nsigma_r,0,'c',false);
+            bgridw.blur(nsigma_x,nsigma_y,nsigma_z,true).deriche(nsigma_r,0,'c',false);
+            if (interpolation_type) cimg_forXYZ(*this,x,y,z) {
+              const T val = (*this)(x,y,z,c);
+              const float X = (float)x*bx/_width, Y = (float)y*by/_height, Z = (float)z*bz/_depth, R = (float)((val-m)*br/range),
+                bval0 = bgrid._linear_atXYZC(X,Y,Z,R), bval1 = bgridw._linear_atXYZC(X,Y,Z,R);
+              (*this)(x,y,z,c) = (T)(bval0/bval1);
+            } else cimg_forXYZ(*this,x,y,z) {
+              const T val = (*this)(x,y,z,c);
+              const int X = x*bx/_width, Y = y*by/_height, Z = z*bz/_depth, R = (int)((val-m)*br/range);
+              const float bval0 = bgrid(X,Y,Z,R), bval1 = bgridw(X,Y,Z,R);
+              (*this)(x,y,z,c) = (T)(bval0/bval1);
+            }
+          }
+        } else { // 2d version of the algorithm
+          CImg<floatT> bgrid(bx,by,br,2);
+          cimg_forC(*this,c) {
+            bgrid.fill(0);
+            cimg_forXY(*this,x,y) {
+              const T val = (*this)(x,y,c);
+              const int X = x*bx/_width, Y = y*by/_height, R = (int)((val-m)*br/range);
+              bgrid(X,Y,R,0) = (float)val;
+              bgrid(X,Y,R,1) = 1;
+            }
+            bgrid.blur(nsigma_x,nsigma_y,0,true).blur(0,0,nsigma_r,false);
+            if (interpolation_type) cimg_forXY(*this,x,y) {
+              const T val = (*this)(x,y,c);
+              const float X = (float)x*bx/_width, Y = (float)y*by/_height, R = (float)((val-m)*br/range),
+                bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1);
+              (*this)(x,y,c) = (T)(bval0/bval1);
+            } else cimg_forXY(*this,x,y) {
+              const T val = (*this)(x,y,c);
+              const int X = x*bx/_width, Y = y*by/_height, R = (int)((val-m)*br/range);
+              const float bval0 = bgrid(X,Y,R,0), bval1 = bgrid(X,Y,R,1);
+              (*this)(x,y,c) = (T)(bval0/bval1);
+            }
+          }
+        }
+      }
+      return *this;
+    }
+
+    CImg<T> get_blur_bilateral(const float sigma_x, const float sigma_y, const float sigma_z, const float sigma_r,
+                               const int bgrid_x, const int bgrid_y, const int bgrid_z, const int bgrid_r,
+                               const bool interpolation_type=true) const {
+      return (+*this).blur_bilateral(sigma_x,sigma_y,sigma_z,sigma_r,bgrid_x,bgrid_y,bgrid_z,bgrid_r,interpolation_type);
+    }
+
+    //! Blur an image using the bilateral filter.
+    CImg<T>& blur_bilateral(const float sigma_s, const float sigma_r, const int bgrid_s=-33, const int bgrid_r=32,
+                            const bool interpolation_type=true) {
+      const float nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100;
+      return blur_bilateral(nsigma_s,nsigma_s,nsigma_s,sigma_r,bgrid_s,bgrid_s,bgrid_s,bgrid_r,interpolation_type);
+    }
+
+    CImg<T> get_blur_bilateral(const float sigma_s, const float sigma_r, const int bgrid_s=-33, const int bgrid_r=32,
+                               const bool interpolation_type=true) const {
+      return (+*this).blur_bilateral(sigma_s,sigma_s,sigma_s,sigma_r,bgrid_s,bgrid_s,bgrid_s,bgrid_r,interpolation_type);
+    }
+
+    //! Blur an image in its patch-based space.
+    CImg<T>& blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3,
+                        const unsigned int lookup_size=4, const float smoothness=0, const bool fast_approx=true) {
+      return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,fast_approx).move_to(*this);
+    }
+
+    CImg<T> get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3,
+                           const unsigned int lookup_size=4, const float smoothness=0, const bool fast_approx=true) const {
+
+#define _cimg_blur_patch3d_fast(N) \
+      cimg_for##N##XYZ(res,x,y,z) { \
+        T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \
+        const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \
+        float sum_weights = 0; \
+        cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (cimg::abs(img(x,y,z,0) - img(p,q,r,0))<sigma_p3) { \
+          T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \
+          float distance2 = 0; \
+          pQ = Q.end(); cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \
+          distance2/=Pnorm; \
+          const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \
+            alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = alldist>3?0.0f:1.0f; \
+          sum_weights+=weight; \
+          cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \
+        } \
+        if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \
+        else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \
+    }
+
+#define _cimg_blur_patch3d(N) \
+      cimg_for##N##XYZ(res,x,y,z) { \
+        T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,x,y,z,c,pP,T); pP+=N3; } \
+        const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \
+        float sum_weights = 0, weight_max = 0; \
+        cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) { \
+          T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N##x##N(img,p,q,r,c,pQ,T); pQ+=N3; } \
+          float distance2 = 0; \
+          pQ = Q.end(); cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \
+          distance2/=Pnorm; \
+          const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \
+            alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)std::exp(-alldist); \
+          if (weight>weight_max) weight_max = weight; \
+          sum_weights+=weight; \
+          cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \
+        } \
+        sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c); \
+        if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \
+        else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \
+      }
+
+#define _cimg_blur_patch2d_fast(N) \
+        cimg_for##N##XY(res,x,y) { \
+          T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \
+          float sum_weights = 0; \
+          cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (cimg::abs(img(x,y,0,0) - img(p,q,0,0))<sigma_p3) { \
+            T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \
+            float distance2 = 0; \
+            pQ = Q.end(); cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \
+            distance2/=Pnorm; \
+            const float dx = (float)p - x, dy = (float)q - y, \
+              alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = alldist>3?0.0f:1.0f; \
+            sum_weights+=weight; \
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \
+          } \
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \
+          else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \
+        }
+
+#define _cimg_blur_patch2d(N) \
+        cimg_for##N##XY(res,x,y) { \
+          T *pP = P._data; cimg_forC(res,c) { cimg_get##N##x##N(img,x,y,0,c,pP,T); pP+=N2; } \
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \
+          float sum_weights = 0, weight_max = 0; \
+          cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) { \
+            T *pQ = Q._data; cimg_forC(res,c) { cimg_get##N##x##N(img,p,q,0,c,pQ,T); pQ+=N2; } \
+            float distance2 = 0; \
+            pQ = Q.end(); cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \
+            distance2/=Pnorm; \
+            const float dx = (float)p - x, dy = (float)q - y, \
+              alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)std::exp(-alldist); \
+            if (weight>weight_max) weight_max = weight; \
+            sum_weights+=weight; \
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \
+          } \
+          sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \
+          else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \
+    }
+
+      CImg<Tfloat> res(_width,_height,_depth,_spectrum,0);
+      const CImg<T> _img = smoothness>0?get_blur(smoothness):CImg<Tfloat>(),&img = smoothness>0?_img:*this;
+      CImg<T> P(patch_size*patch_size*_spectrum), Q(P);
+      const float
+        nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100,
+        sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p,
+        Pnorm = P.size()*sigma_p2;
+      const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1;
+      const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size;
+      if (_depth>1) switch (patch_size) { // 3d
+      case 2 : if (fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break;
+      case 3 : if (fast_approx) _cimg_blur_patch3d_fast(3) else _cimg_blur_patch3d(3) break;
+      default : {
+        const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1;
+        if (fast_approx) cimg_forXYZ(res,x,y,z) { // Fast
+          P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true);
+          const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2;
+          float sum_weights = 0;
+          cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (cimg::abs(img(x,y,z,0)-img(p,q,r,0))<sigma_p3) {
+            (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,true))-=P;
+            const float
+              dx = (float)x - p, dy = (float)y - q, dz = (float)z - r,
+              distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2),
+              weight = distance2>3?0.0f:1.0f;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c);
+          }
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c));
+        } else cimg_forXYZ(res,x,y,z) { // Exact
+          P = img.get_crop(x - psize1,y - psize1,z - psize1,x + psize2,y + psize2,z + psize2,true);
+          const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2;
+          float sum_weights = 0, weight_max = 0;
+          cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) if (p!=x || q!=y || r!=z) {
+            (Q = img.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,true))-=P;
+            const float
+              dx = (float)x - p, dy = (float)y - q, dz = (float)z - r,
+              distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2),
+              weight = (float)std::exp(-distance2);
+            if (weight>weight_max) weight_max = weight;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c);
+          }
+          sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=weight_max*(*this)(x,y,z,c);
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c));
+        }
+      }
+      } else switch (patch_size) { // 2d
+      case 2 : if (fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break;
+      case 3 : if (fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break;
+      case 4 : if (fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break;
+      case 5 : if (fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break;
+      case 6 : if (fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break;
+      case 7 : if (fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break;
+      case 8 : if (fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break;
+      case 9 : if (fast_approx) _cimg_blur_patch2d_fast(9) else _cimg_blur_patch2d(9) break;
+      default : { // Fast
+        const int psize2 = (int)patch_size/2, psize1 = (int)patch_size - psize2 - 1;
+        if (fast_approx) cimg_forXY(res,x,y) { // 2d fast approximation.
+          P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true);
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2;
+          float sum_weights = 0;
+          cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (cimg::abs(img(x,y,0)-img(p,q,0))<sigma_p3) {
+            (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,true))-=P;
+            const float
+              dx = (float)x - p, dy = (float)y - q,
+              distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2),
+              weight = distance2>3?0.0f:1.0f;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c);
+          }
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c));
+        } else cimg_forXY(res,x,y) { // 2d exact algorithm.
+          P = img.get_crop(x - psize1,y - psize1,x + psize2,y + psize2,true);
+          const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2;
+          float sum_weights = 0, weight_max = 0;
+          cimg_for_inXY(res,x0,y0,x1,y1,p,q) if (p!=x || q!=y) {
+            (Q = img.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,true))-=P;
+            const float
+              dx = (float)x - p, dy = (float)y - q,
+              distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2),
+              weight = (float)std::exp(-distance2);
+            if (weight>weight_max) weight_max = weight;
+            sum_weights+=weight;
+            cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c);
+          }
+          sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c);
+          if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights;
+          else cimg_forC(res,c) res(x,y,0,c) = (Tfloat)((*this)(x,y,c));
+        }
+      }
+      }
+      return res;
+    }
+
+    //! Apply a median filter.
+    CImg<T>& blur_median(const unsigned int n) {
+      if (!n) return *this;
+      return get_blur_median(n).move_to(*this);
+    }
+
+    CImg<T> get_blur_median(const unsigned int n) const {
+      if (is_empty() || n<=1) return *this;
+      CImg<T> res(_width,_height,_depth,_spectrum);
+      T *ptrd = res._data;
+      const int hl = n/2, hr = hl - 1 + n%2;
+      if (res._depth!=1) cimg_forXYZC(*this,x,y,z,c) { // 3d
+        const int
+          x0 = x - hl, y0 = y - hl, z0 = z-hl, x1 = x + hr, y1 = y + hr, z1 = z+hr,
+          nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, nz0 = z0<0?0:z0,
+          nx1 = x1>=width()?width()-1:x1, ny1 = y1>=height()?height()-1:y1, nz1 = z1>=depth()?depth()-1:z1;
+        *(ptrd++) = get_crop(nx0,ny0,nz0,c,nx1,ny1,nz1,c).median();
+      } else {
+#define _cimg_median_sort(a,b) if ((a)>(b)) cimg::swap(a,b)
+        if (res._height!=1) switch (n) { // 2d
+        case 3 : {
+          T I[9] = { 0 };
+          CImg_3x3(J,T);
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T) {
+            std::memcpy(J,I,9*sizeof(T));
+            _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
+            _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jpn, Jcn);
+            _cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
+            _cimg_median_sort(Jpp, Jpc); _cimg_median_sort(Jnc, Jnn); _cimg_median_sort(Jcc, Jcn);
+            _cimg_median_sort(Jpc, Jpn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jnp, Jnc);
+            _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcc, Jnp); _cimg_median_sort(Jpn, Jcc);
+            _cimg_median_sort(Jcc, Jnp);
+            *(ptrd++) = Jcc;
+          }
+        } break;
+        case 5 : {
+          T I[25] = { 0 };
+          CImg_5x5(J,T);
+          cimg_forC(*this,c) cimg_for5x5(*this,x,y,0,c,I,T) {
+            std::memcpy(J,I,25*sizeof(T));
+            _cimg_median_sort(Jbb, Jpb); _cimg_median_sort(Jnb, Jab); _cimg_median_sort(Jcb, Jab); _cimg_median_sort(Jcb, Jnb);
+            _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbp, Jcp); _cimg_median_sort(Jbp, Jpp); _cimg_median_sort(Jap, Jbc);
+            _cimg_median_sort(Jnp, Jbc); _cimg_median_sort(Jnp, Jap); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jpc, Jnc);
+            _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jbn, Jpn); _cimg_median_sort(Jac, Jpn); _cimg_median_sort(Jac, Jbn);
+            _cimg_median_sort(Jnn, Jan); _cimg_median_sort(Jcn, Jan); _cimg_median_sort(Jcn, Jnn); _cimg_median_sort(Jpa, Jca);
+            _cimg_median_sort(Jba, Jca); _cimg_median_sort(Jba, Jpa); _cimg_median_sort(Jna, Jaa); _cimg_median_sort(Jcb, Jbp);
+            _cimg_median_sort(Jnb, Jpp); _cimg_median_sort(Jbb, Jpp); _cimg_median_sort(Jbb, Jnb); _cimg_median_sort(Jab, Jcp);
+            _cimg_median_sort(Jpb, Jcp); _cimg_median_sort(Jpb, Jab); _cimg_median_sort(Jpc, Jac); _cimg_median_sort(Jnp, Jac);
+            _cimg_median_sort(Jnp, Jpc); _cimg_median_sort(Jcc, Jbn); _cimg_median_sort(Jap, Jbn); _cimg_median_sort(Jap, Jcc);
+            _cimg_median_sort(Jnc, Jpn); _cimg_median_sort(Jbc, Jpn); _cimg_median_sort(Jbc, Jnc); _cimg_median_sort(Jba, Jna);
+            _cimg_median_sort(Jcn, Jna); _cimg_median_sort(Jcn, Jba); _cimg_median_sort(Jpa, Jaa); _cimg_median_sort(Jnn, Jaa);
+            _cimg_median_sort(Jnn, Jpa); _cimg_median_sort(Jan, Jca); _cimg_median_sort(Jnp, Jcn); _cimg_median_sort(Jap, Jnn);
+            _cimg_median_sort(Jbb, Jnn); _cimg_median_sort(Jbb, Jap); _cimg_median_sort(Jbc, Jan); _cimg_median_sort(Jpb, Jan);
+            _cimg_median_sort(Jpb, Jbc); _cimg_median_sort(Jpc, Jba); _cimg_median_sort(Jcb, Jba); _cimg_median_sort(Jcb, Jpc);
+            _cimg_median_sort(Jcc, Jpa); _cimg_median_sort(Jnb, Jpa); _cimg_median_sort(Jnb, Jcc); _cimg_median_sort(Jnc, Jca);
+            _cimg_median_sort(Jab, Jca); _cimg_median_sort(Jab, Jnc); _cimg_median_sort(Jac, Jna); _cimg_median_sort(Jbp, Jna);
+            _cimg_median_sort(Jbp, Jac); _cimg_median_sort(Jbn, Jaa); _cimg_median_sort(Jpp, Jaa); _cimg_median_sort(Jpp, Jbn);
+            _cimg_median_sort(Jcp, Jpn); _cimg_median_sort(Jcp, Jan); _cimg_median_sort(Jnc, Jpa); _cimg_median_sort(Jbn, Jna);
+            _cimg_median_sort(Jcp, Jnc); _cimg_median_sort(Jcp, Jbn); _cimg_median_sort(Jpb, Jap); _cimg_median_sort(Jnb, Jpc);
+            _cimg_median_sort(Jbp, Jcn); _cimg_median_sort(Jpc, Jcn); _cimg_median_sort(Jap, Jcn); _cimg_median_sort(Jab, Jbc);
+            _cimg_median_sort(Jpp, Jcc); _cimg_median_sort(Jcp, Jac); _cimg_median_sort(Jab, Jpp); _cimg_median_sort(Jab, Jcp);
+            _cimg_median_sort(Jcc, Jac); _cimg_median_sort(Jbc, Jac); _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbc, Jcc);
+            _cimg_median_sort(Jpp, Jbc); _cimg_median_sort(Jpp, Jcn); _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcp, Jcn);
+            _cimg_median_sort(Jcp, Jbc); _cimg_median_sort(Jcc, Jnn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jbc, Jnn);
+            _cimg_median_sort(Jcc, Jba); _cimg_median_sort(Jbc, Jba); _cimg_median_sort(Jbc, Jcc);
+            *(ptrd++) = Jcc;
+          }
+        } break;
+        default : {
+          cimg_forXYC(*this,x,y,c) {
+            const int
+              x0 = x - hl, y0 = y - hl, x1 = x + hr, y1 = y + hr,
+              nx0 = x0<0?0:x0, ny0 = y0<0?0:y0,
+              nx1 = x1>=width()?width()-1:x1, ny1 = y1>=height()?height()-1:y1;
+            *(ptrd++) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median();
+          }
+        }
+        } else switch (n) { // 1d
+        case 2 : {
+          T I[4] = { 0 };
+          cimg_forC(*this,c) cimg_for2x2(*this,x,y,0,c,I,T) *(ptrd++) = (T)(0.5f*(I[0]+I[1]));
+        } break;
+        case 3 : {
+          T I[9] = { 0 };
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T)
+            *(ptrd++) = I[3]<I[4]?(I[4]<I[5]?I[4]:(I[3]<I[5]?I[5]:I[3])):(I[3]<I[5]?I[3]:(I[4]<I[5]?I[5]:I[4]));
+        } break;
+        default : {
+          cimg_forXC(*this,x,c) {
+            const int
+              x0 = x - hl, x1 = x + hr,
+              nx0 = x0<0?0:x0, nx1 = x1>=width()?width()-1:x1;
+            *(ptrd++) = get_crop(nx0,0,0,c,nx1,0,0,c).median();
+          }
+        }
+        }
+      }
+      return res;
+    }
+
+    //! Sharpen image using anisotropic shock filters or inverse diffusion.
+    CImg<T>& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) {
+      if (is_empty()) return *this;
+      T val_min, val_max = max_min(val_min);
+      const float nedge = edge/2;
+      CImg<Tfloat> val, vec, velocity(_width,_height,_depth,_spectrum);
+      Tfloat *ptrd = velocity._data, veloc_max = 0;
+
+      if (_depth>1) { // 3d
+        CImg_3x3x3(I,Tfloat);
+        if (sharpen_type) { // Shock filters.
+          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
+          if (sigma>0) G.blur(sigma);
+          Tfloat *ptrG0 = G.data(0,0,0,0), *ptrG1 = G.data(0,0,0,1), *ptrG2 = G.data(0,0,0,2), *ptrG3 = G.data(0,0,0,3);
+          cimg_forXYZ(G,x,y,z) {
+            G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
+            if (val[0]<0) val[0] = 0;
+            if (val[1]<0) val[1] = 0;
+            if (val[2]<0) val[2] = 0;
+            *(ptrG0++) = vec(0,0);
+            *(ptrG1++) = vec(0,1);
+            *(ptrG2++) = vec(0,2);
+            *(ptrG3++) = 1 - (Tfloat)std::pow(1+val[0]+val[1]+val[2],-(Tfloat)nedge);
+          }
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+            const Tfloat
+              u = G(x,y,z,0),
+              v = G(x,y,z,1),
+              w = G(x,y,z,2),
+              amp = G(x,y,z,3),
+              ixx = Incc + Ipcc - 2*Iccc,
+              ixy = (Innc + Ippc - Inpc - Ipnc)/4,
+              ixz = (Incn + Ipcp - Incp - Ipcn)/4,
+              iyy = Icnc + Icpc - 2*Iccc,
+              iyz = (Icnn + Icpp - Icnp - Icpn)/4,
+              izz = Iccn + Iccp - 2*Iccc,
+              ixf = Incc - Iccc,
+              ixb = Iccc - Ipcc,
+              iyf = Icnc - Iccc,
+              iyb = Iccc - Icpc,
+              izf = Iccn - Iccc,
+              izb = Iccc - Iccp,
+              itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz,
+              it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb),
+              veloc = -amp*cimg::sign(itt)*cimg::abs(it);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          }
+        } else cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) { // Inverse diffusion.
+          const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc;
+          *(ptrd++) = veloc;
+          if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+        }
+      } else {
+        CImg_3x3(I,Tfloat);
+        if (sharpen_type) { // Shock filters.
+          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
+          if (sigma>0) G.blur(sigma);
+          Tfloat *ptrG0 = G.data(0,0,0,0), *ptrG1 = G.data(0,0,0,1), *ptrG2 = G.data(0,0,0,2);
+          cimg_forXY(G,x,y) {
+            G.get_tensor_at(x,y).symmetric_eigen(val,vec);
+            if (val[0]<0) val[0] = 0;
+            if (val[1]<0) val[1] = 0;
+            *(ptrG0++) = vec(0,0);
+            *(ptrG1++) = vec(0,1);
+            *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge);
+          }
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+            const Tfloat
+              u = G(x,y,0),
+              v = G(x,y,1),
+              amp = G(x,y,2),
+              ixx = Inc + Ipc - 2*Icc,
+              ixy = (Inn + Ipp - Inp - Ipn)/4,
+              iyy = Icn + Icp - 2*Icc,
+              ixf = Inc - Icc,
+              ixb = Icc - Ipc,
+              iyf = Icn - Icc,
+              iyb = Icc - Icp,
+              itt = u*u*ixx + v*v*iyy + 2*u*v*ixy,
+              it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb),
+              veloc = -amp*cimg::sign(itt)*cimg::abs(it);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          }
+        } else cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) { // Inverse diffusion.
+          const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc;
+          *(ptrd++) = veloc;
+          if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+        }
+      }
+      if (veloc_max<=0) return *this;
+      return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this);
+    }
+
+    CImg<T> get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) const {
+      return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma);
+    }
+
+    //! Compute the list of images, corresponding to the XY-gradients of an image.
+    /**
+       \param scheme = Numerical scheme used for the gradient computation :
+       - -1 = Backward finite differences
+       - 0 = Centered finite differences
+       - 1 = Forward finite differences
+       - 2 = Using Sobel masks
+       - 3 = Using rotation invariant masks
+       - 4 = Using Deriche recusrsive filter.
+    **/
+    CImgList<Tfloat> get_gradient(const char *const axes=0, const int scheme=3) const {
+      CImgList<Tfloat> grad(2,_width,_height,_depth,_spectrum);
+      Tfloat *ptrd0 = grad[0]._data, *ptrd1 = grad[1]._data;
+      bool is_threed = false;
+      if (axes) {
+        for (unsigned int a = 0; axes[a]; ++a) {
+          const char axis = cimg::uncase(axes[a]);
+          switch (axis) {
+          case 'x' : case 'y' : break;
+          case 'z' : is_threed = true; break;
+          default :
+            throw CImgArgumentException(_cimg_instance
+                                        "get_gradient() : Invalid specified axis '%c'.",
+                                        cimg_instance,
+                                        axis);
+          }
+        }
+      } else is_threed = (_depth>1);
+      if (is_threed) {
+        CImg<Tfloat>(_width,_height,_depth,_spectrum).move_to(grad);
+        Tfloat *ptrd2 = grad[2]._data;
+        switch (scheme) { // 3d.
+        case -1 : { // Backward finite differences.
+          CImg_3x3x3(I,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+            *(ptrd0++) = Iccc - Ipcc;
+            *(ptrd1++) = Iccc - Icpc;
+            *(ptrd2++) = Iccc - Iccp;
+          }
+        } break;
+        case 1 : { // Forward finite differences.
+          CImg_2x2x2(I,Tfloat);
+          cimg_forC(*this,c) cimg_for2x2x2(*this,x,y,z,c,I,Tfloat) {
+            *(ptrd0++) = Incc - Iccc;
+            *(ptrd1++) = Icnc - Iccc;
+            *(ptrd2++) = Iccn - Iccc;
+          }
+        } break;
+        case 4 : { // Using Deriche filter with low standard variation.
+          grad[0] = get_deriche(0,1,'x');
+          grad[1] = get_deriche(0,1,'y');
+          grad[2] = get_deriche(0,1,'z');
+        } break;
+        default : { // Central finite differences.
+          CImg_3x3x3(I,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+            *(ptrd0++) = (Incc - Ipcc)/2;
+            *(ptrd1++) = (Icnc - Icpc)/2;
+            *(ptrd2++) = (Iccn - Iccp)/2;
+          }
+        }
+        }
+      } else switch (scheme) { // 2d.
+      case -1 : { // Backward finite differences.
+        CImg_3x3(I,Tfloat);
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = Icc - Ipc;
+          *(ptrd1++) = Icc - Icp;
+        }
+      } break;
+      case 1 : { // Forward finite differences.
+        CImg_2x2(I,Tfloat);
+        cimg_forZC(*this,z,c) cimg_for2x2(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = Inc - Icc;
+          *(ptrd1++) = Icn - Icc;
+        }
+      } break;
+      case 2 : { // Sobel scheme.
+        CImg_3x3(I,Tfloat);
+        const Tfloat a = 1, b = 2;
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
+          *(ptrd1++) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
+        }
+      } break;
+      case 3 : { // Rotation invariant mask.
+        CImg_3x3(I,Tfloat);
+        const Tfloat a = (Tfloat)(0.25f*(2-std::sqrt(2.0f))), b = (Tfloat)(0.5f*(std::sqrt(2.0f)-1));
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
+          *(ptrd1++) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
+        }
+      } break;
+      case 4 : { // using Deriche filter with low standard variation
+        grad[0] = get_deriche(0,1,'x');
+        grad[1] = get_deriche(0,1,'y');
+      } break;
+      default : { // central finite differences
+        CImg_3x3(I,Tfloat);
+        cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = (Inc - Ipc)/2;
+          *(ptrd1++) = (Icn - Icp)/2;
+        }
+      }
+      }
+      if (!axes) return grad;
+      CImgList<Tfloat> res;
+      for (unsigned int l = 0; axes[l]; ++l) {
+        const char axis = cimg::uncase(axes[l]);
+        switch (axis) {
+        case 'x' : res.insert(grad[0]); break;
+        case 'y' : res.insert(grad[1]); break;
+        case 'z' : res.insert(grad[2]); break;
+        }
+      }
+      grad.assign();
+      return res;
+    }
+
+    //! Get components of the Hessian matrix of an image.
+    CImgList<Tfloat> get_hessian(const char *const axes=0) const {
+      CImgList<Tfloat> res;
+      const char *naxes = axes, *const def_axes2d = "xxxyyy", *const def_axes3d = "xxxyxzyyyzzz";
+      if (!axes) naxes = _depth>1?def_axes3d:def_axes2d;
+      const unsigned int lmax = std::strlen(naxes);
+      if (lmax%2)
+        throw CImgArgumentException(_cimg_instance
+                                    "get_hessian() : Invalid specified axes '%s'.",
+                                    cimg_instance,
+                                    naxes);
+
+      res.assign(lmax/2,_width,_height,_depth,_spectrum);
+      if (!cimg::strcasecmp(naxes,def_axes3d)) { // 3d
+        Tfloat
+          *ptrd0 = res[0]._data, *ptrd1 = res[1]._data, *ptrd2 = res[2]._data,
+          *ptrd3 = res[3]._data, *ptrd4 = res[4]._data, *ptrd5 = res[5]._data;
+        CImg_3x3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+          *(ptrd0++) = Ipcc + Incc - 2*Iccc;          // Ixx
+          *(ptrd1++) = (Ippc + Innc - Ipnc - Inpc)/4; // Ixy
+          *(ptrd2++) = (Ipcp + Incn - Ipcn - Incp)/4; // Ixz
+          *(ptrd3++) = Icpc + Icnc - 2*Iccc;          // Iyy
+          *(ptrd4++) = (Icpp + Icnn - Icpn - Icnp)/4; // Iyz
+          *(ptrd5++) = Iccn + Iccp - 2*Iccc;          // Izz
+        }
+      } else if (!cimg::strcasecmp(naxes,def_axes2d)) { // 2d
+        Tfloat *ptrd0 = res[0]._data, *ptrd1 = res[1]._data, *ptrd2 = res[2]._data;
+        CImg_3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+          *(ptrd0++) = Ipc + Inc - 2*Icc;         // Ixx
+          *(ptrd1++) = (Ipp + Inn - Ipn - Inp)/4; // Ixy
+          *(ptrd2++) = Icp + Icn - 2*Icc;         // Iyy
+        }
+      } else for (unsigned int l = 0; l<lmax; ) { // Version with custom axes.
+        const unsigned int l2 = l/2;
+          char axis1 = naxes[l++], axis2 = naxes[l++];
+          if (axis1>axis2) cimg::swap(axis1,axis2);
+          bool valid_axis = false;
+          Tfloat *ptrd = res[l2]._data;
+          if (axis1=='x' && axis2=='x') { // Ixx
+            valid_axis = true; CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Ipc + Inc - 2*Icc;
+          }
+          else if (axis1=='x' && axis2=='y') { // Ixy
+            valid_axis = true; CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipp + Inn - Ipn - Inp)/4;
+          }
+          else if (axis1=='x' && axis2=='z') { // Ixz
+            valid_axis = true; CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Ipcp + Incn - Ipcn - Incp)/4;
+          }
+          else if (axis1=='y' && axis2=='y') { // Iyy
+            valid_axis = true; CImg_3x3(I,Tfloat);
+            cimg_forZC(*this,z,c) cimg_for3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Icp + Icn - 2*Icc;
+          }
+          else if (axis1=='y' && axis2=='z') { // Iyz
+            valid_axis = true; CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = (Icpp + Icnn - Icpn - Icnp)/4;
+          }
+          else if (axis1=='z' && axis2=='z') { // Izz
+            valid_axis = true; CImg_3x3x3(I,Tfloat);
+            cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Iccn + Iccp - 2*Iccc;
+          }
+          else if (!valid_axis)
+            throw CImgArgumentException(_cimg_instance
+                                        "get_hessian() : Invalid specified axes '%s'.",
+                                        cimg_instance,
+                                        naxes);
+        }
+      return res;
+    }
+
+    //! Compute the laplacian of the instance image.
+    CImg<T>& laplacian() {
+      return get_laplacian().move_to(*this);
+    }
+
+    CImg<Tfloat> get_laplacian() const {
+      if (is_empty()) return CImg<Tfloat>();
+      CImg<Tfloat> res(_width,_height,_depth,_spectrum);
+      Tfloat *ptrd = res._data;
+      if (_depth>1) { // 3d
+        CImg_3x3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat)
+          *(ptrd++) = Incc + Ipcc + Icnc + Icpc + Iccn + Iccp - 6*Iccc;
+      } else if (_height>1) { // 2d
+        CImg_3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat)
+          *(ptrd++) = Inc + Ipc + Icn + Icp - 4*Icc;
+      } else { // 1d
+        CImg_3x3(I,Tfloat);
+        cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat)
+          *(ptrd++) = Inc + Ipc - 2*Icc;
+      }
+      return res;
+    }
+
+    //! Compute the structure tensor field of an image.
+    CImg<T>& structure_tensors(const unsigned int scheme=1) {
+      return get_structure_tensors(scheme).move_to(*this);
+    }
+
+    CImg<Tfloat> get_structure_tensors(const unsigned int scheme=1) const {
+      if (is_empty()) return *this;
+      CImg<Tfloat> res;
+      if (_depth>1) { // 3d
+        res.assign(_width,_height,_depth,6,0);
+        CImg_3x3x3(I,Tfloat);
+        switch (scheme) {
+        case 0 : { // classical central finite differences
+          cimg_forC(*this,c) {
+            Tfloat
+              *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+              *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ix = (Incc - Ipcc)/2,
+                iy = (Icnc - Icpc)/2,
+                iz = (Iccn - Iccp)/2;
+              *(ptrd0++)+=ix*ix;
+              *(ptrd1++)+=ix*iy;
+              *(ptrd2++)+=ix*iz;
+              *(ptrd3++)+=iy*iy;
+              *(ptrd4++)+=iy*iz;
+              *(ptrd5++)+=iz*iz;
+            }
+          }
+        } break;
+        case 1 : { // Forward/backward finite differences (version 1).
+          cimg_forC(*this,c) {
+            Tfloat
+              *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+              *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixf = Incc - Iccc, ixb = Iccc - Ipcc,
+                iyf = Icnc - Iccc, iyb = Iccc - Icpc,
+                izf = Iccn - Iccc, izb = Iccc - Iccp;
+              *(ptrd0++)+=(ixf*ixf + ixf*ixb + ixb*ixf + ixb*ixb)/4;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4;
+              *(ptrd3++)+=(iyf*iyf + iyf*iyb + iyb*iyf + iyb*iyb)/4;
+              *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4;
+              *(ptrd5++)+=(izf*izf + izf*izb + izb*izf + izb*izb)/4;
+            }
+          }
+        } break;
+        default : { // Forward/backward finite differences (version 2).
+          cimg_forC(*this,c) {
+            Tfloat
+              *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+              *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+              const Tfloat
+                ixf = Incc - Iccc, ixb = Iccc - Ipcc,
+                iyf = Icnc - Iccc, iyb = Iccc - Icpc,
+                izf = Iccn - Iccc, izb = Iccc - Iccp;
+              *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(ixf*izf + ixf*izb + ixb*izf + ixb*izb)/4;
+              *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2;
+              *(ptrd4++)+=(iyf*izf + iyf*izb + iyb*izf + iyb*izb)/4;
+              *(ptrd5++)+=(izf*izf + izb*izb)/2;
+            }
+          }
+        } break;
+        }
+      } else { // 2d
+        res.assign(_width,_height,_depth,3,0);
+        CImg_3x3(I,Tfloat);
+        switch (scheme) {
+        case 0 : { // classical central finite differences
+          cimg_forC(*this,c) {
+            Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+              const Tfloat
+                ix = (Inc - Ipc)/2,
+                iy = (Icn - Icp)/2;
+              *(ptrd0++)+=ix*ix;
+              *(ptrd1++)+=ix*iy;
+              *(ptrd2++)+=iy*iy;
+            }
+          }
+        } break;
+        case 1 : { // Forward/backward finite differences (version 1).
+          cimg_forC(*this,c) {
+            Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+              const Tfloat
+                ixf = Inc - Icc, ixb = Icc - Ipc,
+                iyf = Icn - Icc, iyb = Icc - Icp;
+              *(ptrd0++)+=(ixf*ixf + ixf*ixb + ixb*iyf + ixb*ixb)/4;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(iyf*iyf + iyf*iyb + iyb*iyf + iyb*iyb)/4;
+            }
+          }
+        } break;
+        default : { // Forward/backward finite differences (version 2).
+          cimg_forC(*this,c) {
+            Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+              const Tfloat
+                ixf = Inc - Icc, ixb = Icc - Ipc,
+                iyf = Icn - Icc, iyb = Icc - Icp;
+              *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2;
+              *(ptrd1++)+=(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb)/4;
+              *(ptrd2++)+=(iyf*iyf + iyb*iyb)/2;
+            }
+          }
+        } break;
+        }
+      }
+      return res;
+    }
+
+    //! Get a diffusion tensor for edge-preserving anisotropic smoothing of an image.
+    CImg<T>& edge_tensors(const float sharpness=0.7f, const float anisotropy=0.6f,
+                          const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) {
+      CImg<Tfloat> res;
+      const float nsharpness = cimg::max(sharpness,1e-5f), power1 = (is_sqrt?0.5f:1)*nsharpness, power2 = power1/(1e-7f+1-anisotropy);
+      blur(alpha).normalize(0,(T)255);
+
+      if (_depth>1) { // 3d
+        CImg<floatT> val(3), vec(3,3);
+        get_structure_tensors().move_to(res).blur(sigma);
+        Tfloat
+          *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2),
+          *ptrd3 = res.data(0,0,0,3), *ptrd4 = res.data(0,0,0,4), *ptrd5 = res.data(0,0,0,5);
+        cimg_forXYZ(*this,x,y,z) {
+          res.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
+          const float
+            _l1 = val[2], _l2 = val[1], _l3 = val[0],
+            l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0, l3 = _l3>0?_l3:0,
+            ux = vec(0,0), uy = vec(0,1), uz = vec(0,2),
+            vx = vec(1,0), vy = vec(1,1), vz = vec(1,2),
+            wx = vec(2,0), wy = vec(2,1), wz = vec(2,2),
+            n1 = (float)std::pow(1+l1+l2+l3,-power1),
+            n2 = (float)std::pow(1+l1+l2+l3,-power2);
+          *(ptrd0++) = n1*(ux*ux + vx*vx) + n2*wx*wx;
+          *(ptrd1++) = n1*(ux*uy + vx*vy) + n2*wx*wy;
+          *(ptrd2++) = n1*(ux*uz + vx*vz) + n2*wx*wz;
+          *(ptrd3++) = n1*(uy*uy + vy*vy) + n2*wy*wy;
+          *(ptrd4++) = n1*(uy*uz + vy*vz) + n2*wy*wz;
+          *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz;
+        }
+      } else { // for 2d images
+        CImg<floatT> val(2), vec(2,2);
+        get_structure_tensors().move_to(res).blur(sigma);
+        Tfloat *ptrd0 = res.data(0,0,0,0), *ptrd1 = res.data(0,0,0,1), *ptrd2 = res.data(0,0,0,2);
+        cimg_forXY(*this,x,y) {
+          res.get_tensor_at(x,y).symmetric_eigen(val,vec);
+          const float
+            _l1 = val[1], _l2 = val[0],
+            l1 = _l1>0?_l1:0, l2 = _l2>0?_l2:0,
+            ux = vec(1,0), uy = vec(1,1),
+            vx = vec(0,0), vy = vec(0,1),
+            n1 = (float)std::pow(1+l1+l2,-power1),
+            n2 = (float)std::pow(1+l1+l2,-power2);
+          *(ptrd0++) = n1*ux*ux + n2*vx*vx;
+          *(ptrd1++) = n1*ux*uy + n2*vx*vy;
+          *(ptrd2++) = n1*uy*uy + n2*vy*vy;
+        }
+      }
+      return res.move_to(*this);
+    }
+
+    CImg<Tfloat> get_edge_tensors(const float sharpness=0.7f, const float anisotropy=0.6f,
+                                  const float alpha=0.6f, const float sigma=1.1f, const bool is_sqrt=false) const {
+      return CImg<Tfloat>(*this,false).edge_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt);
+    }
+
+    //! Estimate a displacement field between instance image and given target image.
+    /**
+       \param backward : if false, match I2(X+U(X)) = I1(X), else match I2(X) = I1(X-U(X)).
+    **/
+    CImg<T>& displacement(const CImg<T>& target, const float smooth=0.1f, const float precision=0.1f,
+                          const unsigned int nb_scales=0, const unsigned int iteration_max=1000,
+                          const bool backward = true) {
+      return get_displacement(target,smooth,precision,nb_scales,iteration_max,backward).move_to(*this);
+    }
+
+    CImg<Tfloat> get_displacement(const CImg<T>& target,
+                                  const float smoothness=0.1f, const float precision=0.1f,
+                                  const unsigned int nb_scales=0, const unsigned int iteration_max=1000,
+                                  const bool backward = true) const {
+      if (is_empty() || !target) return *this;
+      if (!is_sameXYZC(target))
+        throw CImgArgumentException(_cimg_instance
+                                    "displacement() : Instance and target image (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    target._width,target._height,target._depth,target._spectrum,target._data);
+      if (smoothness<0)
+        throw CImgArgumentException(_cimg_instance
+                                    "displacement() : Invalid specified smoothness %g "
+                                    "(should be >=0",
+                                    cimg_instance,
+                                    smoothness);
+      if (precision<0)
+        throw CImgArgumentException(_cimg_instance
+                                    "displacement() : Invalid specified precision %g "
+                                    "(should be >=0",
+                                    cimg_instance,
+                                    precision);
+
+      const unsigned int nscales = nb_scales>0?nb_scales:(unsigned int)(2*std::log((double)(cimg::max(_width,_height,_depth))));
+      Tfloat m1, M1 = (Tfloat)max_min(m1), m2, M2 = (Tfloat)target.max_min(m2);
+      const Tfloat factor = cimg::max(cimg::abs(m1),cimg::abs(M1),cimg::abs(m2),cimg::abs(M2));
+      CImg<Tfloat> U0;
+      const bool is_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<Tfloat>
+          I1 = get_resize(swidth,sheight,sdepth,-100,2),
+          I2 = target.get_resize(swidth,sheight,sdepth,-100,2);
+        I1/=factor; I2/=factor;
+        CImg<Tfloat> U;
+        if (U0) U = (U0*=1.5f).get_resize(I1.width(),I1.height(),I1.depth(),-100,3);
+        else U.assign(I1.width(),I1.height(),I1.depth(),is_threed?3:2,0);
+
+        // Begin single-scale motion estimation
+        CImg<Tfloat> veloc(U);
+        float dt = 2, energy = cimg::type<float>::max();
+        const CImgList<Tfloat> dI = backward?I1.get_gradient():I2.get_gradient();
+        for (unsigned int iteration = 0; iteration<iteration_max; ++iteration) {
+          veloc.fill(0);
+          float nenergy = 0;
+          if (is_threed) {
+            cimg_for3XYZ(U,x,y,z) {
+              const float
+                sgnU = backward?-1.0f:1.0f,
+                X = (float)(x + sgnU * U(x,y,z,0)),
+                Y = (float)(y + sgnU * U(x,y,z,1)),
+                Z = (float)(z + sgnU * U(x,y,z,2));
+              cimg_forC(U,c) {
+                const Tfloat
+                  Ux = 0.5f*(U(_n1x,y,z,c) - U(_p1x,y,z,c)),
+                  Uy = 0.5f*(U(x,_n1y,z,c) - U(x,_p1y,z,c)),
+                  Uz = 0.5f*(U(x,y,_n1z,c) - U(x,y,_p1z,c)),
+                  Uxx = U(_n1x,y,z,c) + U(_p1x,y,z,c) - 2*U(x,y,z,c),
+                  Uyy = U(x,_n1y,z,c) + U(x,_p1y,z,c) - 2*U(x,y,z,c),
+                  Uzz = U(x,y,_n1z,c) + U(x,y,_n1z,c) - 2*U(x,y,z,c);
+                nenergy+=(float)(smoothness*(Ux*Ux + Uy*Uy + Uz*Uz));
+                Tfloat deltaIgrad = 0;
+                cimg_forC(I1,i) {
+                  const Tfloat deltaIi = (float)(backward?I2(x,y,z,i)-I1._linear_atXYZ(X,Y,Z,i):I2._linear_atXYZ(X,Y,Z,i)-I1(x,y,z,i));
+                  nenergy+=(float)(deltaIi*deltaIi/2);
+                  deltaIgrad+=-deltaIi*dI[c]._linear_atXYZ(X,Y,Z,i);
+                }
+                veloc(x,y,z,c) = deltaIgrad + smoothness*(Uxx + Uyy + Uzz);
+              }
+            }
+          } else {
+            cimg_for3XY(U,x,y) {
+              const float
+                sgnU = backward?-1.0f:1.0f,
+                X = (float)(x + sgnU * U(x,y,0)),
+                Y = (float)(y + sgnU * U(x,y,1));
+              cimg_forC(U,c) {
+                const Tfloat
+                  Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)),
+                  Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)),
+                  Uxx = U(_n1x,y,c) + U(_p1x,y,c) - 2*U(x,y,c),
+                  Uyy = U(x,_n1y,c) + U(x,_p1y,c) - 2*U(x,y,c);
+                nenergy+=(float)(smoothness*(Ux*Ux + Uy*Uy));
+                Tfloat deltaIgrad = 0;
+                cimg_forC(I1,i) {
+                  const Tfloat deltaIi = (float)(backward?I2(x,y,i)-I1.linear_atXY(X,Y,i):I2._linear_atXY(X,Y,i)-I1(x,y,i));
+                  nenergy+=(float)(deltaIi*deltaIi/2);
+                  deltaIgrad+=-deltaIi*dI[c]._linear_atXY(X,Y,i);
+                }
+                veloc(x,y,c) = deltaIgrad + smoothness*(Uxx + Uyy);
+              }
+            }
+          }
+          const Tfloat vmax = cimg::max(cimg::abs(veloc.min()), cimg::abs(veloc.max()));
+          U+=(veloc*=dt/(1e-6+vmax));
+          if (cimg::abs(nenergy-energy)<sprecision) break;
+          if (nenergy<energy) dt*=0.5f;
+          energy = nenergy;
+        }
+        U.move_to(U0);
+      }
+      return U0;
+    }
+
+    //! Compute the Euclidean distance map to a shape of specified isovalue.
+    CImg<T>& distance(const T isovalue,
+                      const float sizex=1, const float sizey=1, const float sizez=1,
+                      const bool compute_sqrt=true) {
+      return get_distance(isovalue,sizex,sizey,sizez,compute_sqrt).move_to(*this);
+    }
+
+    CImg<floatT> get_distance(const T isovalue,
+                              const float sizex=1, const float sizey=1, const float sizez=1,
+                              const bool compute_sqrt=true) const {
+      if (is_empty()) return *this;
+      const int dx = width(), dy = height(), dz = depth();
+      CImg<floatT> res(dx,dy,dz,_spectrum);
+      const float maxdist = (float)std::sqrt((float)dx*dx + dy*dy + dz*dz);
+      cimg_forC(*this,c) {
+        bool is_isophote = false;
+
+        if (_depth>1) { // 3d version
+          cimg_forYZ(*this,y,z) {
+            if ((*this)(0,y,z,c)==isovalue) { is_isophote = true; res(0,y,z,c) = 0; } else res(0,y,z,c) = maxdist;
+            for (int x = 1; x<dx; ++x) if ((*this)(x,y,z,c)==isovalue) { is_isophote = true; res(x,y,z,c) = 0; }
+            else res(x,y,z,c) = res(x-1,y,z,c) + sizex;
+            for (int x = dx-2; x>=0; --x) if (res(x+1,y,z,c)<res(x,y,z,c)) res(x,y,z,c) = res(x+1,y,z,c) + sizex;
+          }
+          if (!is_isophote) { res.get_shared_channel(c).fill(cimg::type<floatT>::max()); continue; }
+          CImg<floatT> tmp(cimg::max(dy,dz));
+          CImg<intT> s(tmp._width), t(s._width);
+          cimg_forXZ(*this,x,z) {
+            cimg_forY(*this,y) tmp[y] = res(x,y,z,c);
+            int q = s[0] = t[0] = 0;
+            for (int y = 1; y<dy; ++y) {
+              const float val = tmp[y], val2 = val*val;
+              while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizey)>_distance_f(t[q],y,val2,sizey)) --q;
+              if (q<0) { q = 0; s[0] = y; }
+              else {
+                const int w = 1 + _distance_sep(s[q],y,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizey);
+                if (w<dy) { s[++q] = y; t[q] = w; }
+              }
+            }
+            for (int y = dy - 1; y>=0; --y) {
+              res(x,y,z,c) = _distance_f(y,s[q],cimg::sqr(tmp[s[q]]),sizey);
+              if (y==t[q]) --q;
+            }
+          }
+          cimg_forXY(*this,x,y) {
+            cimg_forZ(*this,z) tmp[z] = res(x,y,z,c);
+            int q = s[0] = t[0] = 0;
+            for (int z = 1; z<dz; ++z) {
+              const float val = tmp[z];
+              while (q>=0 && _distance_f(t(q),s[q],tmp[s[q]],sizez)>_distance_f(t[q],z,tmp[z],sizez)) --q;
+              if (q<0) { q = 0; s[0] = z; }
+              else {
+                const int w = 1 + _distance_sep(s[q],z,(int)tmp[s[q]],(int)val,sizez);
+                if (w<dz) { s[++q] = z; t[q] = w; }
+              }
+            }
+            for (int z = dz - 1; z>=0; --z) {
+              const float val = _distance_f(z,s[q],tmp[s[q]],sizez);
+              res(x,y,z,c) = compute_sqrt?(float)std::sqrt(val):val;
+              if (z==t[q]) --q;
+            }
+          }
+        } else { // 2d version (with small optimizations)
+          cimg_forX(*this,x) {
+            const T *ptrs = data(x,0,0,c);
+            float *ptrd = res.data(x,0,0,c), d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:maxdist;
+            for (int y = 1; y<dy; ++y) { ptrs+=_width; ptrd+=_width; d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:d+sizey; }
+            for (int y = dy - 2; y>=0; --y) { ptrd-=_width; if (d<*ptrd) *ptrd = (d+=sizey); else d = *ptrd; }
+          }
+          if (!is_isophote) { res.get_shared_channel(c).fill(cimg::type<floatT>::max()); continue; }
+          CImg<floatT> tmp(dx);
+          CImg<intT> s(dx), t(dx);
+          cimg_forY(*this,y) {
+            float *ptmp = tmp._data;
+            std::memcpy(ptmp,res.data(0,y,0,c),sizeof(float)*dx);
+            int q = s[0] = t[0] = 0;
+            for (int x = 1; x<dx; ++x) {
+              const float val = *(++ptmp), val2 = val*val;
+              while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizex)>_distance_f(t[q],x,val2,sizex)) --q;
+              if (q<0) { q = 0; s[0] = x; }
+              else {
+                const int w = 1 + _distance_sep(s[q],x,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizex);
+                if (w<dx) { s[++q] = x; t[q] = w; }
+              }
+            }
+            float *pres = res.data(0,y,0,c) + _width;
+            for (int x = dx - 1; x>=0; --x) {
+              const float val = _distance_f(x,s[q],cimg::sqr(tmp[s[q]]),sizex);
+              *(--pres) = compute_sqrt?(float)std::sqrt(val):val;
+              if (x==t[q]) --q;
+            }
+          }
+        }
+      }
+      return res;
+    }
+
+    static float _distance_f(const int x, const int i, const float gi2, const float fact) {
+      const float xmi = fact*((float)x - i);
+      return xmi*xmi + gi2;
+    }
+    static int _distance_sep(const int i, const int u, const int gi2, const int gu2, const float fact) {
+      const float fact2 = fact*fact;
+      return (int)(fact2*(u*u - i*i) + gu2 - gi2)/(int)(2*fact2*(u - i));
+    }
+
+    //! Compute distance function from 0-valued isophotes by the application of an Eikonal PDE.
+    CImg<T>& distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) {
+      if (is_empty()) return *this;
+      CImg<Tfloat> velocity(*this);
+      for (unsigned int iteration = 0; iteration<nb_iterations; ++iteration) {
+        Tfloat *ptrd = velocity._data, veloc_max = 0;
+        if (_depth>1) { // 3d
+          CImg_3x3x3(I,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) if (band_size<=0 || cimg::abs(Iccc)<band_size) {
+            const Tfloat
+              gx = (Incc - Ipcc)/2,
+              gy = (Icnc - Icpc)/2,
+              gz = (Iccn - Iccp)/2,
+              sgn = -cimg::sign(Iccc),
+              ix = gx*sgn>0?(Incc - Iccc):(Iccc - Ipcc),
+              iy = gy*sgn>0?(Icnc - Iccc):(Iccc - Icpc),
+              iz = gz*sgn>0?(Iccn - Iccc):(Iccc - Iccp),
+              ng = (Tfloat)(1e-5f + std::sqrt(gx*gx + gy*gy + gz*gz)),
+              ngx = gx/ng,
+              ngy = gy/ng,
+              ngz = gz/ng,
+              veloc = sgn*(ngx*ix + ngy*iy + ngz*iz - 1);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          } else *(ptrd++) = 0;
+        } else { // 2d version
+          CImg_3x3(I,Tfloat);
+          cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,Tfloat) if (band_size<=0 || cimg::abs(Icc)<band_size) {
+            const Tfloat
+              gx = (Inc - Ipc)/2,
+              gy = (Icn - Icp)/2,
+              sgn = -cimg::sign(Icc),
+              ix = gx*sgn>0?(Inc - Icc):(Icc - Ipc),
+              iy = gy*sgn>0?(Icn - Icc):(Icc - Icp),
+              ng = (Tfloat)(1e-5f + std::sqrt(gx*gx + gy*gy)),
+              ngx = gx/ng,
+              ngy = gy/ng,
+              veloc = sgn*(ngx*ix + ngy*iy - 1);
+            *(ptrd++) = veloc;
+            if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+          } else *(ptrd++) = 0;
+        }
+        if (veloc_max>0) *this+=(velocity*=time_step/veloc_max);
+      }
+      return *this;
+    }
+
+    CImg<Tfloat> get_distance_eikonal(const unsigned int nb_iterations, const float band_size=0, const float time_step=0.5f) const {
+      return CImg<Tfloat>(*this,false).distance_eikonal(nb_iterations,band_size,time_step);
+    }
+
+    //! Compute the Haar multiscale wavelet transform (monodimensional version).
+    /**
+       \param axis Axis considered for the transform.
+       \param invert Set inverse of direct transform.
+       \param nb_scales Number of scales used for the transform.
+    **/
+    CImg<T>& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) {
+      return get_haar(axis,invert,nb_scales).move_to(*this);
+    }
+
+    CImg<Tfloat> get_haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) const {
+      if (is_empty() || !nb_scales) return *this;
+      CImg<Tfloat> res;
+
+      if (nb_scales==1) {
+        switch (cimg::uncase(axis)) { // Single scale transform
+        case 'x' : {
+          const unsigned int w = _width/2;
+          if (w) {
+            if (w%2)
+              throw CImgInstanceException(_cimg_instance
+                                          "haar() : Sub-image width %u is not even at scale %u.",
+                                          cimg_instance,
+                                          w);
+
+            res.assign(_width,_height,_depth,_spectrum);
+            if (invert) cimg_forYZC(*this,y,z,c) { // Inverse transform along X
+              for (unsigned int x = 0, xw = w, x2 = 0; x<w; ++x, ++xw) {
+                const Tfloat val0 = (Tfloat)(*this)(x,y,z,c), val1 = (Tfloat)(*this)(xw,y,z,c);
+                res(x2++,y,z,c) = val0 - val1;
+                res(x2++,y,z,c) = val0 + val1;
+              }
+            } else cimg_forYZC(*this,y,z,c) { // Direct transform along X
+              for (unsigned int x = 0, xw = w, x2 = 0; x<w; ++x, ++xw) {
+                const Tfloat val0 = (Tfloat)(*this)(x2++,y,z,c), val1 = (Tfloat)(*this)(x2++,y,z,c);
+                res(x,y,z,c) = (val0 + val1)/2;
+                res(xw,y,z,c) = (val1 - val0)/2;
+              }
+            }
+          } else return *this;
+        } break;
+        case 'y' : {
+          const unsigned int h = _height/2;
+          if (h) {
+            if (h%2)
+              throw CImgInstanceException(_cimg_instance
+                                          "haar() : Sub-image height %u is not even at scale %u.",
+                                          cimg_instance,
+                                          h);
+
+            res.assign(_width,_height,_depth,_spectrum);
+            if (invert) cimg_forXZC(*this,x,z,c) { // Inverse transform along Y
+              for (unsigned int y = 0, yh = h, y2 = 0; y<h; ++y, ++yh) {
+                const Tfloat val0 = (Tfloat)(*this)(x,y,z,c), val1 = (Tfloat)(*this)(x,yh,z,c);
+                res(x,y2++,z,c) = val0 - val1;
+                res(x,y2++,z,c) = val0 + val1;
+              }
+            } else cimg_forXZC(*this,x,z,c) {
+              for (unsigned int y = 0, yh = h, y2 = 0; y<h; ++y, ++yh) { // Direct transform along Y
+                const Tfloat val0 = (Tfloat)(*this)(x,y2++,z,c), val1 = (Tfloat)(*this)(x,y2++,z,c);
+                res(x,y,z,c) = (val0 + val1)/2;
+                res(x,yh,z,c) = (val1 - val0)/2;
+              }
+            }
+          } else return *this;
+        } break;
+        case 'z' : {
+          const unsigned int d = _depth/2;
+          if (d) {
+            if (d%2)
+              throw CImgInstanceException(_cimg_instance
+                                          "haar() : Sub-image depth %u is not even at scale %u.",
+                                          cimg_instance,
+                                          d);
+
+            res.assign(_width,_height,_depth,_spectrum);
+            if (invert) cimg_forXYC(*this,x,y,c) { // Inverse transform along Z
+              for (unsigned int z = 0, zd = d, z2 = 0; z<d; ++z, ++zd) {
+                const Tfloat val0 = (Tfloat)(*this)(x,y,z,c), val1 = (Tfloat)(*this)(x,y,zd,c);
+                res(x,y,z2++,c) = val0 - val1;
+                res(x,y,z2++,c) = val0 + val1;
+              }
+            } else cimg_forXYC(*this,x,y,c) {
+              for (unsigned int z = 0, zd = d, z2 = 0; z<d; ++z, ++zd) { // Direct transform along Z
+                const Tfloat val0 = (Tfloat)(*this)(x,y,z2++,c), val1 = (Tfloat)(*this)(x,y,z2++,c);
+                res(x,y,z,c) = (val0 + val1)/2;
+                res(x,y,zd,c) = (val1 - val0)/2;
+              }
+            }
+          } else return *this;
+        } break;
+        default :
+          throw CImgArgumentException(_cimg_instance
+                                      "haar() : Invalid specified axis '%c' "
+                                      "(should be { x | y | z }).",
+                                      cimg_instance,
+                                      axis);
+        }
+      } else { // Multi-scale version
+        if (invert) {
+          res.assign(*this);
+          switch (cimg::uncase(axis)) {
+          case 'x' : {
+            unsigned int w = _width;
+            for (unsigned int s = 1; w && s<nb_scales; ++s) w/=2;
+            for (w = w?w:1; w<=_width; w*=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',true,1));
+          } break;
+          case 'y' : {
+            unsigned int h = _width;
+            for (unsigned int s = 1; h && s<nb_scales; ++s) h/=2;
+            for (h = h?h:1; h<=_height; h*=2) res.draw_image(res.get_crop(0,0,_width-1,h-1).get_haar('y',true,1));
+          } break;
+          case 'z' : {
+            unsigned int d = _depth;
+            for (unsigned int s = 1; d && s<nb_scales; ++s) d/=2;
+            for (d = d?d:1; d<=_depth; d*=2) res.draw_image(res.get_crop(0,0,0,_width-1,_height-1,d-1).get_haar('z',true,1));
+          } break;
+          default :
+            throw CImgArgumentException(_cimg_instance
+                                        "haar() : Invalid specified axis '%c' "
+                                        "(should be { x | y | z }).",
+                                        cimg_instance,
+                                        axis);
+          }
+        } else { // Direct transform
+          res = get_haar(axis,false,1);
+          switch (cimg::uncase(axis)) {
+          case 'x' : {
+            for (unsigned int s = 1, w = _width/2; w && s<nb_scales; ++s, w/=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',false,1));
+          } break;
+          case 'y' : {
+            for (unsigned int s = 1, h = _height/2; h && s<nb_scales; ++s, h/=2) res.draw_image(res.get_crop(0,0,_width-1,h-1).get_haar('y',false,1));
+          } break;
+          case 'z' : {
+            for (unsigned int s = 1, d = _depth/2; d && s<nb_scales; ++s, d/=2) res.draw_image(res.get_crop(0,0,0,_width-1,_height-1,d-1).get_haar('z',false,1));
+          } break;
+          default :
+            throw CImgArgumentException(_cimg_instance
+                                        "haar() : Invalid specified axis '%c' "
+                                        "(should be { x | y | z }).",
+                                        cimg_instance,
+                                        axis);
+          }
+        }
+      }
+      return res;
+    }
+
+    //! Compute the Haar multiscale wavelet transform.
+    /**
+       \param invert Set inverse of direct transform.
+       \param nb_scales Number of scales used for the transform.
+    **/
+    CImg<T>& haar(const bool invert=false, const unsigned int nb_scales=1) {
+      return get_haar(invert,nb_scales).move_to(*this);
+    }
+
+    CImg<Tfloat> get_haar(const bool invert=false, const unsigned int nb_scales=1) const {
+      CImg<Tfloat> res;
+
+      if (nb_scales==1) { // Single scale transform
+        if (_width>1) get_haar('x',invert,1).move_to(res);
+        if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); }
+        if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_to(res); }
+        if (res) return res;
+      } else { // Multi-scale transform
+        if (invert) { // Inverse transform
+          res.assign(*this);
+          if (_width>1) {
+            if (_height>1) {
+              if (_depth>1) {
+                unsigned int w = _width, h = _height, d = _depth; for (unsigned int s = 1; w && h && d && s<nb_scales; ++s) { w/=2; h/=2; d/=2; }
+                for (w = w?w:1, h = h?h:1, d = d?d:1; w<=_width && h<=_height && d<=_depth; w*=2, h*=2, d*=2)
+                  res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).get_haar(true,1));
+              } else {
+                unsigned int w = _width, h = _height; for (unsigned int s = 1; w && h && s<nb_scales; ++s) { w/=2; h/=2; }
+                for (w = w?w:1, h = h?h:1; w<=_width && h<=_height; w*=2, h*=2)
+                  res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).get_haar(true,1));
+              }
+            } else {
+              if (_depth>1) {
+                unsigned int w = _width, d = _depth; for (unsigned int s = 1; w && d && s<nb_scales; ++s) { w/=2; d/=2; }
+                for (w = w?w:1, d = d?d:1; w<=_width && d<=_depth; w*=2, d*=2)
+                  res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).get_haar(true,1));
+              } else {
+                unsigned int w = _width; for (unsigned int s = 1; w && s<nb_scales; ++s) w/=2;
+                for (w = w?w:1; w<=_width; w*=2)
+                  res.draw_image(res.get_crop(0,0,0,w-1,0,0).get_haar(true,1));
+              }
+            }
+          } else {
+            if (_height>1) {
+              if (_depth>1) {
+                unsigned int h = _height, d = _depth; for (unsigned int s = 1; h && d && s<nb_scales; ++s) { h/=2; d/=2; }
+                for (h = h?h:1, d = d?d:1; h<=_height && d<=_depth; h*=2, d*=2)
+                  res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).get_haar(true,1));
+              } else {
+                unsigned int h = _height; for (unsigned int s = 1; h && s<nb_scales; ++s) h/=2;
+                for (h = h?h:1; h<=_height; h*=2)
+                  res.draw_image(res.get_crop(0,0,0,0,h-1,0).get_haar(true,1));
+              }
+            } else {
+              if (_depth>1) {
+                unsigned int d = _depth; for (unsigned int s = 1; d && s<nb_scales; ++s) d/=2;
+                for (d = d?d:1; d<=_depth; d*=2)
+                  res.draw_image(res.get_crop(0,0,0,0,0,d-1).get_haar(true,1));
+              } else return *this;
+            }
+          }
+        } else { // Direct transform
+          res = get_haar(false,1);
+          if (_width>1) {
+            if (_height>1) {
+              if (_depth>1) for (unsigned int s = 1, w = _width/2, h = _height/2, d = _depth/2; w && h && d && s<nb_scales; ++s, w/=2, h/=2, d/=2)
+                res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).haar(false,1));
+              else for (unsigned int s = 1, w = _width/2, h = _height/2; w && h && s<nb_scales; ++s, w/=2, h/=2)
+                res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).haar(false,1));
+            } else {
+              if (_depth>1) for (unsigned int s = 1, w = _width/2, d = _depth/2; w && d && s<nb_scales; ++s, w/=2, d/=2)
+                res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).haar(false,1));
+              else for (unsigned int s = 1, w = _width/2; w && s<nb_scales; ++s, w/=2)
+                res.draw_image(res.get_crop(0,0,0,w-1,0,0).haar(false,1));
+            }
+          } else {
+            if (_height>1) {
+              if (_depth>1) for (unsigned int s = 1, h = _height/2, d = _depth/2; h && d && s<nb_scales; ++s, h/=2, d/=2)
+                res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).haar(false,1));
+              else for (unsigned int s = 1, h = _height/2; h && s<nb_scales; ++s, h/=2)
+                res.draw_image(res.get_crop(0,0,0,0,h-1,0).haar(false,1));
+            } else {
+              if (_depth>1) for (unsigned int s = 1, d = _depth/2; d && s<nb_scales; ++s, d/=2)
+                res.draw_image(res.get_crop(0,0,0,0,0,d-1).haar(false,1));
+              else return *this;
+            }
+          }
+        }
+        return res;
+      }
+      return *this;
+    }
+
+    //! Compute a 1d Fast Fourier Transform, along a specified axis.
+    CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
+      CImgList<Tfloat> res(*this,CImg<Tfloat>());
+      CImg<Tfloat>::FFT(res[0],res[1],axis,invert);
+      return res;
+    }
+
+    //! Compute a n-d Fast-Fourier Transform.
+    CImgList<Tfloat> get_FFT(const bool invert=false) const {
+      CImgList<Tfloat> res(*this,CImg<Tfloat>());
+      CImg<Tfloat>::FFT(res[0],res[1],invert);
+      return res;
+    }
+
+    //! Compute a 1d Fast Fourier Transform, along a specified axis.
+    static void FFT(CImg<T>& real, CImg<T>& imag, const char axis, const bool invert=false) {
+      if (!real)
+        throw CImgInstanceException("CImg<%s>::FFT() : Specified real part is empty.",
+                                    pixel_type());
+
+      if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,0);
+      if (!real.is_sameXYZC(imag))
+        throw CImgInstanceException("CImg<%s>::FFT() : Specified real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p) have different dimensions.",
+                                    pixel_type(),
+                                    real._width,real._height,real._depth,real._spectrum,real._data,
+                                    imag._width,imag._height,imag._depth,imag._spectrum,imag._data);
+#ifdef cimg_use_fftw3
+      fftw_complex *data_in;
+      fftw_plan data_plan;
+
+      switch (cimg::uncase(axis)) {
+      case 'x' : { // Fourier along X, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width);
+        data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        cimg_forYZC(real,y,z,c) {
+          T *ptrr = real.data(0,y,z,c), *ptri = imag.data(0,y,z,c);
+          double *ptrd = (double*)data_in;
+          cimg_forX(real,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._width;
+          if (invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); }
+          else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }
+        }
+      } break;
+      case 'y' : { // Fourier along Y, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height);
+        data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        const unsigned int off = real._width;
+        cimg_forXZC(real,x,z,c) {
+          T *ptrr = real.data(x,0,z,c), *ptri = imag.data(x,0,z,c);
+          double *ptrd = (double*)data_in;
+          cimg_forY(real,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._height;
+          if (invert) cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }
+          else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }
+        }
+      } break;
+      case 'z' : { // Fourier along Z, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth);
+        data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        const unsigned int off = real._width*real._height;
+        cimg_forXYC(real,x,y,c) {
+          T *ptrr = real.data(x,y,0,c), *ptri = imag.data(x,y,0,c);
+          double *ptrd = (double*)data_in;
+          cimg_forZ(real,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._depth;
+          if (invert) cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }
+          else cimg_forZ(real,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }
+        }
+      } break;
+      default : { // Fourier along C, using FFTW library.
+        data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._spectrum);
+        data_plan = fftw_plan_dft_1d(real._spectrum,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+        const unsigned int off = real._width*real._height*real._depth;
+        cimg_forXYZ(real,x,y,z) {
+          T *ptrr = real.data(x,y,z,0), *ptri = imag.data(x,y,z,0);
+          double *ptrd = (double*)data_in;
+          cimg_forC(real,c) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
+          fftw_execute(data_plan);
+          const unsigned int fact = real._spectrum;
+          if (invert) cimg_forC(real,c) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }
+          else cimg_forC(real,c) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }
+        }
+      }
+      }
+      fftw_destroy_plan(data_plan);
+      fftw_free(data_in);
+#else
+      switch (cimg::uncase(axis)) {
+      case 'x' : { // Fourier along X, using built-in functions.
+        const unsigned int N = real._width, N2 = (N>>1);
+        if (((N-1)&N) && N!=1)
+          throw CImgInstanceException("CImgList<%s>::FFT() : Specified real and imaginary parts (%u,%u,%u,%u) have non 2^N dimension along the X-axis.",
+                                      pixel_type(),
+                                      real._width,real._height,real._depth,real._spectrum);
+
+        for (unsigned int i = 0, j = 0; i<N2; ++i) {
+          if (j>i) cimg_forYZC(real,y,z,c) {
+            cimg::swap(real(i,y,z,c),real(j,y,z,c)); cimg::swap(imag(i,y,z,c),imag(j,y,z,c));
+            if (j<N2) {
+              const unsigned int ri = N-1-i, rj = N-1-j;
+              cimg::swap(real(ri,y,z,c),real(rj,y,z,c)); cimg::swap(imag(ri,y,z,c),imag(rj,y,z,c));
+            }
+          }
+          for (unsigned int m = N, n = N2; (j+=n)>=m; j-=m, m = n, n>>=1) {}
+        }
+        for (unsigned int delta = 2; delta<=N; delta<<=1) {
+          const unsigned int delta2 = (delta>>1);
+          for (unsigned int i = 0; i<N; i+=delta) {
+            float wr = 1, wi = 0;
+            const float angle = (float)((invert?+1:-1)*2*cimg::PI/delta),
+                        ca = (float)std::cos(angle),
+                        sa = (float)std::sin(angle);
+            for (unsigned int k = 0; k<delta2; ++k) {
+              const unsigned int j = i + k, nj = j + delta2;
+              cimg_forYZC(real,y,z,c) {
+                T &ir = real(j,y,z,c), &ii = imag(j,y,z,c), &nir = real(nj,y,z,c), &nii = imag(nj,y,z,c);
+                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
+                nir = (T)(ir - tmpr);
+                nii = (T)(ii - tmpi);
+                ir+=(T)tmpr;
+                ii+=(T)tmpi;
+              }
+              const float nwr = wr*ca-wi*sa;
+              wi = wi*ca + wr*sa;
+              wr = nwr;
+            }
+          }
+        }
+        if (invert) { real/=N; imag/=N; }
+      } break;
+      case 'y' : { // Fourier along Y, using built-in functions.
+        const unsigned int N = real._height, N2 = (N>>1);
+        if (((N-1)&N) && N!=1)
+          throw CImgInstanceException("CImgList<%s>::FFT() : Specified real and imaginary parts (%u,%u,%u,%u) have non 2^N dimension along the Y-axis.",
+                                      pixel_type(),
+                                      real._width,real._height,real._depth,real._spectrum);
+
+        for (unsigned int i = 0, j = 0; i<N2; ++i) {
+          if (j>i) cimg_forXZC(real,x,z,c) {
+            cimg::swap(real(x,i,z,c),real(x,j,z,c)); cimg::swap(imag(x,i,z,c),imag(x,j,z,c));
+            if (j<N2) {
+              const unsigned int ri = N - 1 - i, rj = N - 1 - j;
+              cimg::swap(real(x,ri,z,c),real(x,rj,z,c)); cimg::swap(imag(x,ri,z,c),imag(x,rj,z,c));
+            }
+          }
+          for (unsigned int m = N, n = N2; (j+=n)>=m; j-=m, m = n, n>>=1) {}
+        }
+        for (unsigned int delta = 2; delta<=N; delta<<=1) {
+          const unsigned int delta2 = (delta>>1);
+          for (unsigned int i = 0; i<N; i+=delta) {
+            float wr = 1, wi = 0;
+            const float angle = (float)((invert?+1:-1)*2*cimg::PI/delta),
+                        ca = (float)std::cos(angle), sa = (float)std::sin(angle);
+            for (unsigned int k = 0; k<delta2; ++k) {
+              const unsigned int j = i + k, nj = j + delta2;
+              cimg_forXZC(real,x,z,c) {
+                T &ir = real(x,j,z,c), &ii = imag(x,j,z,c), &nir = real(x,nj,z,c), &nii = imag(x,nj,z,c);
+                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
+                nir = (T)(ir - tmpr);
+                nii = (T)(ii - tmpi);
+                ir+=(T)tmpr;
+                ii+=(T)tmpi;
+              }
+              const float nwr = wr*ca-wi*sa;
+              wi = wi*ca + wr*sa;
+              wr = nwr;
+            }
+          }
+        }
+        if (invert) { real/=N; imag/=N; }
+      } break;
+      case 'z' : { // Fourier along Z, using built-in functions.
+        const unsigned int N = real._depth, N2 = (N>>1);
+        if (((N-1)&N) && N!=1)
+          throw CImgInstanceException("CImgList<%s>::FFT() : Specified real and imaginary parts (%u,%u,%u,%u) have non 2^N dimension along the Z-axis.",
+                                      pixel_type(),
+                                      real._width,real._height,real._depth,real._spectrum);
+
+        for (unsigned int i = 0, j = 0; i<N2; ++i) {
+          if (j>i) cimg_forXYC(real,x,y,c) {
+            cimg::swap(real(x,y,i,c),real(x,y,j,c)); cimg::swap(imag(x,y,i,c),imag(x,y,j,c));
+            if (j<N2) {
+              const unsigned int ri = N - 1 - i, rj = N - 1 - j;
+              cimg::swap(real(x,y,ri,c),real(x,y,rj,c)); cimg::swap(imag(x,y,ri,c),imag(x,y,rj,c));
+            }
+          }
+          for (unsigned int m = N, n = N2; (j+=n)>=m; j-=m, m = n, n>>=1) {}
+        }
+        for (unsigned int delta = 2; delta<=N; delta<<=1) {
+          const unsigned int delta2 = (delta>>1);
+          for (unsigned int i = 0; i<N; i+=delta) {
+            float wr = 1, wi = 0;
+            const float angle = (float)((invert?+1:-1)*2*cimg::PI/delta),
+                        ca = (float)std::cos(angle), sa = (float)std::sin(angle);
+            for (unsigned int k = 0; k<delta2; ++k) {
+              const unsigned int j = i + k, nj = j + delta2;
+              cimg_forXYC(real,x,y,c) {
+                T &ir = real(x,y,j,c), &ii = imag(x,y,j,c), &nir = real(x,y,nj,c), &nii = imag(x,y,nj,c);
+                const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
+                nir = (T)(ir - tmpr);
+                nii = (T)(ii - tmpi);
+                ir+=(T)tmpr;
+                ii+=(T)tmpi;
+              }
+              const float nwr = wr*ca-wi*sa;
+              wi = wi*ca + wr*sa;
+              wr = nwr;
+            }
+          }
+        }
+        if (invert) { real/=N; imag/=N; }
+      } break;
+      default :
+        throw CImgArgumentException("CImgList<%s>::FFT() : Invalid specified axis '%c' for real and imaginary parts (%u,%u,%u,%u) "
+                                    "(should be { x | y | z }).",
+                                    pixel_type(),axis,
+                                    real._width,real._height,real._depth,real._spectrum);
+      }
+#endif
+    }
+
+    //! Compute a n-d Fast Fourier Transform.
+    static void FFT(CImg<T>& real, CImg<T>& imag, const bool invert=false) {
+      if (!real)
+        throw CImgInstanceException("CImgList<%s>::FFT() : Empty specified real part.",
+                                    pixel_type());
+
+      if (!imag) imag.assign(real._width,real._height,real._depth,real._spectrum,0);
+      if (!real.is_sameXYZC(imag))
+        throw CImgInstanceException("CImgList<%s>::FFT() : Specified real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p) have different dimensions.",
+                                    pixel_type(),
+                                    real._width,real._height,real._depth,real._spectrum,real._data,
+                                    imag._width,imag._height,imag._depth,imag._spectrum,imag._data);
+
+#ifdef cimg_use_fftw3
+      fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth);
+      fftw_plan data_plan;
+      const unsigned int w = real._width, wh = w*real._height, whd = wh*real._depth;
+      data_plan = fftw_plan_dft_3d(real._width,real._height,real._depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+      cimg_forC(real,c) {
+        T *ptrr = real.data(0,0,0,c), *ptri = imag.data(0,0,0,c);
+        double *ptrd = (double*)data_in;
+        for (unsigned int x = 0; x<real._width; ++x, ptrr-=wh-1, ptri-=wh-1)
+          for (unsigned int y = 0; y<real._height; ++y, ptrr-=whd-w, ptri-=whd-w)
+            for (unsigned int z = 0; z<real._depth; ++z, ptrr+=wh, ptri+=wh) {
+              *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri;
+            }
+        fftw_execute(data_plan);
+        ptrd = (double*)data_in;
+        ptrr = real.data(0,0,0,c);
+        ptri = imag.data(0,0,0,c);
+        if (!invert) for (unsigned int x = 0; x<real._width; ++x, ptrr-=wh-1, ptri-=wh-1)
+          for (unsigned int y = 0; y<real._height; ++y, ptrr-=whd-w, ptri-=whd-w)
+            for (unsigned int z = 0; z<real._depth; ++z, ptrr+=wh, ptri+=wh) {
+              *ptrr = (T)*(ptrd++); *ptri = (T)*(ptrd++);
+            }
+        else for (unsigned int x = 0; x<real._width; ++x, ptrr-=wh-1, ptri-=wh-1)
+          for (unsigned int y = 0; y<real._height; ++y, ptrr-=whd-w, ptri-=whd-w)
+            for (unsigned int z = 0; z<real._depth; ++z, ptrr+=wh, ptri+=wh) {
+              *ptrr = (T)(*(ptrd++)/whd); *ptri = (T)(*(ptrd++)/whd);
+            }
+      }
+      fftw_destroy_plan(data_plan);
+      fftw_free(data_in);
+#else
+      if (real._depth>1)  FFT(real,imag,'z',invert);
+      if (real._height>1) FFT(real,imag,'y',invert);
+      if (real._width>1)  FFT(real,imag,'x',invert);
+#endif
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name 3d Objects Management
+    //@{
+    //-------------------------------------
+
+    //! Shift a 3d object.
+    CImg<T>& shift_object3d(const float tx, const float ty=0, const float tz=0) {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "shift_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      get_shared_line(0)+=tx; get_shared_line(1)+=ty; get_shared_line(2)+=tz;
+      return *this;
+    }
+
+    CImg<Tfloat> get_shift_object3d(const float tx, const float ty=0, const float tz=0) const {
+      return CImg<Tfloat>(*this,false).shift_object3d(tx,ty,tz);
+    }
+
+    //! Shift a 3d object so that it becomes centered.
+    CImg<T>& shift_object3d() {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "shift_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+      float xm, xM = (float)xcoords.max_min(xm), ym, yM = (float)ycoords.max_min(ym), zm, zM = (float)zcoords.max_min(zm);
+      xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2;
+      return *this;
+    }
+
+    CImg<Tfloat> get_shift_object3d() const {
+      return CImg<Tfloat>(*this,false).shift_object3d();
+    }
+
+    //! Resize a 3d object.
+    CImg<T>& resize_object3d(const float sx, const float sy=-100, const float sz=-100) {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "resize_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+      float xm, xM = (float)xcoords.max_min(xm), ym, yM = (float)ycoords.max_min(ym), zm, zM = (float)zcoords.max_min(zm);
+      if (xm<xM) { if (sx>0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; }
+      if (ym<yM) { if (sy>0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; }
+      if (zm<zM) { if (sz>0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; }
+      return *this;
+    }
+
+    CImg<Tfloat> get_resize_object3d(const float sx, const float sy=-100, const float sz=-100) const {
+      return CImg<Tfloat>(*this,false).resize_object3d(sx,sy,sz);
+    }
+
+    //! Resize a 3d object so that its max dimension if one.
+    CImg<T> resize_object3d() const {
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "resize_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
+      float xm, xM = (float)xcoords.max_min(xm), ym, yM = (float)ycoords.max_min(ym), zm, zM = (float)zcoords.max_min(zm);
+      const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz);
+      if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; }
+      return *this;
+    }
+
+    CImg<Tfloat> get_resize_object3d() const {
+      return CImg<Tfloat>(*this,false).resize_object3d();
+    }
+
+    //! Append a 3d object to another one.
+    template<typename tf, typename tp, typename tff>
+    CImg<T>& append_object3d(CImgList<tf>& primitives, const CImg<tp>& obj_vertices, const CImgList<tff>& obj_primitives) {
+      if (!obj_vertices || !obj_primitives) return *this;
+      if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "append_object3d() : Specified vertice image (%u,%u,%u,%u,%p) is not a set of 3d vertices.",
+                                    cimg_instance,
+                                    obj_vertices._width,obj_vertices._height,obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data);
+
+      if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); }
+      if (_height!=3 || _depth>1 || _spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "append_object3d() : Instance is not a set of 3d vertices.",
+                                    cimg_instance);
+
+      const unsigned int P = _width;
+      append(obj_vertices,'x');
+      const unsigned int N = primitives._width;
+      primitives.insert(obj_primitives);
+      for (unsigned int i = N; i<primitives._width; ++i) {
+        CImg<tf> &p = primitives[i];
+        switch (p.size()) {
+        case 1 : p[0]+=P; break; // Point.
+        case 5 : p[0]+=P; p[1]+=P; break; // Sphere.
+        case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment.
+        case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle.
+        case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle.
+        }
+      }
+      return *this;
+    }
+
+    //! Create and return a 3d elevation of the instance image.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param[out] colors The returned list of the 3d object colors.
+       \param elevation The input elevation map.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg");
+       CImgList<unsigned int> faces3d;
+       CImgList<unsigned char> colors3d;
+       const CImg<float> points3d = img.get_elevation3d(faces3d,colors,img.get_norm()*0.2);
+       CImg<unsigned char>().display_object3d(points3d,faces3d,colors3d);
+       \endcode
+       \image html ref_elevation3d.jpg
+    **/
+    template<typename tf, typename tc, typename te>
+    CImg<floatT> get_elevation3d(CImgList<tf>& primitives, CImgList<tc>& colors, const CImg<te>& elevation) const {
+      if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1)
+        throw CImgArgumentException(_cimg_instance
+                                    "get_elevation3d() : Instance and specified elevation (%u,%u,%u,%u,%p) "
+                                    "have incompatible dimensions.",
+                                    cimg_instance,
+                                    elevation._width,elevation._height,elevation._depth,elevation._spectrum,elevation._data);
+
+      if (is_empty()) return *this;
+      float m, M = (float)max_min(m);
+      if (M==m) ++M;
+      colors.assign();
+      const unsigned int size_x1 = _width - 1, size_y1 = _height - 1;
+      for (unsigned int y = 0; y<size_y1; ++y)
+        for (unsigned int x = 0; x<size_x1; ++x) {
+          const unsigned char
+            r = (unsigned char)(((*this)(x,y,0) - m)*255/(M-m)),
+            g = _spectrum>1?(unsigned char)(((*this)(x,y,1) - m)*255/(M-m)):r,
+            b = _spectrum>2?(unsigned char)(((*this)(x,y,2) - m)*255/(M-m)):(_spectrum>1?0:r);
+          CImg<tc>::vector((tc)r,(tc)g,(tc)b).move_to(colors);
+        }
+      const typename CImg<te>::_functor2d_int func(elevation);
+      return elevation3d(primitives,func,0,0,_width-1.0f,_height-1.0f,_width,_height);
+    }
+
+    //! Create and return a isoline of the instance image as a 3d object.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param isovalue The returned list of the 3d object colors.
+       \param size_x The number of subdivisions along the X-axis.
+       \param size_y The number of subdisivions along the Y-axis.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       const CImg<float> img("reference.jpg");
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = img.get_isoline3d(faces3d,100);
+       CImg<unsigned char>().display_object3d(points3d,faces3d,colors3d);
+       \endcode
+       \image html ref_isoline3d.jpg
+    **/
+    template<typename tf>
+    CImg<floatT> get_isoline3d(CImgList<tf>& primitives, const float isovalue,
+                               const int size_x=-100, const int size_y=-100) const {
+      if (_spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "get_isoline3d() : Instance is not a scalar image.",
+                                    cimg_instance);
+      primitives.assign();
+      if (is_empty()) return *this;
+      CImg<floatT> vertices;
+      if ((size_x==-100 && size_y==-100) || (size_x==width() && size_y==height())) {
+        const _functor2d_int func(*this);
+        vertices = isoline3d(primitives,func,isovalue,0,0,width()-1.0f,height()-1.0f,size_x,size_y);
+      } else {
+        const _functor2d_float func(*this);
+        vertices = isoline3d(primitives,func,isovalue,0,0,width()-1.0f,height()-1.0f,size_x,size_y);
+      }
+      return vertices;
+    }
+
+    //! Create and return a isosurface of the instance image as a 3d object.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param isovalue The returned list of the 3d object colors.
+       \param size_x The number of subdivisions along the X-axis.
+       \param size_y The number of subdisivions along the Y-axis.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       const CImg<float> img = CImg<unsigned char>("reference.jpg").resize(-100,-100,20);
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = img.get_isosurface3d(faces3d,100);
+       CImg<unsigned char>().display_object3d(points3d,faces3d,colors3d);
+       \endcode
+       \image html ref_isosurface3d.jpg
+    **/
+    template<typename tf>
+    CImg<floatT> get_isosurface3d(CImgList<tf>& primitives, const float isovalue,
+                                  const int size_x=-100, const int size_y=-100, const int size_z=-100) const {
+      if (_spectrum>1)
+        throw CImgInstanceException(_cimg_instance
+                                    "get_isosurface3d() : Instance is not a scalar image.",
+                                    cimg_instance);
+      primitives.assign();
+      if (is_empty()) return *this;
+      CImg<floatT> vertices;
+      if ((size_x==-100 && size_y==-100 && size_z==-100) || (size_x==width() && size_y==height() && size_z==depth())) {
+        const _functor3d_int func(*this);
+        vertices = isosurface3d(primitives,func,isovalue,0,0,0,width()-1.0f,height()-1.0f,depth()-1.0f,size_x,size_y,size_z);
+      } else {
+        const _functor3d_float func(*this);
+        vertices = isosurface3d(primitives,func,isovalue,0,0,0,width()-1.0f,height()-1.0f,depth()-1.0f,size_x,size_y,size_z);
+      }
+      return vertices;
+    }
+
+    //! Get elevation3d of a function.
+    template<typename tf, typename tfunc>
+    static CImg<floatT> elevation3d(CImgList<tf>& primitives, const tfunc& func,
+                                    const float x0, const float y0, const float x1, const float y1,
+                                    const int size_x=256, const int size_y=256) {
+      const float
+        nx0 = x0<x1?x0:x1, ny0 = y0<y1?y0:y1,
+        nx1 = x0<x1?x1:x0, ny1 = y0<y1?y1:y0;
+      const unsigned int
+        _nsize_x = (unsigned int)(size_x>=0?size_x:(nx1-nx0)*-size_x/100), nsize_x = _nsize_x?_nsize_x:1, nsize_x1 = nsize_x - 1,
+        _nsize_y = (unsigned int)(size_y>=0?size_y:(ny1-ny0)*-size_y/100), nsize_y = _nsize_y?_nsize_y:1, nsize_y1 = nsize_y - 1;
+      if (nsize_x<2 || nsize_y<2)
+        throw CImgArgumentException("CImg<%s>::elevation3d() : Invalid specified size (%d,%d).",
+                                    pixel_type(),
+                                    nsize_x,nsize_y);
+
+      CImg<floatT> vertices(nsize_x*nsize_y,3);
+      floatT *ptr_x = vertices.data(0,0), *ptr_y = vertices.data(0,1), *ptr_z = vertices.data(0,2);
+      for (unsigned int y = 0; y<nsize_y; ++y) {
+        const float Y = ny0 + y*(ny1-ny0)/nsize_y1;
+        for (unsigned int x = 0; x<nsize_x; ++x) {
+          const float X = nx0 + x*(nx1-nx0)/nsize_x1;
+          *(ptr_x++) = (float)x;
+          *(ptr_y++) = (float)y;
+          *(ptr_z++) = (float)func(X,Y);
+        }
+      }
+      primitives.assign(nsize_x1*nsize_y1,1,4);
+      for (unsigned int p = 0, y = 0; y<nsize_y1; ++y) {
+        const unsigned int yw = y*nsize_x;
+        for (unsigned int x = 0; x<nsize_x1; ++x) {
+          const unsigned int xpyw = x + yw, xpyww = xpyw + nsize_x;
+          primitives[p++].fill(xpyw,xpyww,xpyww+1,xpyw+1);
+        }
+      }
+      return vertices;
+    }
+
+    template<typename tf>
+    static CImg<floatT> elevation3d(CImgList<tf>& primitives, const char *const expression,
+                                    const float x0, const float y0, const float x1, const float y1,
+                                    const int sizex=256, const int sizey=256) {
+      const _functor2d_expr func(expression);
+      return elevation3d(primitives,func,x0,y0,x1,y1,sizex,sizey);
+    }
+
+    //! Get isoline as a 3d object.
+    template<typename tf, typename tfunc>
+    static CImg<floatT> isoline3d(CImgList<tf>& primitives, const tfunc& func, const float isovalue,
+                                  const float x0, const float y0, const float x1, const float y1,
+                                  const int sizex=256, const int sizey=256) {
+      static const unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 };
+      static const int segments[16][4] = { { -1,-1,-1,-1 }, { 0,3,-1,-1 }, { 0,1,-1,-1 }, { 1,3,-1,-1 },
+                                           { 1,2,-1,-1 },   { 0,1,2,3 },   { 0,2,-1,-1 }, { 2,3,-1,-1 },
+                                           { 2,3,-1,-1 },   { 0,2,-1,-1},  { 0,3,1,2 },   { 1,2,-1,-1 },
+                                           { 1,3,-1,-1 },   { 0,1,-1,-1},  { 0,3,-1,-1},  { -1,-1,-1,-1 } };
+      const unsigned int
+        _nx = (unsigned int)(sizex>=0?sizex:((x1-x0)*-sizex/100 + 1)),
+        _ny = (unsigned int)(sizey>=0?sizey:((y1-y0)*-sizey/100 + 1)),
+        nx = _nx?_nx:1,
+        ny = _ny?_ny:1,
+        nxm1 = nx - 1,
+        nym1 = ny - 1;
+      primitives.assign();
+      if (!nxm1 || !nym1) return CImg<floatT>();
+      const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1;
+      CImgList<floatT> vertices;
+      CImg<intT> indices1(nx,1,1,2,-1), indices2(nx,1,1,2);
+      CImg<floatT> values1(nx), values2(nx);
+      float X = x0, Y = y0, nX = X + dx, nY = Y + dy;
+
+      // Fill first line with values
+      cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=dx; }
+
+      // Run the marching squares algorithm
+      for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y=nY, nY+=dy) {
+        X = x0; nX = X + dx;
+        indices2.fill(-1);
+        for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X=nX, nX+=dx) {
+
+          // Determine square configuration
+          const float
+            val0 = values1(xi),
+            val1 = values1(nxi),
+            val2 = values2(nxi) = (float)func(nX,nY),
+            val3 = values2(xi) = (float)func(X,nY);
+          const unsigned int
+            configuration = (val0<isovalue?1:0)  | (val1<isovalue?2:0)  | (val2<isovalue?4:0)  | (val3<isovalue?8:0),
+            edge = edges[configuration];
+
+          // Compute intersection vertices
+          if (edge) {
+            if ((edge&1) && indices1(xi,0)<0) {
+              const float Xi = X + (isovalue-val0)*dx/(val1-val0);
+              indices1(xi,0) = vertices._width;
+              CImg<floatT>::vector(Xi,Y,0).move_to(vertices);
+            }
+            if ((edge&2) && indices1(nxi,1)<0) {
+              const float Yi = Y + (isovalue-val1)*dy/(val2-val1);
+              indices1(nxi,1) = vertices._width;
+              CImg<floatT>::vector(nX,Yi,0).move_to(vertices);
+            }
+            if ((edge&4) && indices2(xi,0)<0) {
+              const float Xi = X + (isovalue-val3)*dx/(val2-val3);
+              indices2(xi,0) = vertices._width;
+              CImg<floatT>::vector(Xi,nY,0).move_to(vertices);
+            }
+            if ((edge&8) && indices1(xi,1)<0) {
+              const float Yi = Y + (isovalue-val0)*dy/(val3-val0);
+              indices1(xi,1) = vertices._width;
+              CImg<floatT>::vector(X,Yi,0).move_to(vertices);
+            }
+
+            // Create segments
+            for (const int *segment = segments[configuration]; *segment!=-1; ) {
+              const unsigned int p0 = *(segment++), p1 = *(segment++);
+              const tf
+                i0 = (tf)(_isoline3d_indice(p0,indices1,indices2,xi,nxi)),
+                i1 = (tf)(_isoline3d_indice(p1,indices1,indices2,xi,nxi));
+              CImg<tf>::vector(i0,i1).move_to(primitives);
+            }
+          }
+        }
+        values1.swap(values2);
+        indices1.swap(indices2);
+      }
+      return vertices>'x';
+    }
+
+    template<typename tf>
+    static CImg<floatT> isoline3d(CImgList<tf>& primitives, const char *const expression, const float isovalue,
+                                  const float x0, const float y0, const float x1, const float y1,
+                                  const int sizex=256, const int sizey=256) {
+      const _functor2d_expr func(expression);
+      return isoline3d(primitives,func,isovalue,x0,y0,x1,y1,sizex,sizey);
+    }
+
+    template<typename t>
+    static int _isoline3d_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
+                                 const unsigned int x, const unsigned int nx) {
+      switch (edge) {
+      case 0 : return (int)indices1(x,0);
+      case 1 : return (int)indices1(nx,1);
+      case 2 : return (int)indices2(x,0);
+      case 3 : return (int)indices1(x,1);
+      }
+      return 0;
+    }
+
+    //! Get isosurface as a 3d object.
+    template<typename tf, typename tfunc>
+    static CImg<floatT> isosurface3d(CImgList<tf>& primitives, const tfunc& func, const float isovalue,
+                                     const float x0, const float y0, const float z0,
+                                     const float x1, const float y1, const float z1,
+                                     const int size_x=32, const int size_y=32, const int size_z=32) {
+      static unsigned int edges[256] = {
+        0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
+        0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
+        0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
+        0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
+        0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
+        0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
+        0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
+        0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
+        0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
+        0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
+        0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
+        0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
+        0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
+        0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
+        0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
+        0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 };
+
+      static int triangles[256][16] = {
+        { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
+        { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
+        { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
+        { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
+        { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 },
+        { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 },
+        { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
+        { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 },
+        { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 },
+        { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 },
+        { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 },
+        { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
+        { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
+        { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 },
+        { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 },
+        { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 },
+        { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
+        { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
+        { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
+        { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 },
+        { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 },
+        { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
+        { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 },
+        { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 },
+        { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 },
+        { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 },
+        { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
+        { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 },
+        { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
+        { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 },
+        { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 },
+        { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 },
+        { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 },
+        { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 },
+        { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 },
+        { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 },
+        { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
+        { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
+        { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 },
+        { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
+        { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 },
+        { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 },
+        { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
+        { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
+        { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 },
+        { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 },
+        { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 },
+        { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
+        { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
+        { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
+        { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 },
+        { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 },
+        { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 },
+        { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 },
+        { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 },
+        { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 },
+        { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 },
+        { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 },
+        { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 },
+        { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 },
+        { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 },
+        { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 },
+        { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 },
+        { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 },
+        { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 },
+        { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 },
+        { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 },
+        { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 },
+        { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 },
+        { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 },
+        { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
+        { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 },
+        { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 },
+        { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 },
+        { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 },
+        { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 },
+        { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 },
+        { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 },
+        { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 },
+        { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+        { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }
+      };
+
+      const unsigned int
+        _nx = (unsigned int)(size_x>=0?size_x:((x1-x0)*-size_x/100 + 1)),
+        _ny = (unsigned int)(size_y>=0?size_y:((y1-y0)*-size_y/100 + 1)),
+        _nz = (unsigned int)(size_z>=0?size_y:((z1-z0)*-size_z/100 + 1)),
+        nx = _nx?_nx:1,
+        ny = _ny?_ny:1,
+        nz = _nz?_nz:1,
+        nxm1 = nx - 1,
+        nym1 = ny - 1,
+        nzm1 = nz - 1;
+      primitives.assign();
+      if (!nxm1 || !nym1 || !nzm1) return CImg<floatT>();
+      const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1, dz = (z1 - z0)/nzm1;
+      CImgList<floatT> vertices;
+      CImg<intT> indices1(nx,ny,1,3,-1), indices2(indices1);
+      CImg<floatT> values1(nx,ny), values2(nx,ny);
+      float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0;
+
+      // Fill the first plane with function values
+      Y = y0;
+      cimg_forY(values1,y) {
+        X = x0;
+        cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=dx; }
+        Y+=dy;
+      }
+
+      // Run Marching Cubes algorithm
+      Z = z0; nZ = Z + dz;
+      for (unsigned int zi = 0; zi<nzm1; ++zi, Z = nZ, nZ+=dz) {
+        Y = y0; nY = Y + dy;
+        indices2.fill(-1);
+        for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y = nY, nY+=dy) {
+          X = x0; nX = X + dx;
+          for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X = nX, nX+=dx) {
+
+            // Determine cube configuration
+            const float
+              val0 = values1(xi,yi),
+              val1 = values1(nxi,yi),
+              val2 = values1(nxi,nyi),
+              val3 = values1(xi,nyi),
+              val4 = values2(xi,yi) = (float)func(X,Y,nZ),
+              val5 = values2(nxi,yi) = (float)func(nX,Y,nZ),
+              val6 = values2(nxi,nyi) = (float)func(nX,nY,nZ),
+              val7 = values2(xi,nyi) = (float)func(X,nY,nZ);
+
+            const unsigned int configuration =
+              (val0<isovalue?1:0)  | (val1<isovalue?2:0)  | (val2<isovalue?4:0)  | (val3<isovalue?8:0) |
+              (val4<isovalue?16:0) | (val5<isovalue?32:0) | (val6<isovalue?64:0) | (val7<isovalue?128:0),
+              edge = edges[configuration];
+
+            // Compute intersection vertices
+            if (edge) {
+              if ((edge&1) && indices1(xi,yi,0)<0) {
+                const float Xi = X + (isovalue-val0)*dx/(val1-val0);
+                indices1(xi,yi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,Y,Z).move_to(vertices);
+              }
+              if ((edge&2) && indices1(nxi,yi,1)<0) {
+                const float Yi = Y + (isovalue-val1)*dy/(val2-val1);
+                indices1(nxi,yi,1) = vertices._width;
+                CImg<floatT>::vector(nX,Yi,Z).move_to(vertices);
+              }
+              if ((edge&4) && indices1(xi,nyi,0)<0) {
+                const float Xi = X + (isovalue-val3)*dx/(val2-val3);
+                indices1(xi,nyi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,nY,Z).move_to(vertices);
+              }
+              if ((edge&8) && indices1(xi,yi,1)<0) {
+                const float Yi = Y + (isovalue-val0)*dy/(val3-val0);
+                indices1(xi,yi,1) = vertices._width;
+                CImg<floatT>::vector(X,Yi,Z).move_to(vertices);
+              }
+              if ((edge&16) && indices2(xi,yi,0)<0) {
+                const float Xi = X + (isovalue-val4)*dx/(val5-val4);
+                indices2(xi,yi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,Y,nZ).move_to(vertices);
+              }
+              if ((edge&32) && indices2(nxi,yi,1)<0) {
+                const float Yi = Y + (isovalue-val5)*dy/(val6-val5);
+                indices2(nxi,yi,1) = vertices._width;
+                CImg<floatT>::vector(nX,Yi,nZ).move_to(vertices);
+              }
+              if ((edge&64) && indices2(xi,nyi,0)<0) {
+                const float Xi = X + (isovalue-val7)*dx/(val6-val7);
+                indices2(xi,nyi,0) = vertices._width;
+                CImg<floatT>::vector(Xi,nY,nZ).move_to(vertices);
+              }
+              if ((edge&128) && indices2(xi,yi,1)<0)  {
+                const float Yi = Y + (isovalue-val4)*dy/(val7-val4);
+                indices2(xi,yi,1) = vertices._width;
+                CImg<floatT>::vector(X,Yi,nZ).move_to(vertices);
+              }
+              if ((edge&256) && indices1(xi,yi,2)<0) {
+                const float Zi = Z+ (isovalue-val0)*dz/(val4-val0);
+                indices1(xi,yi,2) = vertices._width;
+                CImg<floatT>::vector(X,Y,Zi).move_to(vertices);
+              }
+              if ((edge&512) && indices1(nxi,yi,2)<0)  {
+                const float Zi = Z + (isovalue-val1)*dz/(val5-val1);
+                indices1(nxi,yi,2) = vertices._width;
+                CImg<floatT>::vector(nX,Y,Zi).move_to(vertices);
+              }
+              if ((edge&1024) && indices1(nxi,nyi,2)<0) {
+                const float Zi = Z + (isovalue-val2)*dz/(val6-val2);
+                indices1(nxi,nyi,2) = vertices._width;
+                CImg<floatT>::vector(nX,nY,Zi).move_to(vertices);
+              }
+              if ((edge&2048) && indices1(xi,nyi,2)<0) {
+                const float Zi = Z + (isovalue-val3)*dz/(val7-val3);
+                indices1(xi,nyi,2) = vertices._width;
+                CImg<floatT>::vector(X,nY,Zi).move_to(vertices);
+              }
+
+              // Create triangles
+              for (const int *triangle = triangles[configuration]; *triangle!=-1; ) {
+                const unsigned int p0 = *(triangle++), p1 = *(triangle++), p2 = *(triangle++);
+                const tf
+                  i0 = (tf)(_isosurface3d_indice(p0,indices1,indices2,xi,yi,nxi,nyi)),
+                  i1 = (tf)(_isosurface3d_indice(p1,indices1,indices2,xi,yi,nxi,nyi)),
+                  i2 = (tf)(_isosurface3d_indice(p2,indices1,indices2,xi,yi,nxi,nyi));
+                CImg<tf>::vector(i0,i2,i1).move_to(primitives);
+              }
+            }
+          }
+        }
+        cimg::swap(values1,values2);
+        cimg::swap(indices1,indices2);
+      }
+      return vertices>'x';
+    }
+
+    template<typename tf>
+    static CImg<floatT> isosurface3d(CImgList<tf>& primitives, const char *const expression, const float isovalue,
+                                     const float x0, const float y0, const float z0,
+                                     const float x1, const float y1, const float z1,
+                                     const int dx=32, const int dy=32, const int dz=32) {
+      const _functor3d_expr func(expression);
+      return isosurface3d(primitives,func,isovalue,x0,y0,z0,x1,y1,z1,dx,dy,dz);
+    }
+
+    template<typename t>
+    static int _isosurface3d_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
+                                    const unsigned int x, const unsigned int y, const unsigned int nx, const unsigned int ny) {
+      switch (edge) {
+      case 0 : return indices1(x,y,0);
+      case 1 : return indices1(nx,y,1);
+      case 2 : return indices1(x,ny,0);
+      case 3 : return indices1(x,y,1);
+      case 4 : return indices2(x,y,0);
+      case 5 : return indices2(nx,y,1);
+      case 6 : return indices2(x,ny,0);
+      case 7 : return indices2(x,y,1);
+      case 8 : return indices1(x,y,2);
+      case 9 : return indices1(nx,y,2);
+      case 10 : return indices1(nx,ny,2);
+      case 11 : return indices1(x,ny,2);
+      }
+      return 0;
+    }
+
+    // Define functors for accessing image values (used in previous functions).
+    struct _functor2d_int {
+      const CImg<T>& ref;
+      _functor2d_int(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y) const {
+        return (float)ref((int)x,(int)y);
+      }
+    };
+
+    struct _functor2d_float {
+      const CImg<T>& ref;
+      _functor2d_float(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y) const {
+        return (float)ref._linear_atXY(x,y);
+      }
+    };
+
+    struct _functor2d_expr {
+      _cimg_math_parser *mp;
+      _functor2d_expr(const char *const expr):mp(0) { mp = new _cimg_math_parser(CImg<T>::empty(),expr,0); }
+      ~_functor2d_expr() { if (mp) delete mp; }
+      float operator()(const float x, const float y) const {
+        return (float)mp->eval(x,y,0,0);
+      }
+    };
+
+    struct _functor3d_int {
+      const CImg<T>& ref;
+      _functor3d_int(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z) const {
+        return (float)ref((int)x,(int)y,(int)z);
+      }
+    };
+
+    struct _functor3d_float {
+      const CImg<T>& ref;
+      _functor3d_float(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z) const {
+        return (float)ref._linear_atXYZ(x,y,z);
+      }
+    };
+
+    struct _functor3d_expr {
+      _cimg_math_parser *mp;
+      ~_functor3d_expr() { if (mp) delete mp; }
+      _functor3d_expr(const char *const expr):mp(0) { mp = new _cimg_math_parser(CImg<T>::empty(),expr,0); }
+      float operator()(const float x, const float y, const float z) const {
+        return (float)mp->eval(x,y,z,0);
+      }
+    };
+
+    struct _functor4d_int {
+      const CImg<T>& ref;
+      _functor4d_int(const CImg<T>& pref):ref(pref) {}
+      float operator()(const float x, const float y, const float z, const unsigned int c) const {
+        return (float)ref((int)x,(int)y,(int)z,c);
+      }
+    };
+
+    //! Create and return a 3d box object.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param size_x The width of the box (dimension along the X-axis).
+       \param size_y The height of the box (dimension along the Y-axis).
+       \param size_z The depth of the box (dimension along the Z-axis).
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::box3d(faces3d,10,20,30);
+       CImg<unsigned char>().display_object3d(points3d,faces3d);
+       \endcode
+       \image html ref_box3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> box3d(CImgList<tf>& primitives,
+                              const float size_x=200, const float size_y=100, const float size_z=100) {
+      primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
+      return CImg<floatT>(8,3,1,1,
+                          0.,size_x,size_x,    0.,    0.,size_x,size_x,    0.,
+                          0.,    0.,size_y,size_y,    0.,    0.,size_y,size_y,
+                          0.,    0.,    0.,    0.,size_z,size_z,size_z,size_z);
+    }
+
+    //! Create and return a 3d cone.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius The radius of the cone basis.
+       \param size_z The cone's height.
+       \param subdivisions The number of basis angular subdivisions.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::cone3d(faces3d,50);
+       CImg<unsigned char>().display_object3d(points3d,faces3d);
+       \endcode
+       \image html ref_cone3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> cone3d(CImgList<tf>& primitives,
+                               const float radius=50, const float size_z=100, const unsigned int subdivisions=24) {
+      primitives.assign();
+      if (!subdivisions) return CImg<floatT>();
+      CImgList<floatT> vertices(2,1,3,1,1,
+                                0.,0.,size_z,
+                                0.,0.,0.);
+      for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) {
+        const float a = (float)(angle*cimg::PI/180);
+        CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0).move_to(vertices);
+      }
+      const unsigned int nbr = vertices._width - 2;
+      for (unsigned int p = 0; p<nbr; ++p) {
+        const unsigned int curr = 2 + p, next = 2 + ((p+1)%nbr);
+        CImg<tf>::vector(1,next,curr).move_to(primitives);
+        CImg<tf>::vector(0,curr,next).move_to(primitives);
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d cylinder.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius The radius of the cylinder basis.
+       \param size_z The cylinder's height.
+       \param subdivisions The number of basis angular subdivisions.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::cylinder3d(faces3d,50);
+       CImg<unsigned char>().display_object3d(points3d,faces3d);
+       \endcode
+       \image html ref_cylinder3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> cylinder3d(CImgList<tf>& primitives,
+                                   const float radius=50, const float size_z=100, const unsigned int subdivisions=24) {
+      primitives.assign();
+      if (!subdivisions) return CImg<floatT>();
+      CImgList<floatT> vertices(2,1,3,1,1,
+                                0.,0.,0.,
+                                0.,0.,size_z);
+      for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) {
+        const float a = (float)(angle*cimg::PI/180);
+        CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0.0f).move_to(vertices);
+        CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),size_z).move_to(vertices);
+      }
+      const unsigned int nbr = (vertices._width - 2)/2;
+      for (unsigned int p = 0; p<nbr; ++p) {
+        const unsigned int curr = 2+2*p, next = 2+(2*((p+1)%nbr));
+        CImg<tf>::vector(0,next,curr).move_to(primitives);
+        CImg<tf>::vector(1,curr+1,next+1).move_to(primitives);
+        CImg<tf>::vector(curr,next,next+1,curr+1).move_to(primitives);
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d torus.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius1 The large radius.
+       \param radius2 The small radius.
+       \param subdivisions1 The number of angular subdivisions for the large radius.
+       \param subdivisions2 The number of angular subdivisions for the small radius.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::torus3d(faces3d,20,4);
+       CImg<unsigned char>().display_object3d(points3d,faces3d);
+       \endcode
+       \image html ref_torus3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> torus3d(CImgList<tf>& primitives,
+                                const float radius1=100, const float radius2=30,
+                                const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) {
+      primitives.assign();
+      if (!subdivisions1 || !subdivisions2) return CImg<floatT>();
+      CImgList<floatT> vertices;
+      for (unsigned int v = 0; v<subdivisions1; ++v) {
+        const float
+          beta = (float)(v*2*cimg::PI/subdivisions1),
+          xc = radius1*(float)std::cos(beta),
+          yc = radius1*(float)std::sin(beta);
+        for (unsigned int u = 0; u<subdivisions2; ++u) {
+          const float
+            alpha = (float)(u*2*cimg::PI/subdivisions2),
+            x = xc + radius2*(float)(std::cos(alpha)*std::cos(beta)),
+            y = yc + radius2*(float)(std::cos(alpha)*std::sin(beta)),
+            z = radius2*(float)std::sin(alpha);
+          CImg<floatT>::vector(x,y,z).move_to(vertices);
+        }
+      }
+      for (unsigned int vv = 0; vv<subdivisions1; ++vv) {
+        const unsigned int nv = (vv+1)%subdivisions1;
+        for (unsigned int uu = 0; uu<subdivisions2; ++uu) {
+          const unsigned int nu = (uu+1)%subdivisions2, svv = subdivisions2*vv, snv = subdivisions2*nv;
+          CImg<tf>::vector(svv+nu,svv+uu,snv+uu).move_to(primitives);
+          CImg<tf>::vector(svv+nu,snv+uu,snv+nu).move_to(primitives);
+        }
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d XY-plane.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param size_x The width of the plane (dimension along the X-axis).
+       \param size_y The height of the plane (dimensions along the Y-axis).
+       \param subdivisions_x The number of planar subdivisions along the X-axis.
+       \param subdivisions_y The number of planar subdivisions along the Y-axis.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::plane3d(faces3d,100,50);
+       CImg<unsigned char>().display_object3d(points3d,faces3d);
+       \endcode
+       \image html ref_plane3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> plane3d(CImgList<tf>& primitives,
+                                const float size_x=100, const float size_y=100,
+                                const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) {
+      primitives.assign();
+      if (!subdivisions_x || !subdivisions_y) return CImg<floatT>();
+      CImgList<floatT> vertices;
+      const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1;
+      const float fx = (float)size_x/w, fy = (float)size_y/h;
+      for (unsigned int y = 0; y<h; ++y) for (unsigned int x = 0; x<w; ++x)
+        CImg<floatT>::vector(fx*x,fy*y,0).move_to(vertices);
+      for (unsigned int y = 0; y<subdivisions_y; ++y) for (unsigned int x = 0; x<subdivisions_x; ++x) {
+        const int off1 = x+y*w, off2 = x+1+y*w, off3 = x+1+(y+1)*w, off4 = x+(y+1)*w;
+        CImg<tf>::vector(off1,off4,off3,off2).move_to(primitives);
+      }
+      return vertices>'x';
+    }
+
+    //! Create and return a 3d sphere.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param radius The radius of the sphere (dimension along the X-axis).
+       \param subdivisions The number of recursive subdivisions from an initial icosahedron.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> points3d = CImg<float>::sphere3d(faces3d,100,4);
+       CImg<unsigned char>().display_object3d(points3d,faces3d);
+       \endcode
+       \image html ref_sphere3d.jpg
+    **/
+    template<typename tf>
+    static CImg<floatT> sphere3d(CImgList<tf>& primitives,
+                                 const float radius=50, const unsigned int subdivisions=3) {
+
+      // Create initial icosahedron
+      primitives.assign();
+      const double tmp = (1+std::sqrt(5.0f))/2, a = 1.0/std::sqrt(1+tmp*tmp), b = tmp*a;
+      CImgList<floatT> vertices(12,1,3,1,1, b,a,0.0, -b,a,0.0, -b,-a,0.0, b,-a,0.0, a,0.0,b, a,0.0,-b,
+                                -a,0.0,-b, -a,0.0,b, 0.0,b,a, 0.0,-b,a, 0.0,-b,-a, 0.0,b,-a);
+      primitives.assign(20,1,3,1,1, 4,8,7, 4,7,9, 5,6,11, 5,10,6, 0,4,3, 0,3,5, 2,7,1, 2,1,6,
+                        8,0,11, 8,11,1, 9,10,3, 9,2,10, 8,4,0, 11,0,5, 4,9,3,
+                        5,3,10, 7,8,1, 6,1,11, 7,2,9, 6,10,2);
+      // edge - length/2
+      float he = (float)a;
+
+      // Recurse subdivisions
+      for (unsigned int i = 0; i<subdivisions; ++i) {
+        const unsigned int L = primitives._width;
+        he/=2;
+        const float he2 = he*he;
+        for (unsigned int l = 0; l<L; ++l) {
+          const unsigned int
+            p0 = (unsigned int)primitives(0,0), p1 = (unsigned int)primitives(0,1), p2 = (unsigned int)primitives(0,2);
+          const float
+            x0 = vertices(p0,0), y0 = vertices(p0,1), z0 = vertices(p0,2),
+            x1 = vertices(p1,0), y1 = vertices(p1,1), z1 = vertices(p1,2),
+            x2 = vertices(p2,0), y2 = vertices(p2,1), z2 = vertices(p2,2),
+            tnx0 = (x0+x1)/2, tny0 = (y0+y1)/2, tnz0 = (z0+z1)/2, nn0 = (float)std::sqrt(tnx0*tnx0+tny0*tny0+tnz0*tnz0),
+            tnx1 = (x0+x2)/2, tny1 = (y0+y2)/2, tnz1 = (z0+z2)/2, nn1 = (float)std::sqrt(tnx1*tnx1+tny1*tny1+tnz1*tnz1),
+            tnx2 = (x1+x2)/2, tny2 = (y1+y2)/2, tnz2 = (z1+z2)/2, nn2 = (float)std::sqrt(tnx2*tnx2+tny2*tny2+tnz2*tnz2),
+            nx0 = tnx0/nn0, ny0 = tny0/nn0, nz0 = tnz0/nn0,
+            nx1 = tnx1/nn1, ny1 = tny1/nn1, nz1 = tnz1/nn1,
+            nx2 = tnx2/nn2, ny2 = tny2/nn2, nz2 = tnz2/nn2;
+          int i0 = -1, i1 = -1, i2 = -1;
+          cimglist_for(vertices,p) {
+            const float x = (float)vertices(p,0), y = (float)vertices(p,1), z = (float)vertices(p,2);
+            if (cimg::sqr(x-nx0) + cimg::sqr(y-ny0) + cimg::sqr(z-nz0)<he2) i0 = p;
+            if (cimg::sqr(x-nx1) + cimg::sqr(y-ny1) + cimg::sqr(z-nz1)<he2) i1 = p;
+            if (cimg::sqr(x-nx2) + cimg::sqr(y-ny2) + cimg::sqr(z-nz2)<he2) i2 = p;
+          }
+          if (i0<0) { CImg<floatT>::vector(nx0,ny0,nz0).move_to(vertices); i0 = vertices._width - 1; }
+          if (i1<0) { CImg<floatT>::vector(nx1,ny1,nz1).move_to(vertices); i1 = vertices._width - 1; }
+          if (i2<0) { CImg<floatT>::vector(nx2,ny2,nz2).move_to(vertices); i2 = vertices._width - 1; }
+          primitives.remove(0);
+          CImg<tf>::vector(p0,i0,i1).move_to(primitives);
+          CImg<tf>::vector((tf)i0,(tf)p1,(tf)i2).move_to(primitives);
+          CImg<tf>::vector((tf)i1,(tf)i2,(tf)p2).move_to(primitives);
+          CImg<tf>::vector((tf)i1,(tf)i0,(tf)i2).move_to(primitives);
+        }
+      }
+      return (vertices>'x')*=radius;
+    }
+
+    //! Create and return a 3d ellipsoid.
+    /**
+       \param[out] primitives The returned list of the 3d object primitives
+                              (template type \e tf should be at least \e unsigned \e int).
+       \param tensor The tensor which gives the shape and size of the ellipsoid.
+       \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron.
+       \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N-1).
+       \par Sample code :
+       \code
+       CImgList<unsigned int> faces3d;
+       const CImg<float> tensor = CImg<float>::diagonal(10,7,3),
+                         points3d = CImg<float>::ellipsoid3d(faces3d,tensor,4);
+       CImg<unsigned char>().display_object3d(points3d,faces3d);
+       \endcode
+       \image html ref_ellipsoid3d.jpg
+    **/
+    template<typename tf, typename t>
+    static CImg<floatT> ellipsoid3d(CImgList<tf>& primitives,
+                                    const CImg<t>& tensor, const unsigned int subdivisions=3) {
+      primitives.assign();
+      if (!subdivisions) return CImg<floatT>();
+      CImg<floatT> S, V;
+      tensor.symmetric_eigen(S,V);
+      const float l0 = S[0], l1 = S[1], l2 = S[2];
+      CImg<floatT> vertices = sphere(primitives,subdivisions);
+      vertices.get_shared_line(0)*=l0;
+      vertices.get_shared_line(1)*=l1;
+      vertices.get_shared_line(2)*=l2;
+      return V.transpose()*vertices;
+    }
+
+    //! Convert a 3d object into a CImg3d.
+    template<typename tp, typename tc, typename to>
+    CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives,
+                              const CImgList<tc>& colors,
+                              const to& opacities) {
+      return get_object3dtoCImg3d(primitives,colors,opacities).move_to(*this);
+    }
+
+    template<typename tp, typename tc>
+    CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives,
+                              const CImgList<tc>& colors) {
+      return get_object3dtoCImg3d(primitives,colors).move_to(*this);
+    }
+
+    template<typename tp>
+    CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives) {
+      return get_object3dtoCImg3d(primitives).move_to(*this);
+    }
+
+    CImg<T>& object3dtoCImg3d() {
+      return get_object3dtoCImg3d().move_to(*this);
+    }
+
+    template<typename tp, typename tc, typename to>
+    CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                      const CImgList<tc>& colors,
+                                      const to& opacities) const {
+      char error_message[1024] = { 0 };
+      if (!is_object3d(primitives,colors,opacities,true,error_message))
+        throw CImgInstanceException(_cimg_instance
+                                    "object3dtoCImg3d() : Invalid specified 3d object (%u,%u) (%s).",
+                                    cimg_instance,_width,primitives._width,error_message);
+      CImg<floatT> res(1,_size_object3dtoCImg3d(primitives,colors,opacities));
+      float *ptrd = res._data;
+
+      // Put magick number.
+      *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f;
+      *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f;
+
+      // Put number of vertices and primitives.
+      *(ptrd++) = (float)_width;
+      *(ptrd++) = (float)primitives._width;
+
+      // Put vertex data.
+      if (is_empty() || !primitives) return res;
+      const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2);
+      cimg_forX(*this,p) { *(ptrd++) = (float)*(ptrx++); *(ptrd++) = (float)*(ptry++); *(ptrd++) = (float)*(ptrz++); }
+
+      // Put primitive data.
+      cimglist_for(primitives,p) {
+        *(ptrd++) = (float)primitives[p].size();
+        const tp *ptrp = primitives[p]._data;
+        cimg_foroff(primitives[p],i) *(ptrd++) = (float)*(ptrp++);
+      }
+
+      // Put color/texture data.
+      const unsigned int csiz = cimg::min(colors._width,primitives._width);
+      for (int c = 0; c<(int)csiz; ++c) {
+        const CImg<tc>& color = colors[c];
+        const tc *ptrc = color._data;
+        if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; }
+        else {
+          *(ptrd++) = -128.0f;
+          int shared_ind = -1;
+          if (color.is_shared()) for (int i = 0; i<c; ++i) if (ptrc==colors[i]._data) { shared_ind = i; break; }
+          if (shared_ind<0) {
+            *(ptrd++) = (float)color._width;
+            *(ptrd++) = (float)color._height;
+            *(ptrd++) = (float)color._spectrum;
+            cimg_foroff(color,l) *(ptrd++) = (float)*(ptrc++);
+          } else {
+            *(ptrd++) = (float)shared_ind;
+            *(ptrd++) = 0;
+            *(ptrd++) = 0;
+          }
+        }
+      }
+      const int csiz2 = primitives._width - colors._width;
+      for (int c = 0; c<csiz2; ++c) { *(ptrd++) = 200.0f; *(ptrd++) = 200.0f; *(ptrd++) = 200.0f; }
+
+      // Put opacity data.
+      ptrd = _object3dtoCImg3d(opacities,ptrd);
+      const unsigned int osiz = primitives._width - opacities._width;
+      for (unsigned int o = 0; o<osiz; ++o) *(ptrd++) = 1.0f;
+      return res;
+    }
+
+    template<typename to>
+    float* _object3dtoCImg3d(const CImgList<to>& opacities, float *ptrd) const {
+      cimglist_for(opacities,o) {
+        const CImg<to>& opacity = opacities[o];
+        const to *ptro = opacity._data;
+        if (opacity.size()==1) *(ptrd++) = (float)*ptro;
+        else {
+          *(ptrd++) = -128.0f;
+          int shared_ind = -1;
+          if (opacity.is_shared()) for (int i = 0; i<o; ++i) if (ptro==opacities[i]._data) { shared_ind = i; break; }
+          if (shared_ind<0) {
+            *(ptrd++) = (float)opacity._width;
+            *(ptrd++) = (float)opacity._height;
+            *(ptrd++) = (float)opacity._spectrum;
+            cimg_foroff(opacity,l) *(ptrd++) = (float)*(ptro++);
+          } else {
+            *(ptrd++) = (float)shared_ind;
+            *(ptrd++) = 0;
+            *(ptrd++) = 0;
+          }
+        }
+      }
+      return ptrd;
+    }
+
+    template<typename to>
+    float* _object3dtoCImg3d(const CImg<to>& opacities, float *ptrd) const {
+      const to *ptro = opacities._data;
+      cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++);
+      return ptrd;
+    }
+
+    template<typename tp, typename tc, typename to>
+    unsigned int _size_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                        const CImgList<tc>& colors,
+                                        const CImgList<to>& opacities) const {
+      unsigned int siz = 8 + 3*width();
+      cimglist_for(primitives,p) siz+=primitives[p].size() + 1;
+      for (int c = cimg::min(primitives._width,colors._width)-1; c>=0; --c) {
+        if (colors[c].is_shared()) siz+=4;
+        else { const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4+csiz:3; }
+      }
+      if (colors._width<primitives._width) siz+=3*(primitives._width - colors._width);
+      cimglist_for(opacities,o) {
+        if (opacities[o].is_shared()) siz+=4;
+        else { const unsigned int osiz = opacities[o].size(); siz+=(osiz!=1)?4+osiz:1; }
+      }
+      siz+=primitives._width - opacities._width;
+      return siz;
+    }
+
+    template<typename tp, typename tc, typename to>
+    unsigned int _size_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                        const CImgList<tc>& colors,
+                                        const CImg<to>& opacities) const {
+      unsigned int siz = 8 + 3*width();
+      cimglist_for(primitives,p) siz+=primitives[p].size() + 1;
+      for (int c = cimg::min(primitives._width,colors._width)-1; c>=0; --c) {
+        const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4+csiz:3;
+      }
+      if (colors._width<primitives._width) siz+=3*(primitives._width - colors._width);
+      siz+=opacities.size();
+      return siz;
+    }
+
+    template<typename tp, typename tc>
+    CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives,
+                                      const CImgList<tc>& colors) const {
+      CImgList<T> opacities;
+      return get_object3dtoCImg3d(primitives,colors,opacities);
+    }
+
+    template<typename tp>
+    CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives) const {
+      CImgList<T> colors, opacities;
+      return get_object3dtoCImg3d(primitives,colors,opacities);
+    }
+
+    CImg<floatT> get_object3dtoCImg3d() const {
+      CImgList<T> opacities, colors;
+      CImgList<uintT> primitives(width(),1,1,1,1);
+      cimglist_for(primitives,p) primitives(p,0) = p;
+      return get_object3dtoCImg3d(primitives,colors,opacities);
+    }
+
+    //! Convert a CImg3d (one-column image) into a 3d object.
+    template<typename tp, typename tc, typename to>
+    CImg<T> get_CImg3dtoobject3d(CImgList<tp>& primitives, CImgList<tc>& colors, CImgList<to>& opacities) const {
+      char error_message[1024] = { 0 };
+      if (!is_CImg3d(true,error_message))
+        throw CImgInstanceException(_cimg_instance
+                                    "CImg3dtoobject3d() : Instance image is not a CImg3d (%s).",
+                                    cimg_instance,error_message);
+      const T *ptrs = _data + 6;
+      const unsigned int nb_points = (unsigned int)*(ptrs++), nb_primitives = (unsigned int)*(ptrs++);
+      const CImg<T> points = CImg<T>(ptrs,3,nb_points,1,1,true).get_transpose();
+      ptrs+=3*nb_points;
+      primitives.assign(nb_primitives);
+      cimglist_for(primitives,p) {
+        const unsigned int nb_inds = (unsigned int)*(ptrs++);
+        primitives[p].assign(ptrs,1,nb_inds,1,1,false);
+        ptrs+=nb_inds;
+      }
+      colors.assign(nb_primitives);
+      cimglist_for(colors,c) {
+        if ((int)*ptrs==-128) {
+          ++ptrs;
+          const unsigned int w = (unsigned int)*(ptrs++), h = (unsigned int)*(ptrs++), s = (unsigned int)*(ptrs++);
+          if (!h && !s) colors[c].assign(colors[w],true);
+          else { colors[c].assign(ptrs,w,h,1,s,false); ptrs+=w*h*s; }
+        } else { colors[c].assign(ptrs,1,3,1,1,false); ptrs+=3; }
+      }
+      opacities.assign(nb_primitives);
+      cimglist_for(opacities,o) {
+        if ((int)*ptrs==-128) {
+          ++ptrs;
+          const unsigned int w = (unsigned int)*(ptrs++), h = (unsigned int)*(ptrs++), s = (unsigned int)*(ptrs++);
+          if (!h && !s) opacities[o].assign(opacities[w],true);
+          else { opacities[o].assign(ptrs,w,h,1,s,false); ptrs+=w*h*s; }
+        } else opacities[o].assign(1,1,1,1,*(ptrs++));
+      }
+      return points;
+    }
+
+    template<typename tp, typename tc, typename to>
+    CImg<T>& CImg3dtoobject3d(CImgList<tp>& primitives, CImgList<tc>& colors, CImgList<to>& opacities) {
+      return get_CImg3dtoobject3d(primitives,colors,opacities).move_to(*this);
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name Drawing Functions
+    //@{
+    //---------------------------
+
+    // The following _draw_scanline() routines are *non user-friendly functions*, used only for internal purpose.
+    // Pre-requisites : x0<x1, y-coordinate is valid, col is valid.
+    template<typename tc>
+    CImg<T>& _draw_scanline(const int x0, const int x1, const int y,
+                            const tc *const color, const float opacity=1,
+                            const float brightness=1, const bool init=false) {
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      static float nopacity = 0, copacity = 0;
+      static unsigned int whd = 0;
+      static const tc *col = 0;
+      if (init) {
+        nopacity = cimg::abs(opacity);
+        copacity = 1 - cimg::max(opacity,0);
+        whd = _width*_height*_depth;
+      } else {
+        const int nx0 = x0>0?x0:0, nx1 = x1<width()?x1:width()-1, dx = nx1 - nx0;
+        if (dx>=0) {
+          col = color;
+          const unsigned int off = whd-dx-1;
+          T *ptrd = data(nx0,y);
+          if (opacity>=1) { // ** Opaque drawing **
+            if (brightness==1) { // Brightness==1
+              if (sizeof(T)!=1) cimg_forC(*this,c) {
+                const T val = (T)*(col++);
+                for (int x = dx; x>=0; --x) *(ptrd++) = val;
+                ptrd+=off;
+              } else cimg_forC(*this,c) {
+                const T val = (T)*(col++);
+                std::memset(ptrd,(int)val,dx+1);
+                ptrd+=whd;
+              }
+            } else if (brightness<1) { // Brightness<1
+              if (sizeof(T)!=1) cimg_forC(*this,c) {
+                const T val = (T)(*(col++)*brightness);
+                for (int x = dx; x>=0; --x) *(ptrd++) = val;
+                ptrd+=off;
+              } else cimg_forC(*this,c) {
+                const T val = (T)(*(col++)*brightness);
+                std::memset(ptrd,(int)val,dx+1);
+                ptrd+=whd;
+              }
+            } else { // Brightness>1
+              if (sizeof(T)!=1) cimg_forC(*this,c) {
+                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+                for (int x = dx; x>=0; --x) *(ptrd++) = val;
+                ptrd+=off;
+              } else cimg_forC(*this,c) {
+                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+                std::memset(ptrd,(int)val,dx+1);
+                ptrd+=whd;
+              }
+            }
+          } else { // ** Transparent drawing **
+            if (brightness==1) { // Brightness==1
+              cimg_forC(*this,c) {
+                const T val = (T)*(col++);
+                for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
+                ptrd+=off;
+              }
+            } else if (brightness<=1) { // Brightness<1
+              cimg_forC(*this,c) {
+                const T val = (T)(*(col++)*brightness);
+                for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
+                ptrd+=off;
+              }
+            } else { // Brightness>1
+              cimg_forC(*this,c) {
+                const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
+                for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
+                ptrd+=off;
+              }
+            }
+          }
+        }
+      }
+      return *this;
+    }
+
+    template<typename tc>
+    CImg<T>& _draw_scanline(const tc *const color, const float opacity=1) {
+      return _draw_scanline(0,0,0,color,opacity,0,true);
+    }
+
+    //! Draw a 2d colored point (pixel).
+    /**
+       \param x0 X-coordinate of the point.
+       \param y0 Y-coordinate of the point.
+       \param color Pointer to \c spectrum() consecutive values, defining the color values.
+       \param opacity Drawing opacity (optional).
+       \note
+       - Clipping is supported.
+       - To set pixel values without clipping needs, you should use the faster CImg::operator()() function.
+       \par Example:
+       \code
+       CImg<unsigned char> img(100,100,1,3,0);
+       const unsigned char color[] = { 255,128,64 };
+       img.draw_point(50,50,color);
+       \endcode
+    **/
+    template<typename tc>
+    CImg<T>& draw_point(const int x0, const int y0,
+                        const tc *const color, const float opacity=1) {
+      return draw_point(x0,y0,0,color,opacity);
+    }
+
+    //! Draw a 3d colored point (voxel).
+    template<typename tc>
+    CImg<T>& draw_point(const int x0, const int y0, const int z0,
+                        const tc *const color, const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_point() : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      if (x0>=0 && y0>=0 && z0>=0 && x0<width() && y0<height() && z0<depth()) {
+        const unsigned int whd = _width*_height*_depth;
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        T *ptrd = data(x0,y0,z0,0);
+        const tc *col = color;
+        if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; }
+        else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; }
+      }
+      return *this;
+    }
+
+    // Draw a cloud of colored points.
+    template<typename t, typename tc>
+    CImg<T>& draw_point(const CImg<t>& points,
+                        const tc *const color, const float opacity=1) {
+      if (is_empty() || !points) return *this;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_point() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+      case 2 : {
+        cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),color,opacity);
+      } break;
+      default : {
+        cimg_forX(points,i) draw_point((int)points(i,0),(int)points(i,1),(int)points(i,2),color,opacity);
+      }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d colored line.
+    /**
+       \param x0 X-coordinate of the starting line point.
+       \param y0 Y-coordinate of the starting line point.
+       \param x1 X-coordinate of the ending line point.
+       \param y1 Y-coordinate of the ending line point.
+       \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity (optional).
+       \param pattern An integer whose bits describe the line pattern (optional).
+       \param init_hatch Flag telling if a reinitialization of the hash state must be done (optional).
+       \note
+       - Clipping is supported.
+       - Line routine uses Bresenham's algorithm.
+       - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern.
+       \par Example:
+       \code
+       CImg<unsigned char> img(100,100,1,3,0);
+       const unsigned char color[] = { 255,128,64 };
+        img.draw_line(40,40,80,70,color);
+       \endcode
+    **/
+    template<typename tc>
+    CImg<T>& draw_line(const int x0, const int y0,
+                       const int x1, const int y1,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      static unsigned int hatch = ~0U - (~0U>>1);
+      if (init_hatch) hatch = ~0U - (~0U>>1);
+      const bool xdir = x0<x1, ydir = y0<y1;
+      int
+        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+        &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+        &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+      if (xright<0 || xleft>=width()) return *this;
+      if (xleft<0) { yleft-=(int)((float)xleft*((float)yright - yleft)/((float)xright - xleft)); xleft = 0; }
+      if (xright>=width()) { yright-=(int)(((float)xright - width())*((float)yright - yleft)/((float)xright - xleft)); xright = width() - 1; }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) { xup-=(int)((float)yup*((float)xdown - xup)/((float)ydown - yup)); yup = 0; }
+      if (ydown>=height()) { xdown-=(int)(((float)ydown - height())*((float)xdown - xup)/((float)ydown - yup)); ydown = height() - 1; }
+      T *ptrd0 = data(nx0,ny0);
+      int dx = xright - xleft, dy = ydown - yup;
+      const bool steep = dy>dx;
+      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+      const int
+        offx = (nx0<nx1?1:-1)*(steep?_width:1),
+        offy = (ny0<ny1?1:-1)*(steep?1:_width),
+        wh = _width*_height;
+      if (opacity>=1) {
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          if (pattern&hatch) {
+            T *ptrd = ptrd0; const tc* col = color;
+            cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          T *ptrd = ptrd0; const tc* col = color; cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=wh; }
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        }
+      } else {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          if (pattern&hatch) {
+            T *ptrd = ptrd0; const tc* col = color;
+            cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          T *ptrd = ptrd0; const tc* col = color; cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d colored line, with z-buffering.
+    template<typename tz,typename tc>
+    CImg<T>& draw_line(CImg<tz>& zbuffer,
+                       const int x0, const int y0, const float z0,
+                       const int x1, const int y1, const float z1,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Specified color is (null).",
+                                    cimg_instance);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (is_empty() || z0<=0 || z1<=0) return *this;
+      static unsigned int hatch = ~0U - (~0U>>1);
+      if (init_hatch) hatch = ~0U - (~0U>>1);
+      const bool xdir = x0<x1, ydir = y0<y1;
+      int
+        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+        &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+        &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+      tzfloat
+        Z0 = 1/(tzfloat)z0, Z1 = 1/(tzfloat)z1, nz0 = Z0, nz1 = Z1, dz = Z1 - Z0,
+        &zleft = xdir?nz0:nz1,
+        &zright = xdir?nz1:nz0,
+        &zup = ydir?nz0:nz1,
+        &zdown = ydir?nz1:nz0;
+      if (xright<0 || xleft>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        zleft-=(tzfloat)xleft*(zright - zleft)/D;
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        zright-=(tzfloat)d*(zright - zleft)/D;
+        xright = width() - 1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        zup-=(tzfloat)yup*(zdown - zup)/D;
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        zdown-=(tzfloat)d*(zdown - zup)/D;
+        ydown = height() - 1;
+      }
+      T *ptrd0 = data(nx0,ny0);
+      tz *ptrz = zbuffer.data(nx0,ny0);
+      int dx = xright - xleft, dy = ydown - yup;
+      const bool steep = dy>dx;
+      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+      const int
+        offx = (nx0<nx1?1:-1)*(steep?_width:1),
+        offy = (ny0<ny1?1:-1)*(steep?1:_width),
+        wh = _width*_height,
+        ndx = dx>0?dx:1;
+      if (opacity>=1) {
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz && pattern&hatch) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=wh; }
+          }
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+        }
+      } else {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz && pattern&hatch) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            T *ptrd = ptrd0; const tc *col = color;
+            cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
+          }
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 3d colored line.
+    template<typename tc>
+    CImg<T>& draw_line(const int x0, const int y0, const int z0,
+                       const int x1, const int y1, const int z1,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      static unsigned int hatch = ~0U - (~0U>>1);
+      if (init_hatch) hatch = ~0U - (~0U>>1);
+      int nx0 = x0, ny0 = y0, nz0 = z0, nx1 = x1, ny1 = y1, nz1 = z1;
+      if (nx0>nx1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+      if (nx1<0 || nx0>=width()) return *this;
+      if (nx0<0) { const float D = 1.0f + nx1 - nx0; ny0-=(int)((float)nx0*(1.0f + ny1 - ny0)/D); nz0-=(int)((float)nx0*(1.0f + nz1 - nz0)/D); nx0 = 0; }
+      if (nx1>=width()) { const float d = (float)nx1 - width(), D = 1.0f + nx1 - nx0; ny1+=(int)(d*(1.0f + ny0 - ny1)/D); nz1+=(int)(d*(1.0f + nz0 - nz1)/D); nx1 = width() - 1; }
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+      if (ny1<0 || ny0>=height()) return *this;
+      if (ny0<0) { const float D = 1.0f + ny1 - ny0; nx0-=(int)((float)ny0*(1.0f + nx1 - nx0)/D); nz0-=(int)((float)ny0*(1.0f + nz1 - nz0)/D); ny0 = 0; }
+      if (ny1>=height()) { const float d = (float)ny1 - height(), D = 1.0f + ny1 - ny0; nx1+=(int)(d*(1.0f + nx0 - nx1)/D); nz1+=(int)(d*(1.0f + nz0 - nz1)/D); ny1 = height() - 1; }
+      if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+      if (nz1<0 || nz0>=depth()) return *this;
+      if (nz0<0) { const float D = 1.0f + nz1 - nz0; nx0-=(int)((float)nz0*(1.0f + nx1 - nx0)/D); ny0-=(int)((float)nz0*(1.0f + ny1 - ny0)/D); nz0 = 0; }
+      if (nz1>=depth()) { const float d = (float)nz1 - depth(), D = 1.0f + nz1 - nz0; nx1+=(int)(d*(1.0f + nx0 - nx1)/D); ny1+=(int)(d*(1.0f + ny0 - ny1)/D); nz1 = depth() - 1; }
+      const unsigned int dmax = cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0), whd = _width*_height*_depth;
+      const float px = (nx1 - nx0)/(float)dmax, py = (ny1 - ny0)/(float)dmax, pz = (nz1 - nz0)/(float)dmax;
+      float x = (float)nx0, y = (float)ny0, z = (float)nz0;
+      if (opacity>=1) for (unsigned int t = 0; t<=dmax; ++t) {
+        if (!(~pattern) || (~pattern && pattern&hatch)) {
+          T* ptrd = data((unsigned int)x,(unsigned int)y,(unsigned int)z);
+          const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; }
+        }
+        x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
+      } else {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        for (unsigned int t = 0; t<=dmax; ++t) {
+          if (!(~pattern) || (~pattern && pattern&hatch)) {
+            T* ptrd = data((unsigned int)x,(unsigned int)y,(unsigned int)z);
+            const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; }
+          }
+          x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d textured line.
+    /**
+       \param x0 X-coordinate of the starting line point.
+       \param y0 Y-coordinate of the starting line point.
+       \param x1 X-coordinate of the ending line point.
+       \param y1 Y-coordinate of the ending line point.
+       \param texture Texture image defining the pixel colors.
+       \param tx0 X-coordinate of the starting texture point.
+       \param ty0 Y-coordinate of the starting texture point.
+       \param tx1 X-coordinate of the ending texture point.
+       \param ty1 Y-coordinate of the ending texture point.
+       \param opacity Drawing opacity (optional).
+       \param pattern An integer whose bits describe the line pattern (optional).
+       \param init_hatch Flag telling if the hash variable must be reinitialized (optional).
+       \note
+       - Clipping is supported but not for texture coordinates.
+       - Line routine uses the well known Bresenham's algorithm.
+       \par Example:
+       \code
+       CImg<unsigned char> img(100,100,1,3,0), texture("texture256x256.ppm");
+       const unsigned char color[] = { 255,128,64 };
+       img.draw_line(40,40,80,70,texture,0,0,255,255);
+       \endcode
+    **/
+    template<typename tc>
+    CImg<T>& draw_line(const int x0, const int y0,
+                       const int x1, const int y1,
+                       const CImg<tc>& texture,
+                       const int tx0, const int ty0,
+                       const int tx1, const int ty1,
+                       const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty()) return *this;
+      if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+      static unsigned int hatch = ~0U - (~0U>>1);
+      if (init_hatch) hatch = ~0U - (~0U>>1);
+      const bool xdir = x0<x1, ydir = y0<y1;
+      int
+        dtx = tx1-tx0, dty = ty1-ty0,
+        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+        tnx0 = tx0, tnx1 = tx1, tny0 = ty0, tny1 = ty1,
+        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1, &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+        &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1, &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0,
+        &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+      if (xright<0 || xleft>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        txleft-=(int)((float)xleft*((float)txright - txleft)/D);
+        tyleft-=(int)((float)xleft*((float)tyright - tyleft)/D);
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        txright-=(int)(d*((float)txright - txleft)/D);
+        tyright-=(int)(d*((float)tyright - tyleft)/D);
+        xright = width() - 1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        txup-=(int)((float)yup*((float)txdown - txup)/D);
+        tyup-=(int)((float)yup*((float)tydown - tyup)/D);
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        txdown-=(int)(d*((float)txdown - txup)/D);
+        tydown-=(int)(d*((float)tydown - tyup)/D);
+        ydown = height() - 1;
+      }
+      T *ptrd0 = data(nx0,ny0);
+      int dx = xright - xleft, dy = ydown - yup;
+      const bool steep = dy>dx;
+      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+      const int
+        offx = (nx0<nx1?1:-1)*(steep?_width:1),
+        offy = (ny0<ny1?1:-1)*(steep?1:_width),
+        wh = _width*_height,
+        ndx = dx>0?dx:1;
+      if (opacity>=1) {
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          if (pattern&hatch) {
+            T *ptrd = ptrd0;
+            const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+            cimg_forC(*this,c) { *ptrd = (T)texture(tx,ty,0,c); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          T *ptrd = ptrd0;
+          const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+          cimg_forC(*this,c) { *ptrd = (T)texture(tx,ty,0,c); ptrd+=wh; }
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        }
+      } else {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          T *ptrd = ptrd0;
+          if (pattern&hatch) {
+            const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+            cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture(tx,ty,0,c) + *ptrd*copacity); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          T *ptrd = ptrd0;
+          const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+          cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture(tx,ty,0,c) + *ptrd*copacity); ptrd+=wh; }
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d textured line, with perspective correction.
+    template<typename tc>
+    CImg<T>& draw_line(const int x0, const int y0, const float z0,
+                       const int x1, const int y1, const float z1,
+                       const CImg<tc>& texture,
+                       const int tx0, const int ty0,
+                       const int tx1, const int ty1,
+                       const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty() && z0<=0 && z1<=0) return *this;
+      if (is_overlapped(texture)) return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+      static unsigned int hatch = ~0U - (~0U>>1);
+      if (init_hatch) hatch = ~0U - (~0U>>1);
+      const bool xdir = x0<x1, ydir = y0<y1;
+      int
+        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+        &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+        &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+      float
+        Tx0 = tx0/z0, Tx1 = tx1/z1,
+        Ty0 = ty0/z0, Ty1 = ty1/z1,
+        Z0 = 1/z0, Z1 = 1/z1,
+        dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
+        tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
+        &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
+        &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+        &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
+        &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+      if (xright<0 || xleft>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        zleft-=(float)xleft*(zright - zleft)/D;
+        txleft-=(float)xleft*(txright - txleft)/D;
+        tyleft-=(float)xleft*(tyright - tyleft)/D;
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        zright-=d*(zright - zleft)/D;
+        txright-=d*(txright - txleft)/D;
+        tyright-=d*(tyright - tyleft)/D;
+        xright = width() - 1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        zup-=(float)yup*(zdown - zup)/D;
+        txup-=(float)yup*(txdown - txup)/D;
+        tyup-=(float)yup*(tydown - tyup)/D;
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        zdown-=d*(zdown - zup)/D;
+        txdown-=d*(txdown - txup)/D;
+        tydown-=d*(tydown - tyup)/D;
+        ydown = height() - 1;
+      }
+      T *ptrd0 = data(nx0,ny0);
+      int dx = xright - xleft, dy = ydown - yup;
+      const bool steep = dy>dx;
+      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+      const int
+        offx = (nx0<nx1?1:-1)*(steep?_width:1),
+        offy = (ny0<ny1?1:-1)*(steep?1:_width),
+        wh = _width*_height,
+        ndx = dx>0?dx:1;
+      if (opacity>=1) {
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          if (pattern&hatch) {
+            const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+            T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+          T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); ptrd+=wh; }
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        }
+      } else {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          if (pattern&hatch) {
+            const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+            T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *ptrd*copacity); ptrd+=wh; }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+          T *ptrd = ptrd0;
+          cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *ptrd*copacity); ptrd+=wh; }
+          ptrd0+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d textured line, with z-buffering and perspective correction.
+    template<typename tz, typename tc>
+    CImg<T>& draw_line(CImg<tz>& zbuffer,
+                       const int x0, const int y0, const float z0,
+                       const int x1, const int y1, const float z1,
+                       const CImg<tc>& texture,
+                       const int tx0, const int ty0,
+                       const int tx1, const int ty1,
+                       const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty() || z0<=0 || z1<=0) return *this;
+      if (is_overlapped(texture)) return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+      static unsigned int hatch = ~0U - (~0U>>1);
+      if (init_hatch) hatch = ~0U - (~0U>>1);
+      const bool xdir = x0<x1, ydir = y0<y1;
+      int
+        nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+        &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+        &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+        &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+        &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+      float
+        Tx0 = tx0/z0, Tx1 = tx1/z1,
+        Ty0 = ty0/z0, Ty1 = ty1/z1,
+        dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
+        tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1,
+        &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
+        &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+        &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
+        &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+      tzfloat
+        Z0 = 1/(tzfloat)z0, Z1 = 1/(tzfloat)z1,
+        dz = Z1 - Z0,  nz0 = Z0, nz1 = Z1,
+        &zleft = xdir?nz0:nz1,
+        &zright = xdir?nz1:nz0,
+        &zup = ydir?nz0:nz1,
+        &zdown = ydir?nz1:nz0;
+      if (xright<0 || xleft>=width()) return *this;
+      if (xleft<0) {
+        const float D = (float)xright - xleft;
+        yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+        zleft-=(float)xleft*(zright - zleft)/D;
+        txleft-=(float)xleft*(txright - txleft)/D;
+        tyleft-=(float)xleft*(tyright - tyleft)/D;
+        xleft = 0;
+      }
+      if (xright>=width()) {
+        const float d = (float)xright - width(), D = (float)xright - xleft;
+        yright-=(int)(d*((float)yright - yleft)/D);
+        zright-=d*(zright - zleft)/D;
+        txright-=d*(txright - txleft)/D;
+        tyright-=d*(tyright - tyleft)/D;
+        xright = width()-1;
+      }
+      if (ydown<0 || yup>=height()) return *this;
+      if (yup<0) {
+        const float D = (float)ydown - yup;
+        xup-=(int)((float)yup*((float)xdown - xup)/D);
+        zup-=yup*(zdown - zup)/D;
+        txup-=yup*(txdown - txup)/D;
+        tyup-=yup*(tydown - tyup)/D;
+        yup = 0;
+      }
+      if (ydown>=height()) {
+        const float d = (float)ydown - height(), D = (float)ydown - yup;
+        xdown-=(int)(d*((float)xdown - xup)/D);
+        zdown-=d*(zdown - zup)/D;
+        txdown-=d*(txdown - txup)/D;
+        tydown-=d*(tydown - tyup)/D;
+        ydown = height()-1;
+      }
+      T *ptrd0 = data(nx0,ny0);
+      tz *ptrz = zbuffer.data(nx0,ny0);
+      int dx = xright - xleft, dy = ydown - yup;
+      const bool steep = dy>dx;
+      if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+      const int
+        offx = (nx0<nx1?1:-1)*(steep?_width:1),
+        offy = (ny0<ny1?1:-1)*(steep?1:_width),
+        wh = _width*_height,
+        ndx = dx>0?dx:1;
+      if (opacity>=1) {
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          if (pattern&hatch) {
+            const tzfloat z = Z0 + x*dz/ndx;
+            if (z>=(tzfloat)*ptrz) {
+              *ptrz = (tz)z;
+              const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+              T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); ptrd+=wh; }
+            }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+            T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,c); ptrd+=wh; }
+          }
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+        }
+      } else {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          if (pattern&hatch) {
+            const tzfloat z = Z0 + x*dz/ndx;
+            if (z>=(tzfloat)*ptrz) {
+              *ptrz = (tz)z;
+              const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+              T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *ptrd*copacity); ptrd+=wh; }
+            }
+          }
+          hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+        } else for (int error = dx>>1, x = 0; x<=dx; ++x) {
+          const tzfloat z = Z0 + x*dz/ndx;
+          if (z>=(tzfloat)*ptrz) {
+            *ptrz = (tz)z;
+            const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
+            T *ptrd = ptrd0; cimg_forC(*this,c) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,c) + *ptrd*copacity); ptrd+=wh; }
+          }
+          ptrd0+=offx; ptrz+=offx;
+          if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offx; error+=dx; }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a set of consecutive colored lines in the instance image.
+    /**
+       \param points Coordinates of vertices, stored as a list of vectors.
+       \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity (optional).
+       \param pattern An integer whose bits describe the line pattern (optional).
+       \param init_hatch If set to true, init hatch motif.
+       \note
+       - This function uses several call to the single CImg::draw_line() procedure,
+       depending on the vectors size in \p points.
+       \par Example:
+       \code
+       CImg<unsigned char> img(100,100,1,3,0);
+       const unsigned char color[] = { 255,128,64 };
+       CImgList<int> points;
+       points.insert(CImg<int>::vector(0,0)).
+             .insert(CImg<int>::vector(70,10)).
+             .insert(CImg<int>::vector(80,60)).
+             .insert(CImg<int>::vector(10,90));
+       img.draw_line(points,color);
+       \endcode
+    **/
+    template<typename t, typename tc>
+    CImg<T>& draw_line(const CImg<t>& points,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (is_empty() || !points || points._width<2) return *this;
+      bool ninit_hatch = init_hatch;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+
+      case 2 : {
+        const int x0 = (int)points(0,0), y0 = (int)points(0,1);
+        int ox = x0, oy = y0;
+        for (unsigned int i = 1; i<points._width; ++i) {
+          const int x = (int)points(i,0), y = (int)points(i,1);
+          draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y;
+        }
+      } break;
+      default : {
+        const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
+        int ox = x0, oy = y0, oz = z0;
+        for (unsigned int i = 1; i<points._width; ++i) {
+          const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
+          draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y; oz = z;
+        }
+      }
+      }
+      return *this;
+    }
+
+    //! Draw a colored arrow in the 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 spectrum() consecutive values of type \c T, defining the drawing color.
+       \param angle Aperture angle of the arrow head (optional).
+       \param length Length of the arrow head. If negative, describes a percentage of the arrow length (optional).
+       \param opacity Drawing opacity (optional).
+       \param pattern An integer whose bits describe the line pattern (optional).
+       \note
+       - Clipping is supported.
+    **/
+    template<typename tc>
+    CImg<T>& draw_arrow(const int x0, const int y0,
+                        const int x1, const int y1,
+                        const tc *const color, const float opacity=1,
+                        const float angle=30, const float length=-10,
+                        const unsigned int pattern=~0U) {
+      if (is_empty()) return *this;
+      const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v,
+        deg = (float)(angle*cimg::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.0f,
+        l = (length>=0)?length:-length*(float)std::sqrt(sq)/100;
+      if (sq>0) {
+        const float
+            cl = (float)std::cos(ang - deg), sl = (float)std::sin(ang - deg),
+            cr = (float)std::cos(ang + deg), sr = (float)std::sin(ang + deg);
+        const int
+          xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl),
+          xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr),
+          xc = x1 + (int)((l+1)*(cl+cr))/2, yc = y1 + (int)((l+1)*(sl+sr))/2;
+        draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity);
+      } else draw_point(x0,y0,color,opacity);
+      return *this;
+    }
+
+    //! Draw a cubic spline curve in the 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 spectrum() consecutive values of type \c T, defining the drawing color.
+       \param precision Curve drawing precision (optional).
+       \param opacity Drawing opacity (optional).
+       \param pattern An integer whose bits describe the line pattern (optional).
+       \param init_hatch If \c true, init hatch motif.
+       \note
+       - The curve is a 2d cubic Bezier spline, from the set of specified starting/ending points
+       and corresponding velocity vectors.
+       - The spline is drawn as a serie of connected segments. The \p precision parameter sets the
+       average number of pixels in each drawn segment.
+       - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), (\p xb,\p yb), (\p x1,\p y1) }
+       where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point and (\p xa,\p ya), (\p xb,\p yb) are two
+       \e control points.
+       The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from the control points as
+       \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb).
+       \par Example:
+       \code
+       CImg<unsigned char> img(100,100,1,3,0);
+       const unsigned char color[] = { 255,255,255 };
+       img.draw_spline(30,30,0,100,90,40,0,-100,color);
+       \endcode
+    **/
+    template<typename tc>
+    CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
+                         const int x1, const int y1, const float u1, const float v1,
+                         const tc *const color, const float opacity=1,
+                         const float precision=0.25, const unsigned int pattern=~0U,
+                         const bool init_hatch=true) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_spline() : Specified color is (null).",
+                                    cimg_instance);
+      if (is_empty()) return *this;
+      if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity);
+      bool ninit_hatch = init_hatch;
+      const float
+        ax = u0 + u1 + 2*(x0 - x1),
+        bx = 3*(x1 - x0) - 2*u0 - u1,
+        ay = v0 + v1 + 2*(y0 - y1),
+        by = 3*(y1 - y0) - 2*v0 - v1,
+        _precision = 1/(std::sqrt(cimg::sqr(x0-x1)+cimg::sqr(y0-y1))*(precision>0?precision:1));
+      int ox = x0, oy = y0;
+      for (float t = 0; t<1; t+=_precision) {
+        const float t2 = t*t, t3 = t2*t;
+        const int
+          nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
+          ny = (int)(ay*t3 + by*t2 + v0*t + y0);
+        draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch);
+        ninit_hatch = false;
+        ox = nx; oy = ny;
+      }
+      return draw_line(ox,oy,x1,y1,color,opacity,pattern,false);
+    }
+
+    //! Draw a cubic spline curve in the instance image (for volumetric images).
+    /**
+       \note
+       - Similar to CImg::draw_spline() for a 3d spline in a volumetric image.
+    **/
+    template<typename tc>
+    CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
+                         const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
+                         const tc *const color, const float opacity=1,
+                         const float precision=4, const unsigned int pattern=~0U,
+                         const bool init_hatch=true) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_spline() : Specified color is (null).",
+                                    cimg_instance);
+      if (is_empty()) return *this;
+      if (x0==x1 && y0==y1 && z0==z1) return draw_point(x0,y0,z0,color,opacity);
+      bool ninit_hatch = init_hatch;
+      const float
+        ax = u0 + u1 + 2*(x0 - x1),
+        bx = 3*(x1 - x0) - 2*u0 - u1,
+        ay = v0 + v1 + 2*(y0 - y1),
+        by = 3*(y1 - y0) - 2*v0 - v1,
+        az = w0 + w1 + 2*(z0 - z1),
+        bz = 3*(z1 - z0) - 2*w0 - w1,
+        _precision = 1/(std::sqrt(cimg::sqr(x0-x1)+cimg::sqr(y0-y1))*(precision>0?precision:1));
+      int ox = x0, oy = y0, oz = z0;
+      for (float t = 0; t<1; t+=_precision) {
+        const float t2 = t*t, t3 = t2*t;
+        const int
+          nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
+          ny = (int)(ay*t3 + by*t2 + v0*t + y0),
+          nz = (int)(az*t3 + bz*t2 + w0*t + z0);
+        draw_line(ox,oy,oz,nx,ny,nz,color,opacity,pattern,ninit_hatch);
+        ninit_hatch = false;
+        ox = nx; oy = ny; oz = nz;
+      }
+      return draw_line(ox,oy,oz,x1,y1,z1,color,opacity,pattern,false);
+    }
+
+    //! Draw a cubic spline curve in the instance image.
+    /**
+       \param x0 X-coordinate of the starting curve point
+       \param y0 Y-coordinate of the starting curve point
+       \param u0 X-coordinate of the starting velocity
+       \param v0 Y-coordinate of the starting velocity
+       \param x1 X-coordinate of the ending curve point
+       \param y1 Y-coordinate of the ending curve point
+       \param u1 X-coordinate of the ending velocity
+       \param v1 Y-coordinate of the ending velocity
+       \param texture Texture image defining line pixel colors.
+       \param tx0 X-coordinate of the starting texture point.
+       \param ty0 Y-coordinate of the starting texture point.
+       \param tx1 X-coordinate of the ending texture point.
+       \param ty1 Y-coordinate of the ending texture point.
+       \param precision Curve drawing precision (optional).
+       \param opacity Drawing opacity (optional).
+       \param pattern An integer whose bits describe the line pattern (optional).
+       \param init_hatch if \c true, reinit hatch motif.
+    **/
+    template<typename t>
+    CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
+                         const int x1, const int y1, const float u1, const float v1,
+                         const CImg<t>& texture,
+                         const int tx0, const int ty0, const int tx1, const int ty1,
+                         const float opacity=1,
+                         const float precision=4, const unsigned int pattern=~0U,
+                         const bool init_hatch=true) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (is_empty()) return *this;
+      if (is_overlapped(texture)) return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch);
+      if (x0==x1 && y0==y1) return draw_point(x0,y0,texture.get_vector_at(x0,y0),opacity);
+      bool ninit_hatch = init_hatch;
+      const float
+        ax = u0 + u1 + 2*(x0 - x1),
+        bx = 3*(x1 - x0) - 2*u0 - u1,
+        ay = v0 + v1 + 2*(y0 - y1),
+        by = 3*(y1 - y0) - 2*v0 - v1,
+        _precision = 1/(std::sqrt(cimg::sqr(x0-x1)+cimg::sqr(y0-y1))*(precision>0?precision:1));
+      int ox = x0, oy = y0, otx = tx0, oty = ty0;
+      for (float t = 0; t<1; t+=_precision) {
+        const float t2 = t*t, t3 = t2*t;
+        const int
+          nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
+          ny = (int)(ay*t3 + by*t2 + v0*t + y0),
+          ntx = tx0 + (int)((tx1-tx0)*t),
+          nty = ty0 + (int)((ty1-ty0)*t);
+        draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch);
+        ninit_hatch = false;
+        ox = nx; oy = ny; otx = ntx; oty = nty;
+      }
+      return draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false);
+    }
+
+    // Draw a set of connected spline curves in the instance image (internal).
+    template<typename tp, typename tt, typename tc>
+    CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
+                         const tc *const color, const float opacity=1,
+                         const bool close_set=false, const float precision=4,
+                         const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (is_empty() || !points || !tangents || points._width<2 || tangents._width<2) return *this;
+      bool ninit_hatch = init_hatch;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_spline() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+
+      case 2 : {
+        const int x0 = (int)points(0,0), y0 = (int)points(0,1);
+        const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1);
+        int ox = x0, oy = y0;
+        float ou = u0, ov = v0;
+        for (unsigned int i = 1; i<points._width; ++i) {
+          const int x = (int)points(i,0), y = (int)points(i,1);
+          const float u = (float)tangents(i,0), v = (float)tangents(i,1);
+          draw_spline(ox,oy,ou,ov,x,y,u,v,color,precision,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y; ou = u; ov = v;
+        }
+        if (close_set) draw_spline(ox,oy,ou,ov,x0,y0,u0,v0,color,precision,opacity,pattern,false);
+      } break;
+      default : {
+        const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
+        const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1), w0 = (float)tangents(0,2);
+        int ox = x0, oy = y0, oz = z0;
+        float ou = u0, ov = v0, ow = w0;
+        for (unsigned int i = 1; i<points._width; ++i) {
+          const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
+          const float u = (float)tangents(i,0), v = (float)tangents(i,1), w = (float)tangents(i,2);
+          draw_spline(ox,oy,oz,ou,ov,ow,x,y,z,u,v,w,color,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y; oz = z; ou = u; ov = v; ow = w;
+        }
+        if (close_set) draw_spline(ox,oy,oz,ou,ov,ow,x0,y0,z0,u0,v0,w0,color,precision,opacity,pattern,false);
+      }
+      }
+      return *this;
+    }
+
+    //! Draw a set of consecutive colored splines in the instance image.
+    template<typename tp, typename tc>
+    CImg<T>& draw_spline(const CImg<tp>& points,
+                         const tc *const color, const float opacity=1,
+                         const bool close_set=false, const float precision=4,
+                         const unsigned int pattern=~0U, const bool init_hatch=true) {
+      if (is_empty() || !points || points._width<2) return *this;
+      CImg<Tfloat> tangents;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_spline() : Invalid specified point set (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    points._width,points._height,points._depth,points._spectrum,points._data);
+      case 2 : {
+        tangents.assign(points._width,points._height);
+        cimg_forX(points,p) {
+          const unsigned int
+            p0 = close_set?(p+points._width-1)%points._width:(p?p-1:0),
+            p1 = close_set?(p+1)%points._width:(p+1<points._width?p+1:p);
+          const float
+            x = (float)points(p,0),
+            y = (float)points(p,1),
+            x0 = (float)points(p0,0),
+            y0 = (float)points(p0,1),
+            x1 = (float)points(p1,0),
+            y1 = (float)points(p1,1),
+            u0 = x - x0,
+            v0 = y - y0,
+            n0 = 1e-8f + (float)std::sqrt(u0*u0 + v0*v0),
+            u1 = x1 - x,
+            v1 = y1 - y,
+            n1 = 1e-8f + (float)std::sqrt(u1*u1 + v1*v1),
+            u = u0/n0 + u1/n1,
+            v = v0/n0 + v1/n1,
+            n = 1e-8f + (float)std::sqrt(u*u + v*v),
+            fact = 0.5f*(n0 + n1);
+          tangents(p,0) = (Tfloat)(fact*u/n);
+          tangents(p,1) = (Tfloat)(fact*v/n);
+        }
+      } break;
+      default : {
+        tangents.assign(points._width,points._height);
+        cimg_forX(points,p) {
+          const unsigned int
+            p0 = close_set?(p+points._width-1)%points._width:(p?p-1:0),
+            p1 = close_set?(p+1)%points._width:(p+1<points._width?p+1:p);
+          const float
+            x = (float)points(p,0),
+            y = (float)points(p,1),
+            z = (float)points(p,2),
+            x0 = (float)points(p0,0),
+            y0 = (float)points(p0,1),
+            z0 = (float)points(p0,2),
+            x1 = (float)points(p1,0),
+            y1 = (float)points(p1,1),
+            z1 = (float)points(p1,2),
+            u0 = x - x0,
+            v0 = y - y0,
+            w0 = z - z0,
+            n0 = 1e-8f + (float)std::sqrt(u0*u0 + v0*v0 + w0*w0),
+            u1 = x1 - x,
+            v1 = y1 - y,
+            w1 = z1 - z,
+            n1 = 1e-8f + (float)std::sqrt(u1*u1 + v1*v1 + w1*w1),
+            u = u0/n0 + u1/n1,
+            v = v0/n0 + v1/n1,
+            w = w0/n0 + w1/n1,
+            n = 1e-8f + (float)std::sqrt(u*u + v*v + w*w),
+            fact = 0.5f*(n0 + n1);
+          tangents(p,0) = (Tfloat)(fact*u/n);
+          tangents(p,1) = (Tfloat)(fact*v/n);
+          tangents(p,2) = (Tfloat)(fact*w/n);
+        }
+      }
+      }
+      return draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch);
+    }
+
+    // Inner macro for drawing triangles.
+#define _cimg_for_triangle1(img,xl,xr,y,x0,y0,x1,y1,x2,y2) \
+        for (int y = y0<0?0:y0, \
+               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+               _sxn=1, \
+               _sxr=1, \
+               _sxl=1, \
+               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+               _dyn = y2-y1, \
+               _dyr = y2-y0, \
+               _dyl = y1-y0, \
+               _counter = (_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+                           _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+                           _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+                           cimg::min((int)(img)._height-y-1,y2-y)), \
+               _errn = _dyn/2, \
+               _errr = _dyr/2, \
+               _errl = _dyl/2, \
+               _rxn = _dyn?(x2-x1)/_dyn:0, \
+               _rxr = _dyr?(x2-x0)/_dyr:0, \
+               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn); \
+             _counter>=0; --_counter, ++y, \
+               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+               xl+=(y!=y1)?_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0): \
+                           (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle2(img,xl,cl,xr,cr,y,x0,y0,c0,x1,y1,c1,x2,y2,c2) \
+        for (int y = y0<0?0:y0, \
+               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+               cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
+               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+               cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
+               _sxn=1, _scn=1, \
+               _sxr=1, _scr=1, \
+               _sxl=1, _scl=1, \
+               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+               _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
+               _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
+               _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
+               _dyn = y2-y1, \
+               _dyr = y2-y0, \
+               _dyl = y1-y0, \
+               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+                          _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
+                          _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
+                          _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
+                          cimg::min((int)(img)._height-y-1,y2-y)), \
+               _errn = _dyn/2, _errcn = _errn, \
+               _errr = _dyr/2, _errcr = _errr, \
+               _errl = _dyl/2, _errcl = _errl, \
+               _rxn = _dyn?(x2-x1)/_dyn:0, \
+               _rcn = _dyn?(c2-c1)/_dyn:0, \
+               _rxr = _dyr?(x2-x0)/_dyr:0, \
+               _rcr = _dyr?(c2-c0)/_dyr:0, \
+               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+               _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
+                                       (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ); \
+             _counter>=0; --_counter, ++y, \
+               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+               cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
+               xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
+                           _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+               (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
+                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle3(img,xl,txl,tyl,xr,txr,tyr,y,x0,y0,tx0,ty0,x1,y1,tx1,ty1,x2,y2,tx2,ty2) \
+        for (int y = y0<0?0:y0, \
+               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
+               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
+               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
+               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
+               _sxn=1, _stxn=1, _styn=1, \
+               _sxr=1, _stxr=1, _styr=1, \
+               _sxl=1, _stxl=1, _styl=1, \
+               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
+               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
+               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
+               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
+               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
+               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
+               _dyn = y2-y1, \
+               _dyr = y2-y0, \
+               _dyl = y1-y0, \
+               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
+                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
+                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
+                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
+                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
+                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
+                          cimg::min((int)(img)._height-y-1,y2-y)), \
+               _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, \
+               _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, \
+               _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, \
+               _rxn = _dyn?(x2-x1)/_dyn:0, \
+               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
+               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
+               _rxr = _dyr?(x2-x0)/_dyr:0, \
+               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
+               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
+               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
+                                       (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
+               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
+                                       (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
+             _counter>=0; --_counter, ++y, \
+               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
+               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
+               xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
+                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
+                           _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+               (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
+                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1,\
+                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle4(img,xl,cl,txl,tyl,xr,cr,txr,tyr,y,x0,y0,c0,tx0,ty0,x1,y1,c1,tx1,ty1,x2,y2,c2,tx2,ty2) \
+        for (int y = y0<0?0:y0, \
+               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+               cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
+               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
+               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
+               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+               cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
+               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
+               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
+               _sxn=1, _scn=1, _stxn=1, _styn=1, \
+               _sxr=1, _scr=1, _stxr=1, _styr=1, \
+               _sxl=1, _scl=1, _stxl=1, _styl=1, \
+               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
+               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
+               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
+               _dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
+               _dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
+               _dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
+               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
+               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
+               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
+               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
+               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
+               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
+               _dyn = y2-y1, \
+               _dyr = y2-y0, \
+               _dyl = y1-y0, \
+               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+                          _dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
+                          _dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
+                          _dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
+                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
+                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
+                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
+                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
+                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
+                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
+                          cimg::min((int)(img)._height-y-1,y2-y)), \
+               _errn = _dyn/2, _errcn = _errn, _errtxn = _errn, _errtyn = _errn, \
+               _errr = _dyr/2, _errcr = _errr, _errtxr = _errr, _errtyr = _errr, \
+               _errl = _dyl/2, _errcl = _errl, _errtxl = _errl, _errtyl = _errl, \
+               _rxn = _dyn?(x2-x1)/_dyn:0, \
+               _rcn = _dyn?(c2-c1)/_dyn:0, \
+               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
+               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
+               _rxr = _dyr?(x2-x0)/_dyr:0, \
+               _rcr = _dyr?(c2-c0)/_dyr:0, \
+               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
+               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
+               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+               _rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
+                                       (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ), \
+               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
+                                        (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
+               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
+                                        (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
+             _counter>=0; --_counter, ++y, \
+               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+               cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
+               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
+               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
+               xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
+                            txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
+                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
+                            _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+               (_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
+                _errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
+                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
+                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+#define _cimg_for_triangle5(img,xl,txl,tyl,lxl,lyl,xr,txr,tyr,lxr,lyr,y,x0,y0,tx0,ty0,lx0,ly0,x1,y1,tx1,ty1,lx1,ly1,x2,y2,tx2,ty2,lx2,ly2) \
+        for (int y = y0<0?0:y0, \
+               xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
+               txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
+               tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
+               lxr = y0>=0?lx0:(lx0-y0*(lx2-lx0)/(y2-y0)), \
+               lyr = y0>=0?ly0:(ly0-y0*(ly2-ly0)/(y2-y0)), \
+               xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
+               txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
+               tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
+               lxl = y1>=0?(y0>=0?(y0==y1?lx1:lx0):(lx0-y0*(lx1-lx0)/(y1-y0))):(lx1-y1*(lx2-lx1)/(y2-y1)), \
+               lyl = y1>=0?(y0>=0?(y0==y1?ly1:ly0):(ly0-y0*(ly1-ly0)/(y1-y0))):(ly1-y1*(ly2-ly1)/(y2-y1)), \
+               _sxn=1, _stxn=1, _styn=1, _slxn=1, _slyn=1, \
+               _sxr=1, _stxr=1, _styr=1, _slxr=1, _slyr=1, \
+               _sxl=1, _stxl=1, _styl=1, _slxl=1, _slyl=1, \
+               _dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), _dyn = y2-y1, \
+               _dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), _dyr = y2-y0, \
+               _dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), _dyl = y1-y0, \
+               _dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
+               _dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
+               _dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
+               _dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
+               _dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
+               _dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
+               _dlxn = lx2>lx1?lx2-lx1:(_slxn=-1,lx1-lx2), \
+               _dlxr = lx2>lx0?lx2-lx0:(_slxr=-1,lx0-lx2), \
+               _dlxl = lx1>lx0?lx1-lx0:(_slxl=-1,lx0-lx1), \
+               _dlyn = ly2>ly1?ly2-ly1:(_slyn=-1,ly1-ly2), \
+               _dlyr = ly2>ly0?ly2-ly0:(_slyr=-1,ly0-ly2), \
+               _dlyl = ly1>ly0?ly1-ly0:(_slyl=-1,ly0-ly1), \
+               _counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
+                          _dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
+                          _dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
+                          _dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
+                          _dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
+                          _dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
+                          _dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
+                          _dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
+                          _dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
+                          _dlxn-=_dyn?_dyn*(_dlxn/_dyn):0, \
+                          _dlxr-=_dyr?_dyr*(_dlxr/_dyr):0, \
+                          _dlxl-=_dyl?_dyl*(_dlxl/_dyl):0, \
+                          _dlyn-=_dyn?_dyn*(_dlyn/_dyn):0, \
+                          _dlyr-=_dyr?_dyr*(_dlyr/_dyr):0, \
+                          _dlyl-=_dyl?_dyl*(_dlyl/_dyl):0, \
+                          cimg::min((int)(img)._height-y-1,y2-y)), \
+               _errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, _errlxn = _errn, _errlyn = _errn, \
+               _errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, _errlxr = _errr, _errlyr = _errr, \
+               _errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, _errlxl = _errl, _errlyl = _errl, \
+               _rxn = _dyn?(x2-x1)/_dyn:0, \
+               _rtxn = _dyn?(tx2-tx1)/_dyn:0, \
+               _rtyn = _dyn?(ty2-ty1)/_dyn:0, \
+               _rlxn = _dyn?(lx2-lx1)/_dyn:0, \
+               _rlyn = _dyn?(ly2-ly1)/_dyn:0, \
+               _rxr = _dyr?(x2-x0)/_dyr:0, \
+               _rtxr = _dyr?(tx2-tx0)/_dyr:0, \
+               _rtyr = _dyr?(ty2-ty0)/_dyr:0, \
+               _rlxr = _dyr?(lx2-lx0)/_dyr:0, \
+               _rlyr = _dyr?(ly2-ly0)/_dyr:0, \
+               _rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
+                                       (_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
+               _rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
+                                        (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
+               _rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
+                                        (_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ), \
+               _rlxl = (y0!=y1 && y1>0)?(_dyl?(lx1-lx0)/_dyl:0): \
+                                        (_errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxn ), \
+               _rlyl = (y0!=y1 && y1>0)?(_dyl?(ly1-ly0)/_dyl:0): \
+                                        (_errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyn ); \
+             _counter>=0; --_counter, ++y, \
+               xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
+               txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
+               tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
+               lxr+=_rlxr+((_errlxr-=_dlxr)<0?_errlxr+=_dyr,_slxr:0), \
+               lyr+=_rlyr+((_errlyr-=_dlyr)<0?_errlyr+=_dyr,_slyr:0), \
+               xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
+                            tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
+                            lxl+=_rlxl+((_errlxl-=_dlxl)<0?(_errlxl+=_dyl,_slxl):0), \
+                            lyl+=_rlyl+((_errlyl-=_dlyl)<0?(_errlyl+=_dyl,_slyl):0), \
+                            _rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
+               (_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
+                _errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
+                _errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxl=_rlxn, lxl=lx1, \
+                _errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyl=_rlyn, lyl=ly1, \
+                _errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
+
+    // Draw a colored triangle (inner routine, uses bresenham's algorithm).
+    template<typename tc>
+    CImg<T>& _draw_triangle(const int x0, const int y0,
+                            const int x1, const int y1,
+                            const int x2, const int y2,
+                            const tc *const color, const float opacity,
+                            const float brightness) {
+      _draw_scanline(color,opacity);
+      const float nbrightness = brightness<0?0:(brightness>2?2:brightness);
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2);
+      if (ny0<height() && ny2>=0) {
+        if ((nx1 - nx0)*(ny2 - ny0) - (nx2 - nx0)*(ny1 - ny0)<0)
+          _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xl,xr,y,color,opacity,nbrightness);
+        else
+          _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xr,xl,y,color,opacity,nbrightness);
+      }
+      return *this;
+    }
+
+    //! Draw a 2d filled colored triangle.
+    template<typename tc>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const tc *const color, const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1);
+      return *this;
+    }
+
+    //! Draw a 2d outlined colored triangle.
+    template<typename tc>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const tc *const color, const float opacity,
+                           const unsigned int pattern) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      draw_line(x0,y0,x1,y1,color,opacity,pattern,true).
+        draw_line(x1,y1,x2,y2,color,opacity,pattern,false).
+        draw_line(x2,y2,x0,y0,color,opacity,pattern,false);
+      return *this;
+    }
+
+    //! Draw a 2d filled colored triangle, with z-buffering.
+    template<typename tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+                           const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const tc *const color, const float opacity=1,
+                           const float brightness=1) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Specified color is (null).",
+                                    cimg_instance);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float
+        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+        nbrightness = brightness<0?0:(brightness>2?2:brightness);
+      const int whd = _width*_height*_depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2);
+      if (ny0>=height() || ny2<0) return *this;
+      tzfloat
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+      _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
+        if (y==ny1) { zl = nz1; pzl = pzn; }
+        int xleft = xleft0, xright = xright0;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright);
+        const int dx = xright - xleft;
+        const tzfloat pentez = (zright - zleft)/dx;
+        if (xleft<0 && dx) zleft-=xleft*(zright - zleft)/dx;
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width() - 1;
+        T* ptrd = data(xleft,y,0,0);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nbrightness*(*col++)); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          }
+        } else {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color; cimg_forC(*this,c) { *ptrd = (T)(nopacity*nbrightness**(col++) + *ptrd*copacity); ptrd+=whd; }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+          }
+        }
+        zr+=pzr; zl+=pzl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Gouraud-shaded colored triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the 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 spectrum() values of type \c T, defining the global drawing color.
+       \param brightness0 = brightness of the first corner (in [0,2]).
+       \param brightness1 = brightness of the second corner (in [0,2]).
+       \param brightness2 = brightness of the third corner (in [0,2]).
+       \param opacity = opacity of the drawing.
+       \note Clipping is supported.
+    **/
+    template<typename tc>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const tc *const color,
+                           const float brightness0,
+                           const float brightness1,
+                           const float brightness2,
+                           const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nc0,nc1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nc0,nc2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nc1,nc2);
+      if (ny0>=height() || ny2<0) return *this;
+      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+        int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
+        if (xright<xleft) cimg::swap(xleft,xright,cleft,cright);
+        const int
+          dx = xright - xleft,
+          dc = cright>cleft?cright - cleft:cleft - cright,
+          rc = dx?(cright - cleft)/dx:0,
+          sc = cright>cleft?1:-1,
+          ndc = dc-(dx?dx*(dc/dx):0);
+        int errc = dx>>1;
+        if (xleft<0 && dx) cleft-=xleft*(cright - cleft)/dx;
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width() - 1;
+        T* ptrd = data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = color;
+          cimg_forC(*this,c) {
+            *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+            ptrd+=whd;
+          }
+          ptrd-=offx;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+        } else for (int x = xleft; x<=xright; ++x) {
+          const tc *col = color;
+          cimg_forC(*this,c) {
+            const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd;
+          }
+          ptrd-=offx;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Gouraud-shaded colored triangle, with z-buffering.
+    template<typename tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+                           const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const tc *const color,
+                           const float brightness0,
+                           const float brightness1,
+                           const float brightness2,
+                           const float opacity=1) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Specified color is (null).",
+                                    cimg_instance);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1,nc0,nc1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2,nc0,nc2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2,nc1,nc2);
+      if (ny0>=height() || ny2<0) return *this;
+      tzfloat
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+        if (y==ny1) { zl = nz1; pzl = pzn; }
+        int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,cleft,cright);
+        const int
+          dx = xright - xleft,
+          dc = cright>cleft?cright - cleft:cleft - cright,
+          rc = dx?(cright-cleft)/dx:0,
+          sc = cright>cleft?1:-1,
+          ndc = dc-(dx?dx*(dc/dx):0);
+        const tzfloat pentez = (zright - zleft)/dx;
+        int errc = dx>>1;
+        if (xleft<0 && dx) {
+          cleft-=xleft*(cright - cleft)/dx;
+          zleft-=xleft*(zright - zleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T *ptrd = data(xleft,y);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                *ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+                ptrd+=whd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+            cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+          } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+            cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+          }
+        zr+=pzr; zl+=pzl;
+      }
+      return *this;
+    }
+
+    //! Draw a colored triangle with interpolated colors.
+    template<typename tc1, typename tc2, typename tc3>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const tc1 *const color1,
+                           const tc2 *const color2,
+                           const tc3 *const color3,
+                           const float opacity=1) {
+      const unsigned char one = 1;
+      cimg_forC(*this,c) get_shared_channel(c).draw_triangle(x0,y0,x1,y1,x2,y2,&one,color1[c],color2[c],color3[c],opacity);
+      return *this;
+    }
+
+    //! Draw a 2d textured triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the instance image.
+       \param y0 = Y-coordinate of the first corner in the instance image.
+       \param x1 = X-coordinate of the second corner in the instance image.
+       \param y1 = Y-coordinate of the second corner in the instance image.
+       \param x2 = X-coordinate of the third corner in the instance image.
+       \param y2 = Y-coordinate of the third corner in the instance image.
+       \param texture = texture image used to fill the triangle.
+       \param tx0 = X-coordinate of the first corner in the texture image.
+       \param ty0 = Y-coordinate of the first corner in the texture image.
+       \param tx1 = X-coordinate of the second corner in the texture image.
+       \param ty1 = Y-coordinate of the second corner in the texture image.
+       \param tx2 = X-coordinate of the third corner in the texture image.
+       \param ty2 = Y-coordinate of the third corner in the texture image.
+       \param opacity = opacity of the drawing.
+       \param brightness = brightness of the drawing (in [0,2]).
+       \note Clipping is supported, but texture coordinates do not support clipping.
+    **/
+    template<typename tc>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const float opacity=1,
+                           const float brightness=1) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty()) return *this;
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float
+        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+        nbrightness = brightness<0?0:(brightness>2?2:brightness);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2);
+      if (ny0>=height() || ny2<0) return *this;
+      _cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y,
+                          nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) {
+        int
+          xleft = xleft0, xright = xright0,
+          txleft = txleft0, txright = txright0,
+          tyleft = tyleft0, tyright = tyright0;
+        if (xright<xleft) cimg::swap(xleft,xright,txleft,txright,tyleft,tyright);
+        const int
+          dx = xright - xleft,
+          dtx = txright>txleft?txright - txleft:txleft - txright,
+          dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
+          rtx = dx?(txright - txleft)/dx:0,
+          rty = dx?(tyright - tyleft)/dx:0,
+          stx = txright>txleft?1:-1,
+          sty = tyright>tyleft?1:-1,
+          ndtx = dtx - (dx?dx*(dtx/dx):0),
+          ndty = dty - (dx?dx*(dty/dx):0);
+        int errtx = dx>>1, errty = errtx;
+        if (xleft<0 && dx) {
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+            const tc *col = texture.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)*col;
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx;
+            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
+            const tc *col = texture.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nbrightness**col);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx;
+            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+          } else for (int x = xleft; x<=xright; ++x) {
+            const tc *col = texture.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx;
+            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+          }
+        } else {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+            const tc *col = texture.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx;
+            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
+            const tc *col = texture.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx;
+            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+          } else for (int x = xleft; x<=xright; ++x) {
+            const tc *col = texture.data(txleft,tyleft);
+            cimg_forC(*this,c) {
+              const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
+              *ptrd = (T)(nopacity*val + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx;
+            txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+            tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d textured triangle, with perspective correction.
+    template<typename tc>
+    CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const float opacity=1,
+                           const float brightness=1) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float
+        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+        nbrightness = brightness<0?0:(brightness>2?2:brightness);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+      float
+        ntx0 = tx0/z0, nty0 = ty0/z0,
+        ntx1 = tx1/z1, nty1 = ty1/z1,
+        ntx2 = tx2/z2, nty2 = ty2/z2,
+        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
+      if (ny0>=height() || ny2<0) return *this;
+      float
+        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+        ptyl = (nty1 - nty0)/(ny1 - ny0),
+        ptyr = (nty2 - nty0)/(ny2 - ny0),
+        ptyn = (nty2 - nty1)/(ny2 - ny1),
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+      _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
+        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int xleft = xleft0, xright = xright0;
+        float
+          zleft = zl, zright = zr,
+          txleft = txl, txright = txr,
+          tyleft = tyl, tyright = tyr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
+        const int dx = xright - xleft;
+        const float
+          pentez = (zright - zleft)/dx,
+          pentetx = (txright - txleft)/dx,
+          pentety = (tyright - tyleft)/dx;
+        if (xleft<0 && dx) {
+          zleft-=xleft*(zright - zleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+            const float invz = 1/zleft;
+            const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)*col;
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          } else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
+            const float invz = 1/zleft;
+            const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nbrightness**col);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          } else for (int x = xleft; x<=xright; ++x) {
+            const float invz = 1/zleft;
+            const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          }
+        } else {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
+            const float invz = 1/zleft;
+            const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
+            const float invz = 1/zleft;
+            const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          } else for (int x = xleft; x<=xright; ++x) {
+            const float invz = 1/zleft;
+            const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+            cimg_forC(*this,c) {
+              const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+              *ptrd = (T)(nopacity*val + *ptrd*copacity);
+              ptrd+=whd; col+=twhd;
+            }
+            ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          }
+        }
+        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d textured triangle, with z-buffering and perspective correction.
+    template<typename tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+                           const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const float opacity=1,
+                           const float brightness=1) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float
+        nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
+        nbrightness = brightness<0?0:(brightness>2?2:brightness);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
+      float
+        ntx0 = tx0/z0, nty0 = ty0/z0,
+        ntx1 = tx1/z1, nty1 = ty1/z1,
+        ntx2 = tx2/z2, nty2 = ty2/z2;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
+      if (ny0>=height() || ny2<0) return *this;
+      float
+        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+        ptyl = (nty1 - nty0)/(ny1 - ny0),
+        ptyr = (nty2 - nty0)/(ny2 - ny0),
+        ptyn = (nty2 - nty1)/(ny2 - ny1),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+      tzfloat
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+      _cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
+        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int xleft = xleft0, xright = xright0;
+        float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
+        const int dx = xright - xleft;
+        const float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx;
+        const tzfloat pentez = (zright - zleft)/dx;
+        if (xleft<0 && dx) {
+          zleft-=xleft*(zright - zleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T *ptrd = data(xleft,y,0,0);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)*col;
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)(nbrightness**col);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            }
+        } else {
+          if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)(nopacity**col + *ptrd*copacity);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+              if (zleft>=(tzfloat)*ptrz) {
+                *ptrz = (tz)zleft;
+                const tzfloat invz = 1/zleft;
+                const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+                cimg_forC(*this,c) {
+                  const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
+                  *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                  ptrd+=whd; col+=twhd;
+                }
+                ptrd-=offx;
+              }
+              zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            }
+        }
+        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Pseudo-Phong-shaded triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the 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 spectrum() values of type \c T, defining the global drawing color.
+       \param light = light image.
+       \param lx0 = X-coordinate of the first corner in the light image.
+       \param ly0 = Y-coordinate of the first corner in the light image.
+       \param lx1 = X-coordinate of the second corner in the light image.
+       \param ly1 = Y-coordinate of the second corner in the light image.
+       \param lx2 = X-coordinate of the third corner in the light image.
+       \param ly2 = Y-coordinate of the third corner in the light image.
+       \param opacity = opacity of the drawing.
+       \note Clipping is supported, but texture coordinates do not support clipping.
+    **/
+    template<typename tc, typename tl>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const tc *const color,
+                           const CImg<tl>& light,
+                           const int lx0, const int ly0,
+                           const int lx1, const int ly1,
+                           const int lx2, const int ly2,
+                           const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle : Specified color is (null).",
+                                    cimg_instance);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+
+      if (is_empty()) return *this;
+      if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+      const int whd = _width*_height*_depth, offx = _spectrum*whd-1;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2);
+      if (ny0>=height() || ny2<0) return *this;
+      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+        int
+          xleft = xleft0, xright = xright0,
+          lxleft = lxleft0, lxright = lxright0,
+          lyleft = lyleft0, lyright = lyright0;
+        if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright);
+        const int
+          dx = xright - xleft,
+          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+          rlx = dx?(lxright - lxleft)/dx:0,
+          rly = dx?(lyright - lyleft)/dx:0,
+          slx = lxright>lxleft?1:-1,
+          sly = lyright>lyleft?1:-1,
+          ndlx = dlx - (dx?dx*(dlx/dx):0),
+          ndly = dly - (dx?dx*(dly/dx):0);
+        int errlx = dx>>1, errly = errlx;
+        if (xleft<0 && dx) {
+          lxleft-=xleft*(lxright - lxleft)/dx;
+          lyleft-=xleft*(lyright - lyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = color;
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            *ptrd = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
+            ptrd+=whd;
+          }
+          ptrd-=offx;
+          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+        } else  for (int x = xleft; x<=xright; ++x) {
+          const tc *col = color;
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            const T val = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd;
+          }
+          ptrd-=offx;
+          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Pseudo-Phong-shaded triangle, with z-buffering.
+    template<typename tz, typename tc, typename tl>
+    CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+                           const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const tc *const color,
+                           const CImg<tl>& light,
+                           const int lx0, const int ly0,
+                           const int lx1, const int ly1,
+                           const int lx2, const int ly2,
+                           const float opacity=1) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Specified color is (null).",
+                                    cimg_instance);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,
+                                                     +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2);
+      if (ny0>=height() || ny2<0) return *this;
+      tzfloat
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+        if (y==ny1) { zl = nz1; pzl = pzn; }
+        int
+          xleft = xleft0, xright = xright0,
+          lxleft = lxleft0, lxright = lxright0,
+          lyleft = lyleft0, lyright = lyright0;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,lxleft,lxright,lyleft,lyright);
+        const int
+          dx = xright - xleft,
+          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+          rlx = dx?(lxright - lxleft)/dx:0,
+          rly = dx?(lyright - lyleft)/dx:0,
+          slx = lxright>lxleft?1:-1,
+          sly = lyright>lyleft?1:-1,
+          ndlx = dlx - (dx?dx*(dlx/dx):0),
+          ndly = dly - (dx?dx*(dly/dx):0);
+        const tzfloat pentez = (zright - zleft)/dx;
+        int errlx = dx>>1, errly = errlx;
+        if (xleft<0 && dx) {
+          zleft-=xleft*(zright - zleft)/dx;
+          lxleft-=xleft*(lxright - lxleft)/dx;
+          lyleft-=xleft*(lyright - lyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T *ptrd = data(xleft,y,0,0);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                const tc cval = *(col++);
+                *ptrd = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
+                ptrd+=whd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+            lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+            lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tc *col = color;
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                const tc cval = *(col++);
+                const T val = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez;
+            lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+            lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+          }
+        zr+=pzr; zl+=pzl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Gouraud-shaded textured triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the instance image.
+       \param y0 = Y-coordinate of the first corner in the instance image.
+       \param x1 = X-coordinate of the second corner in the instance image.
+       \param y1 = Y-coordinate of the second corner in the instance image.
+       \param x2 = X-coordinate of the third corner in the instance image.
+       \param y2 = Y-coordinate of the third corner in the instance image.
+       \param texture = texture image used to fill the triangle.
+       \param tx0 = X-coordinate of the first corner in the texture image.
+       \param ty0 = Y-coordinate of the first corner in the texture image.
+       \param tx1 = X-coordinate of the second corner in the texture image.
+       \param ty1 = Y-coordinate of the second corner in the texture image.
+       \param tx2 = X-coordinate of the third corner in the texture image.
+       \param ty2 = Y-coordinate of the third corner in the texture image.
+       \param brightness0 = brightness value of the first corner.
+       \param brightness1 = brightness value of the second corner.
+       \param brightness2 = brightness value of the third corner.
+       \param opacity = opacity of the drawing.
+       \note Clipping is supported, but texture coordinates do not support clipping.
+    **/
+    template<typename tc>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const float brightness0,
+                           const float brightness1,
+                           const float brightness2,
+                           const float opacity=1) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty()) return *this;
+      if (is_overlapped(texture))
+        return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,brightness0,brightness1,brightness2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2);
+      if (ny0>=height() || ny2<0) return *this;
+      _cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y,
+                          nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) {
+        int
+          xleft = xleft0, xright = xright0,
+          cleft = cleft0, cright = cright0,
+          txleft = txleft0, txright = txright0,
+          tyleft = tyleft0, tyright = tyright0;
+        if (xright<xleft) cimg::swap(xleft,xright,cleft,cright,txleft,txright,tyleft,tyright);
+        const int
+          dx = xright - xleft,
+          dc = cright>cleft?cright - cleft:cleft - cright,
+          dtx = txright>txleft?txright - txleft:txleft - txright,
+          dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
+          rc = dx?(cright - cleft)/dx:0,
+          rtx = dx?(txright - txleft)/dx:0,
+          rty = dx?(tyright - tyleft)/dx:0,
+          sc = cright>cleft?1:-1,
+          stx = txright>txleft?1:-1,
+          sty = tyright>tyleft?1:-1,
+          ndc = dc - (dx?dx*(dc/dx):0),
+          ndtx = dtx - (dx?dx*(dtx/dx):0),
+          ndty = dty - (dx?dx*(dty/dx):0);
+        int errc = dx>>1, errtx = errc, errty = errc;
+        if (xleft<0 && dx) {
+          cleft-=xleft*(cright - cleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = texture.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+        } else for (int x = xleft; x<=xright; ++x) {
+          const tc *col = texture.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Gouraud-shaded textured triangle, with perspective correction.
+    template<typename tc>
+    CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const float brightness0,
+                           const float brightness1,
+                           const float brightness2,
+                           const float opacity=1) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
+                                                       brightness0,brightness1,brightness2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      float
+        ntx0 = tx0/z0, nty0 = ty0/z0,
+        ntx1 = tx1/z1, nty1 = ty1/z1,
+        ntx2 = tx2/z2, nty2 = ty2/z2,
+        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
+      if (ny0>=height() || ny2<0) return *this;
+      float
+        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+        ptyl = (nty1 - nty0)/(ny1 - ny0),
+        ptyr = (nty2 - nty0)/(ny2 - ny0),
+        ptyn = (nty2 - nty1)/(ny2 - ny1),
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int
+          xleft = xleft0, xright = xright0,
+          cleft = cleft0, cright = cright0;
+        float
+          zleft = zl, zright = zr,
+          txleft = txl, txright = txr,
+          tyleft = tyl, tyright = tyr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
+        const int
+          dx = xright - xleft,
+          dc = cright>cleft?cright - cleft:cleft - cright,
+          rc = dx?(cright - cleft)/dx:0,
+          sc = cright>cleft?1:-1,
+          ndc = dc - (dx?dx*(dc/dx):0);
+        const float
+          pentez = (zright - zleft)/dx,
+          pentetx = (txright - txleft)/dx,
+          pentety = (tyright - tyleft)/dx;
+        int errc = dx>>1;
+        if (xleft<0 && dx) {
+          cleft-=xleft*(cright - cleft)/dx;
+          zleft-=xleft*(zright - zleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const float invz = 1/zleft;
+          const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+        } else for (int x = xleft; x<=xright; ++x) {
+          const float invz = 1/zleft;
+          const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+        }
+        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Gouraud-shaded textured triangle, with z-buffering and perspective correction.
+    template<typename tz, typename tc>
+    CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+                           const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const float brightness0,
+                           const float brightness1,
+                           const float brightness2,
+                           const float opacity=1) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_line() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
+                                                       brightness0,brightness1,brightness2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nc0 = (int)((brightness0<0.0f?0.0f:(brightness0>2.0f?2.0f:brightness0))*256.0f),
+        nc1 = (int)((brightness1<0.0f?0.0f:(brightness1>2.0f?2.0f:brightness1))*256.0f),
+        nc2 = (int)((brightness2<0.0f?0.0f:(brightness2>2.0f?2.0f:brightness2))*256.0f);
+      float
+        ntx0 = tx0/z0, nty0 = ty0/z0,
+        ntx1 = tx1/z1, nty1 = ty1/z1,
+        ntx2 = tx2/z2, nty2 = ty2/z2;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
+      if (ny0>=height() || ny2<0) return *this;
+      float
+        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+        ptyl = (nty1 - nty0)/(ny1 - ny0),
+        ptyr = (nty2 - nty0)/(ny2 - ny0),
+        ptyn = (nty2 - nty1)/(ny2 - ny1),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+      tzfloat
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+      _cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
+        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
+        float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
+        const int
+          dx = xright - xleft,
+          dc = cright>cleft?cright - cleft:cleft - cright,
+          rc = dx?(cright - cleft)/dx:0,
+          sc = cright>cleft?1:-1,
+          ndc = dc - (dx?dx*(dc/dx):0);
+        float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx;
+        const tzfloat pentez = (zright - zleft)/dx;
+        int errc = dx>>1;
+        if (xleft<0 && dx) {
+          cleft-=xleft*(cright - cleft)/dx;
+          zleft-=xleft*(zright - zleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                *ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+                ptrd+=whd; col+=twhd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+          } else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd; col+=twhd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
+          }
+        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Pseudo-Phong-shaded textured triangle.
+    /**
+       \param x0 = X-coordinate of the first corner in the instance image.
+       \param y0 = Y-coordinate of the first corner in the instance image.
+       \param x1 = X-coordinate of the second corner in the instance image.
+       \param y1 = Y-coordinate of the second corner in the instance image.
+       \param x2 = X-coordinate of the third corner in the instance image.
+       \param y2 = Y-coordinate of the third corner in the instance image.
+       \param texture = texture image used to fill the triangle.
+       \param tx0 = X-coordinate of the first corner in the texture image.
+       \param ty0 = Y-coordinate of the first corner in the texture image.
+       \param tx1 = X-coordinate of the second corner in the texture image.
+       \param ty1 = Y-coordinate of the second corner in the texture image.
+       \param tx2 = X-coordinate of the third corner in the texture image.
+       \param ty2 = Y-coordinate of the third corner in the texture image.
+       \param light = light image.
+       \param lx0 = X-coordinate of the first corner in the light image.
+       \param ly0 = Y-coordinate of the first corner in the light image.
+       \param lx1 = X-coordinate of the second corner in the light image.
+       \param ly1 = Y-coordinate of the second corner in the light image.
+       \param lx2 = X-coordinate of the third corner in the light image.
+       \param ly2 = Y-coordinate of the third corner in the light image.
+       \param opacity = opacity of the drawing.
+       \note Clipping is supported, but texture coordinates do not support clipping.
+    **/
+    template<typename tc, typename tl>
+    CImg<T>& draw_triangle(const int x0, const int y0,
+                           const int x1, const int y1,
+                           const int x2, const int y2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const CImg<tl>& light,
+                           const int lx0, const int ly0,
+                           const int lx1, const int ly1,
+                           const int lx2, const int ly2,
+                           const float opacity=1) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+
+      if (is_empty()) return *this;
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      if (is_overlapped(light))   return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
+        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2);
+      if (ny0>=height() || ny2<0) return *this;
+      _cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y,
+                          nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) {
+        int
+          xleft = xleft0, xright = xright0,
+          lxleft = lxleft0, lxright = lxright0,
+          lyleft = lyleft0, lyright = lyright0,
+          txleft = txleft0, txright = txright0,
+          tyleft = tyleft0, tyright = tyright0;
+        if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright,txleft,txright,tyleft,tyright);
+        const int
+          dx = xright - xleft,
+          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+          dtx = txright>txleft?txright - txleft:txleft - txright,
+          dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
+          rlx = dx?(lxright - lxleft)/dx:0,
+          rly = dx?(lyright - lyleft)/dx:0,
+          rtx = dx?(txright - txleft)/dx:0,
+          rty = dx?(tyright - tyleft)/dx:0,
+          slx = lxright>lxleft?1:-1,
+          sly = lyright>lyleft?1:-1,
+          stx = txright>txleft?1:-1,
+          sty = tyright>tyleft?1:-1,
+          ndlx = dlx - (dx?dx*(dlx/dx):0),
+          ndly = dly - (dx?dx*(dly/dx):0),
+          ndtx = dtx - (dx?dx*(dtx/dx):0),
+          ndty = dty - (dx?dx*(dty/dx):0);
+        int errlx = dx>>1, errly = errlx, errtx = errlx, errty = errlx;
+        if (xleft<0 && dx) {
+          lxleft-=xleft*(lxright - lxleft)/dx;
+          lyleft-=xleft*(lyright - lyleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const tc *col = texture.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx;
+          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+        } else for (int x = xleft; x<=xright; ++x) {
+          const tc *col = texture.data(txleft,tyleft);
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx;
+          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+          txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+          tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Pseudo-Phong-shaded textured triangle, with perspective correction.
+    template<typename tc, typename tl>
+    CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const CImg<tl>& light,
+                           const int lx0, const int ly0,
+                           const int lx1, const int ly1,
+                           const int lx2, const int ly2,
+                           const float opacity=1) {
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      if (is_overlapped(light)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd-1;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+      float
+        ntx0 = tx0/z0, nty0 = ty0/z0,
+        ntx1 = tx1/z1, nty1 = ty1/z1,
+        ntx2 = tx2/z2, nty2 = ty2/z2,
+        nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
+      if (ny0>=height() || ny2<0) return *this;
+      float
+        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+        ptyl = (nty1 - nty0)/(ny1 - ny0),
+        ptyr = (nty2 - nty0)/(ny2 - ny0),
+        ptyn = (nty2 - nty1)/(ny2 - ny1),
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
+        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int
+          xleft = xleft0, xright = xright0,
+          lxleft = lxleft0, lxright = lxright0,
+          lyleft = lyleft0, lyright = lyright0;
+        float
+          zleft = zl, zright = zr,
+          txleft = txl, txright = txr,
+          tyleft = tyl, tyright = tyr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
+        const int
+          dx = xright - xleft,
+          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+          rlx = dx?(lxright - lxleft)/dx:0,
+          rly = dx?(lyright - lyleft)/dx:0,
+          slx = lxright>lxleft?1:-1,
+          sly = lyright>lyleft?1:-1,
+          ndlx = dlx - (dx?dx*(dlx/dx):0),
+          ndly = dly - (dx?dx*(dly/dx):0);
+        const float
+          pentez = (zright - zleft)/dx,
+          pentetx = (txright - txleft)/dx,
+          pentety = (tyright - tyleft)/dx;
+        int errlx = dx>>1, errly = errlx;
+        if (xleft<0 && dx) {
+          zleft-=xleft*(zright - zleft)/dx;
+          lxleft-=xleft*(lxright - lxleft)/dx;
+          lyleft-=xleft*(lyright - lyleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y,0,0);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
+          const float invz = 1/zleft;
+          const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+        } else for (int x = xleft; x<=xright; ++x) {
+          const float invz = 1/zleft;
+          const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+          cimg_forC(*this,c) {
+            const tl l = light(lxleft,lyleft,c);
+            const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+            *ptrd = (T)(nopacity*val + *ptrd*copacity);
+            ptrd+=whd; col+=twhd;
+          }
+          ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+          lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+          lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+        }
+        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+      }
+      return *this;
+    }
+
+    //! Draw a 2d Pseudo-Phong-shaded textured triangle, with z-buffering and perspective correction.
+    template<typename tz, typename tc, typename tl>
+    CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+                           const int x0, const int y0, const float z0,
+                           const int x1, const int y1, const float z1,
+                           const int x2, const int y2, const float z2,
+                           const CImg<tc>& texture,
+                           const int tx0, const int ty0,
+                           const int tx1, const int ty1,
+                           const int tx2, const int ty2,
+                           const CImg<tl>& light,
+                           const int lx0, const int ly0,
+                           const int lx1, const int ly1,
+                           const int lx2, const int ly2,
+                           const float opacity=1) {
+      typedef typename cimg::superset<tz,float>::type tzfloat;
+      if (!is_sameXY(zbuffer))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Instance and specified Z-buffer (%u,%u,%u,%u,%p) have different dimensions.",
+                                    cimg_instance,
+                                    zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+      if (texture._depth>1 || texture._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+      if (light._depth>1 || light._spectrum<_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_triangle() : Invalid specified light texture (%u,%u,%u,%u,%p).",
+                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+
+      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+      if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
+                                                       +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
+                                                     texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+      static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const int whd = _width*_height*_depth, twhd = texture._width*texture._height*texture._depth, offx = _spectrum*whd;
+      int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+        nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+      float
+        ntx0 = tx0/z0, nty0 = ty0/z0,
+        ntx1 = tx1/z1, nty1 = ty1/z1,
+        ntx2 = tx2/z2, nty2 = ty2/z2;
+      tzfloat nz0 = 1/(tzfloat)z0, nz1 = 1/(tzfloat)z1, nz2 = 1/(tzfloat)z2;
+      if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
+      if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
+      if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
+      if (ny0>=height() || ny2<0) return *this;
+      float
+        ptxl = (ntx1 - ntx0)/(ny1 - ny0),
+        ptxr = (ntx2 - ntx0)/(ny2 - ny0),
+        ptxn = (ntx2 - ntx1)/(ny2 - ny1),
+        ptyl = (nty1 - nty0)/(ny1 - ny0),
+        ptyr = (nty2 - nty0)/(ny2 - ny0),
+        ptyn = (nty2 - nty1)/(ny2 - ny1),
+        txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
+        tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
+        txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
+        tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
+      tzfloat
+        pzl = (nz1 - nz0)/(ny1 - ny0),
+        pzr = (nz2 - nz0)/(ny2 - ny0),
+        pzn = (nz2 - nz1)/(ny2 - ny1),
+        zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
+        zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
+      _cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
+                          nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
+        if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
+        int
+          xleft = xleft0, xright = xright0,
+          lxleft = lxleft0, lxright = lxright0,
+          lyleft = lyleft0, lyright = lyright0;
+        float txleft = txl, txright = txr, tyleft = tyl, tyright = tyr;
+        tzfloat zleft = zl, zright = zr;
+        if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
+        const int
+          dx = xright - xleft,
+          dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
+          dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
+          rlx = dx?(lxright - lxleft)/dx:0,
+          rly = dx?(lyright - lyleft)/dx:0,
+          slx = lxright>lxleft?1:-1,
+          sly = lyright>lyleft?1:-1,
+          ndlx = dlx - (dx?dx*(dlx/dx):0),
+          ndly = dly - (dx?dx*(dly/dx):0);
+        float pentetx = (txright - txleft)/dx, pentety = (tyright - tyleft)/dx;
+        const tzfloat pentez = (zright - zleft)/dx;
+        int errlx = dx>>1, errly = errlx;
+        if (xleft<0 && dx) {
+          zleft-=xleft*(zright - zleft)/dx;
+          lxleft-=xleft*(lxright - lxleft)/dx;
+          lyleft-=xleft*(lyright - lyleft)/dx;
+          txleft-=xleft*(txright - txleft)/dx;
+          tyleft-=xleft*(tyright - tyleft)/dx;
+        }
+        if (xleft<0) xleft = 0;
+        if (xright>=width()-1) xright = width()-1;
+        T* ptrd = data(xleft,y);
+        tz *ptrz = zbuffer.data(xleft,y);
+        if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                *ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+                ptrd+=whd; col+=twhd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+            lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+          } else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+            if (zleft>=(tzfloat)*ptrz) {
+              *ptrz = (tz)zleft;
+              const tzfloat invz = 1/zleft;
+              const tc *col = texture.data((int)(txleft*invz),(int)(tyleft*invz));
+              cimg_forC(*this,c) {
+                const tl l = light(lxleft,lyleft,c);
+                const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
+                *ptrd = (T)(nopacity*val + *ptrd*copacity);
+                ptrd+=whd; col+=twhd;
+              }
+              ptrd-=offx;
+            }
+            zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+            lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+            lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+          }
+        zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+      }
+      return *this;
+    }
+
+    //! Draw a 4d filled rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0,\c c0)-(\c x1,\c y1,\c z1,\c c1).
+    /**
+       \param x0 X-coordinate of the upper-left rectangle corner.
+       \param y0 Y-coordinate of the upper-left rectangle corner.
+       \param z0 Z-coordinate of the upper-left rectangle corner.
+       \param c0 C-coordinate of the upper-left rectangle corner.
+       \param x1 X-coordinate of the lower-right rectangle corner.
+       \param y1 Y-coordinate of the lower-right rectangle corner.
+       \param z1 Z-coordinate of the lower-right rectangle corner.
+       \param c1 C-coordinate of the lower-right rectangle corner.
+       \param val Scalar value used to fill the rectangle area.
+       \param opacity Drawing opacity (optional).
+       \note
+       - Clipping is supported.
+    **/
+    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0, const int c0,
+                            const int x1, const int y1, const int z1, const int c1,
+                            const T val, const float opacity=1) {
+      if (is_empty()) return *this;
+      const bool bx = (x0<x1), by = (y0<y1), bz = (z0<z1), bc = (c0<c1);
+      const int
+        nx0 = bx?x0:x1, nx1 = bx?x1:x0,
+        ny0 = by?y0:y1, ny1 = by?y1:y0,
+        nz0 = bz?z0:z1, nz1 = bz?z1:z0,
+        nc0 = bc?c0:c1, nc1 = bc?c1:c0;
+      const int
+        lX = (1 + nx1 - nx0) + (nx1>=width()?width() - 1 - nx1:0) + (nx0<0?nx0:0),
+        lY = (1 + ny1 - ny0) + (ny1>=height()?height() - 1 - ny1:0) + (ny0<0?ny0:0),
+        lZ = (1 + nz1 - nz0) + (nz1>=depth()?depth() - 1 - nz1:0) + (nz0<0?nz0:0),
+        lC = (1 + nc1 - nc0) + (nc1>=spectrum()?spectrum() - 1 - nc1:0) + (nc0<0?nc0:0);
+      const unsigned int offX = _width - lX, offY = _width*(_height - lY), offZ = _width*_height*(_depth - lZ);
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      T *ptrd = data(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nc0<0?0:nc0);
+      if (lX>0 && lY>0 && lZ>0 && lC>0)
+        for (int v = 0; v<lC; ++v) {
+          for (int z = 0; z<lZ; ++z) {
+            for (int y = 0; y<lY; ++y) {
+              if (opacity>=1) {
+                if (sizeof(T)!=1) { for (int x = 0; x<lX; ++x) *(ptrd++) = val; ptrd+=offX; }
+                else { std::memset(ptrd,(int)val,lX); ptrd+=_width; }
+              } else { for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ptrd; } ptrd+=offX; }
+            }
+            ptrd+=offY;
+          }
+          ptrd+=offZ;
+        }
+      return *this;
+    }
+
+    //! Draw a 3d filled colored rectangle in the 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 spectrum() consecutive values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity (optional).
+       \note
+       - Clipping is supported.
+    **/
+    template<typename tc>
+    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
+                            const int x1, const int y1, const int z1,
+                            const tc *const color, const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_rectangle : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,color[c],opacity);
+      return *this;
+    }
+
+    //! Draw a 3d outlined colored rectangle in the instance image.
+    template<typename tc>
+    CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
+                            const int x1, const int y1, const int z1,
+                            const tc *const color, const float opacity,
+                            const unsigned int pattern) {
+      return draw_line(x0,y0,z0,x1,y0,z0,color,opacity,pattern,true).
+        draw_line(x1,y0,z0,x1,y1,z0,color,opacity,pattern,false).
+        draw_line(x1,y1,z0,x0,y1,z0,color,opacity,pattern,false).
+        draw_line(x0,y1,z0,x0,y0,z0,color,opacity,pattern,false).
+        draw_line(x0,y0,z1,x1,y0,z1,color,opacity,pattern,true).
+        draw_line(x1,y0,z1,x1,y1,z1,color,opacity,pattern,false).
+        draw_line(x1,y1,z1,x0,y1,z1,color,opacity,pattern,false).
+        draw_line(x0,y1,z1,x0,y0,z1,color,opacity,pattern,false).
+        draw_line(x0,y0,z0,x0,y0,z1,color,opacity,pattern,true).
+        draw_line(x1,y0,z0,x1,y0,z1,color,opacity,pattern,true).
+        draw_line(x1,y1,z0,x1,y1,z1,color,opacity,pattern,true).
+        draw_line(x0,y1,z0,x0,y1,z1,color,opacity,pattern,true);
+    }
+
+    //! Draw a 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 spectrum() consecutive values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity (optional).
+       \note
+       - Clipping is supported.
+    **/
+    template<typename tc>
+    CImg<T>& draw_rectangle(const int x0, const int y0,
+                            const int x1, const int y1,
+                            const tc *const color, const float opacity=1) {
+      return draw_rectangle(x0,y0,0,x1,y1,_depth-1,color,opacity);
+    }
+
+    //! Draw a 2d outlined colored rectangle.
+    template<typename tc>
+    CImg<T>& draw_rectangle(const int x0, const int y0,
+                            const int x1, const int y1,
+                            const tc *const color, const float opacity,
+                            const unsigned int pattern) {
+      if (is_empty()) return *this;
+      if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true);
+      if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true);
+      const bool bx = (x0<x1), by = (y0<y1);
+      const int
+        nx0 = bx?x0:x1, nx1 = bx?x1:x0,
+        ny0 = by?y0:y1, ny1 = by?y1:y0;
+      if (ny1==ny0+1) return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
+                      draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false);
+      return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
+        draw_line(nx1,ny0+1,nx1,ny1-1,color,opacity,pattern,false).
+        draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false).
+        draw_line(nx0,ny1-1,nx0,ny0+1,color,opacity,pattern,false);
+    }
+
+    //! Draw a filled polygon in the instance image.
+    template<typename t, typename tc>
+    CImg<T>& draw_polygon(const CImg<t>& points,
+                          const tc *const color, const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_polygon() : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty() || !points || points._width<3) return *this;
+
+      // Normalize 2d input coordinates.
+      CImg<intT> npoints(points._width,2);
+      int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1);
+      unsigned int nb_points = 1;
+      for (unsigned int p = 1; p<points._width; ++p) {
+        const int nx = (int)points(p,0), ny = (int)points(p,1);
+        if (nx!=x || ny!=y) { npoints(nb_points,0) = nx; npoints(nb_points++,1) = ny; x = nx; y = ny; }
+      }
+
+      if (nb_points==3) return draw_triangle((int)npoints(0,0),(int)npoints(0,1),
+                                             (int)npoints(1,0),(int)npoints(1,1),
+                                             (int)npoints(2,0),(int)npoints(2,1),color,opacity);
+      // Draw polygon segments.
+      _draw_scanline(color,opacity);
+      int
+        xmax = 0, xmin = (int)npoints.get_shared_points(0,nb_points-1,0).min_max(xmax),
+        ymax = 0, ymin = (int)npoints.get_shared_points(0,nb_points-1,1).min_max(ymax);
+      if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this;
+      if (ymin==ymax) return _draw_scanline(xmin,xmax,ymin,color,opacity);
+      const unsigned int
+        nymin = ymin<0?0:(unsigned int)ymin,
+        nymax = ymax>=height()?_height-1:(unsigned int)ymax,
+        dy = 1 + nymax - nymin;
+      CImg<intT> X(1+2*nb_points,dy,1,1,0), tmp;
+      int cx = (int)npoints(0,0), cy = (int)npoints(0,1);
+      unsigned int cp = 0;
+      for (unsigned int p = 0; p<nb_points; ++p) {
+        const unsigned int np = (p!=nb_points-1)?p+1:0, ap = (np!=nb_points-1)?np+1:0;
+        const int
+          nx = (int)npoints(np,0), ny = (int)npoints(np,1), ay = (int)npoints(ap,1),
+          y0 = cy - nymin, y1 = ny - nymin;
+        if (y0!=y1) {
+          const int countermin = ((ny<ay && cy<ny) || (ny>ay && cy>ny))?1:0;
+          for (int x = cx, y = y0, _sx = 1, _sy = 1,
+                 _dx = nx>cx?nx-cx:((_sx=-1),cx-nx),
+                 _dy = y1>y0?y1-y0:((_sy=-1),y0-y1),
+                 _counter = ((_dx-=_dy?_dy*(_dx/_dy):0),_dy),
+                 _err = _dx>>1,
+                 _rx = _dy?(nx-cx)/_dy:0;
+               _counter>=countermin;
+               --_counter, y+=_sy, x+=_rx + ((_err-=_dx)<0?_err+=_dy,_sx:0))
+            if (y>=0 && y<(int)dy) X(++X(0,y),y) = x;
+          cp = np; cx = nx; cy = ny;
+        } else {
+          const int pp = (cp?cp-1:nb_points-1), py = (int)npoints(pp,1);
+          if (y0>=0 && y0<(int)dy && (!p || (cy>py && ay>cy) || (cy<py && ay<cy))) X(++X(0,y0),y0) = nx;
+          if (cy!=ay) { cp = np; cx = nx; cy = ny; }
+        }
+      }
+
+      // Draw polygon scanlines.
+      for (int y = 0; y<(int)dy; ++y) {
+        tmp.assign(X.data(1,y),X(0,y),1,1,1,true).sort();
+        for (int i = 1; i<=X(0,y); ) {
+          const int xb = X(i++,y), xe = X(i++,y);
+          _draw_scanline(xb,xe,nymin+y,color,opacity);
+        }
+      }
+
+      return *this;
+    }
+
+    //! Draw a outlined polygon in the instance image.
+    template<typename t, typename tc>
+    CImg<T>& draw_polygon(const CImg<t>& points,
+                          const tc *const color, const float opacity, const unsigned int pattern) {
+      if (is_empty() || !points || points._width<3) return *this;
+      bool ninit_hatch = true;
+      switch (points._height) {
+      case 0 : case 1 :
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_polygon() : Invalid specified point set.",
+                                    cimg_instance);
+      case 2 : { // 2d version.
+        CImg<intT> npoints(points._width,2);
+        int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1);
+        unsigned int nb_points = 1;
+        for (unsigned int p = 1; p<points._width; ++p) {
+          const int nx = (int)points(p,0), ny = (int)points(p,1);
+          if (nx!=x || ny!=y) { npoints(nb_points,0) = nx; npoints(nb_points++,1) = ny; x = nx; y = ny; }
+        }
+        const int x0 = (int)npoints(0,0), y0 = (int)npoints(0,1);
+        int ox = x0, oy = y0;
+        for (unsigned int i = 1; i<nb_points; ++i) {
+          const int x = (int)npoints(i,0), y = (int)npoints(i,1);
+          draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y;
+        }
+        draw_line(ox,oy,x0,y0,color,opacity,pattern,false);
+      } break;
+      default : { // 3d version.
+        CImg<intT> npoints(points._width,3);
+        int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1), z = npoints(0,2) = (int)points(0,2);
+        unsigned int nb_points = 1;
+        for (unsigned int p = 1; p<points._width; ++p) {
+          const int nx = (int)points(p,0), ny = (int)points(p,1), nz = (int)points(p,2);
+          if (nx!=x || ny!=y || nz!=z) { npoints(nb_points,0) = nx; npoints(nb_points,1) = ny; npoints(nb_points++,2) = nz; x = nx; y = ny; z = nz; }
+        }
+        const int x0 = (int)npoints(0,0), y0 = (int)npoints(0,1), z0 = (int)npoints(0,2);
+        int ox = x0, oy = y0, oz = z0;
+        for (unsigned int i = 1; i<nb_points; ++i) {
+          const int x = (int)npoints(i,0), y = (int)npoints(i,1), z = (int)npoints(i,2);
+          draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
+          ninit_hatch = false;
+          ox = x; oy = y; oz = z;
+        }
+        draw_line(ox,oy,oz,x0,y0,z0,color,opacity,pattern,false);
+      }
+      }
+      return *this;
+    }
+
+    //! Draw a filled circle.
+    /**
+       \param x0 X-coordinate of the circle center.
+       \param y0 Y-coordinate of the circle center.
+       \param radius  Circle radius.
+       \param color Array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity.
+       \note
+       - Circle version of the Bresenham's algorithm is used.
+    **/
+    template<typename tc>
+    CImg<T>& draw_circle(const int x0, const int y0, int radius,
+                         const tc *const color, const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_circle : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      _draw_scanline(color,opacity);
+      if (radius<0 || x0-radius>=width() || y0+radius<0 || y0-radius>=height()) return *this;
+      if (y0>=0 && y0<height()) _draw_scanline(x0-radius,x0+radius,y0,color,opacity);
+      for (int f = 1-radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x<y; ) {
+        if (f>=0) {
+          const int x1 = x0-x, x2 = x0+x, y1 = y0-y, y2 = y0+y;
+          if (y1>=0 && y1<height()) _draw_scanline(x1,x2,y1,color,opacity);
+          if (y2>=0 && y2<height()) _draw_scanline(x1,x2,y2,color,opacity);
+          f+=(ddFy+=2); --y;
+        }
+        const bool no_diag = y!=(x++);
+        ++(f+=(ddFx+=2));
+        const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x;
+        if (no_diag) {
+          if (y1>=0 && y1<height()) _draw_scanline(x1,x2,y1,color,opacity);
+          if (y2>=0 && y2<height()) _draw_scanline(x1,x2,y2,color,opacity);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw an outlined circle.
+    /**
+       \param x0 X-coordinate of the circle center.
+       \param y0 Y-coordinate of the circle center.
+       \param radius Circle radius.
+       \param color Array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity.
+    **/
+    template<typename tc>
+    CImg<T>& draw_circle(const int x0, const int y0, int radius,
+                         const tc *const color, const float opacity,
+                         const unsigned int) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_circle : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      if (radius<0 || x0-radius>=width() || y0+radius<0 || y0-radius>=height()) return *this;
+      if (!radius) return draw_point(x0,y0,color,opacity);
+      draw_point(x0-radius,y0,color,opacity).draw_point(x0+radius,y0,color,opacity).
+        draw_point(x0,y0-radius,color,opacity).draw_point(x0,y0+radius,color,opacity);
+      if (radius==1) return *this;
+      for (int f = 1-radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x<y; ) {
+        if (f>=0) { f+=(ddFy+=2); --y; }
+        ++x; ++(f+=(ddFx+=2));
+        if (x!=y+1) {
+          const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x, x3 = x0-x, x4 = x0+x, y3 = y0-y, y4 = y0+y;
+          draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity).
+            draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity);
+          if (x!=y)
+            draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity).
+              draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity);
+        }
+      }
+      return *this;
+    }
+
+    // Draw a 2d ellipse (inner routine).
+    template<typename tc>
+    CImg<T>& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle,
+                           const tc *const color, const float opacity,
+                           const unsigned int pattern) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_ellipse : Specified color is (null).",
+                                    cimg_instance);
+      if (is_empty()) return *this;
+      if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity);
+      _draw_scanline(color,opacity);
+      const float
+        nr1 = cimg::abs(r1), nr2 = cimg::abs(r2),
+        nangle = (float)(angle*cimg::PI/180),
+        u = (float)std::cos(nangle),
+        v = (float)std::sin(nangle),
+        rmax = cimg::max(nr1,nr2),
+        l1 = (float)std::pow(rmax/(nr1>0?nr1:1e-6),2),
+        l2 = (float)std::pow(rmax/(nr2>0?nr2:1e-6),2),
+        a = l1*u*u + l2*v*v,
+        b = u*v*(l1-l2),
+        c = l1*v*v + l2*u*u;
+      const int
+        yb = (int)std::sqrt(a*rmax*rmax/(a*c - b*b)),
+        tymin = y0 - yb - 1,
+        tymax = y0 + yb + 1,
+        ymin = tymin<0?0:tymin,
+        ymax = tymax>=height()?_height-1:tymax;
+      int oxmin = 0, oxmax = 0;
+      bool first_line = true;
+      for (int y = ymin; y<=ymax; ++y) {
+        const float
+          Y = y - y0 + (y<y0?0.5f:-0.5f),
+          delta = b*b*Y*Y - a*(c*Y*Y - rmax*rmax),
+          sdelta = delta>0?(float)std::sqrt(delta)/a:0.0f,
+          bY = b*Y/a,
+          fxmin = x0 - 0.5f - bY - sdelta,
+          fxmax = x0 + 0.5f - bY + sdelta;
+        const int xmin = (int)fxmin, xmax = (int)fxmax;
+        if (!pattern) _draw_scanline(xmin,xmax,y,color,opacity);
+        else {
+          if (first_line) {
+            if (y0-yb>=0) _draw_scanline(xmin,xmax,y,color,opacity);
+            else draw_point(xmin,y,color,opacity).draw_point(xmax,y,color,opacity);
+            first_line = false;
+          } else {
+            if (xmin<oxmin) _draw_scanline(xmin,oxmin-1,y,color,opacity);
+            else _draw_scanline(oxmin+(oxmin==xmin?0:1),xmin,y,color,opacity);
+            if (xmax<oxmax) _draw_scanline(xmax,oxmax-1,y,color,opacity);
+            else _draw_scanline(oxmax+(oxmax==xmax?0:1),xmax,y,color,opacity);
+            if (y==tymax) _draw_scanline(xmin+1,xmax-1,y,color,opacity);
+          }
+        }
+        oxmin = xmin; oxmax = xmax;
+      }
+      return *this;
+    }
+
+    //! Draw a filled ellipse.
+    /**
+       \param x0 = X-coordinate of the ellipse center.
+       \param y0 = Y-coordinate of the ellipse center.
+       \param r1 = First radius of the ellipse.
+       \param r2 = Second radius of the ellipse.
+       \param angle = Angle of the first radius.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename tc>
+    CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle,
+                          const tc *const color, const float opacity=1) {
+      return _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,0U);
+    }
+
+    //! Draw a filled ellipse.
+    /**
+       \param x0 = X-coordinate of the ellipse center.
+       \param y0 = Y-coordinate of the ellipse center.
+       \param tensor = Diffusion tensor describing the ellipse.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename t, typename tc>
+    CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
+                          const tc *const color, const float opacity=1) {
+      CImgList<t> eig = tensor.get_symmetric_eigen();
+      const CImg<t> &val = eig[0], &vec = eig[1];
+      return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)),
+                          std::atan2(vec(0,1),vec(0,0))*180/cimg::PI,
+                          color,opacity);
+    }
+
+    //! Draw an outlined ellipse.
+    /**
+       \param x0 = X-coordinate of the ellipse center.
+       \param y0 = Y-coordinate of the ellipse center.
+       \param r1 = First radius of the ellipse.
+       \param r2 = Second radius of the ellipse.
+       \param ru = X-coordinate of the orientation vector related to the first radius.
+       \param rv = Y-coordinate of the orientation vector related to the first radius.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename tc>
+    CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle,
+                          const tc *const color, const float opacity, const unsigned int pattern) {
+      if (pattern) _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,pattern);
+      return *this;
+    }
+
+    //! Draw an outlined ellipse.
+    /**
+       \param x0 = X-coordinate of the ellipse center.
+       \param y0 = Y-coordinate of the ellipse center.
+       \param tensor = Diffusion tensor describing the ellipse.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename t, typename tc>
+    CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
+                          const tc *const color, const float opacity,
+                          const unsigned int pattern) {
+      CImgList<t> eig = tensor.get_symmetric_eigen();
+      const CImg<t> &val = eig[0], &vec = eig[1];
+      return draw_ellipse(x0,y0,std::sqrt(val(0)),std::sqrt(val(1)),
+                          std::atan2(vec(0,1),vec(0,0))*180/cimg::PI,
+                          color,opacity,pattern);
+    }
+
+    //! Draw an image.
+    /**
+       \param sprite Sprite image.
+       \param x0 X-coordinate of the sprite position.
+       \param y0 Y-coordinate of the sprite position.
+       \param z0 Z-coordinate of the sprite position.
+       \param c0 C-coordinate of the sprite position.
+       \param opacity Drawing opacity (optional).
+       \note
+       - Clipping is supported.
+    **/
+    template<typename t>
+    CImg<T>& draw_image(const int x0, const int y0, const int z0, const int c0,
+                        const CImg<t>& sprite, const float opacity=1) {
+      if (!sprite)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_image() : Empty specified sprite.",
+                                    cimg_instance);
+      if (is_empty()) return *this;
+      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity);
+      if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1) return assign(sprite,false);
+      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0);
+      const int
+        lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0),
+        lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0),
+        lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0),
+        lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0);
+      const t
+        *ptrs = sprite._data -
+        (bx?x0:0) -
+        (by?y0*sprite.width():0) -
+        (bz?z0*sprite.width()*sprite.height():0) -
+        (bc?c0*sprite.width()*sprite.height()*sprite.depth():0);
+      const unsigned int
+        offX = _width - lX, soffX = sprite._width - lX,
+        offY = _width*(_height - lY), soffY = sprite._width*(sprite._height - lY),
+        offZ = _width*_height*(_depth - lZ), soffZ = sprite._width*sprite._height*(sprite._depth - lZ);
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      if (lX>0 && lY>0 && lZ>0 && lC>0) {
+        T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0);
+        for (int v = 0; v<lC; ++v) {
+          for (int z = 0; z<lZ; ++z) {
+            for (int y = 0; y<lY; ++y) {
+              if (opacity>=1) for (int x = 0; x<lX; ++x) *(ptrd++) = (T)*(ptrs++);
+              else for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
+              ptrd+=offX; ptrs+=soffX;
+            }
+            ptrd+=offY; ptrs+=soffY;
+          }
+          ptrd+=offZ; ptrs+=soffZ;
+        }
+      }
+      return *this;
+    }
+
+    // Optimized version (internal).
+    CImg<T>& draw_image(const int x0, const int y0, const int z0, const int c0,
+                        const CImg<T>& sprite, const float opacity=1) {
+      if (!sprite)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_image() : Empty specified sprite.",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity);
+      if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1) return assign(sprite,false);
+      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0);
+      const int
+        lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0),
+        lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0),
+        lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0),
+        lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0);
+      const T
+        *ptrs = sprite._data -
+        (bx?x0:0) -
+        (by?y0*sprite.width():0) -
+        (bz?z0*sprite.width()*sprite.height():0) -
+        (bc?c0*sprite.width()*sprite.height()*sprite.depth():0);
+      const unsigned int
+        offX = _width - lX, soffX = sprite._width - lX,
+        offY = _width*(_height - lY), soffY = sprite._width*(sprite._height - lY),
+        offZ = _width*_height*(_depth - lZ), soffZ = sprite._width*sprite._height*(sprite._depth - lZ),
+        slX = lX*sizeof(T);
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      if (lX>0 && lY>0 && lZ>0 && lC>0) {
+        T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0);
+        for (int v = 0; v<lC; ++v) {
+          for (int z = 0; z<lZ; ++z) {
+            if (opacity>=1) for (int y = 0; y<lY; ++y) { std::memcpy(ptrd,ptrs,slX); ptrd+=_width; ptrs+=sprite._width; }
+            else for (int y = 0; y<lY; ++y) {
+              for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
+              ptrd+=offX; ptrs+=soffX;
+            }
+            ptrd+=offY; ptrs+=soffY;
+          }
+          ptrd+=offZ; ptrs+=soffZ;
+        }
+      }
+      return *this;
+    }
+
+    //! Draw an image.
+    template<typename t>
+    CImg<T>& draw_image(const int x0, const int y0, const int z0,
+                        const CImg<t>& sprite, const float opacity=1) {
+      return draw_image(x0,y0,z0,0,sprite,opacity);
+    }
+
+    //! Draw an image.
+    template<typename t>
+    CImg<T>& draw_image(const int x0, const int y0,
+                        const CImg<t>& sprite, const float opacity=1) {
+      return draw_image(x0,y0,0,sprite,opacity);
+    }
+
+    //! Draw an image.
+    template<typename t>
+    CImg<T>& draw_image(const int x0,
+                        const CImg<t>& sprite, const float opacity=1) {
+      return draw_image(x0,0,sprite,opacity);
+    }
+
+    //! Draw an image.
+    template<typename t>
+    CImg<T>& draw_image(const CImg<t>& sprite, const float opacity=1) {
+      return draw_image(0,sprite,opacity);
+    }
+
+    //! Draw a sprite image in the 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 c0 C-coordinate of the sprite position in the instance image.
+       \param mask_valmax Maximum pixel value of the mask image \c mask (optional).
+       \param opacity Drawing opacity.
+       \note
+       - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite.
+       - Clipping is supported.
+       - Dimensions along x,y and z of \p sprite and \p mask must be the same.
+    **/
+    template<typename ti, typename tm>
+    CImg<T>& draw_image(const int x0, const int y0, const int z0, const int c0,
+                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+                        const float mask_valmax=1) {
+      if (!sprite)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_image() : Empty specified sprite.",
+                                    cimg_instance);
+      if (!mask)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_image() : Empty specified mask.",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_valmax);
+      if (is_overlapped(mask))   return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_valmax);
+      if (mask._width!=sprite._width || mask._height!=sprite._height || mask._depth!=sprite._depth)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_image() : Sprite (%u,%u,%u,%u,%p) and mask (%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    sprite._width,sprite._height,sprite._depth,sprite._spectrum,sprite._data,
+                                    mask._width,mask._height,mask._depth,mask._spectrum,mask._data);
+
+      const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0);
+      const int
+        lX = sprite.width() - (x0 + sprite.width()>width()?x0 + sprite.width() - width():0) + (bx?x0:0),
+        lY = sprite.height() - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0) + (by?y0:0),
+        lZ = sprite.depth() - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0) + (bz?z0:0),
+        lC = sprite.spectrum() - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0) + (bc?c0:0);
+      const int
+        coff = -(bx?x0:0)-(by?y0*mask.width():0)-(bz?z0*mask.width()*mask.height():0)-(bc?c0*mask.width()*mask.height()*mask.depth():0),
+        ssize = mask.width()*mask.height()*mask.depth();
+      const ti *ptrs = sprite._data + coff;
+      const tm *ptrm = mask._data   + coff;
+      const unsigned int
+        offX = _width - lX, soffX = sprite._width - lX,
+        offY = _width*(_height - lY), soffY = sprite._width*(sprite._height - lY),
+        offZ = _width*_height*(_depth - lZ), soffZ = sprite._width*sprite._height*(sprite._depth - lZ);
+      if (lX>0 && lY>0 && lZ>0 && lC>0) {
+        T *ptrd = data(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,c0<0?0:c0);
+        for (int c = 0; c<lC; ++c) {
+          ptrm = mask._data + (ptrm - mask._data)%ssize;
+          for (int z = 0; z<lZ; ++z) {
+            for (int y = 0; y<lY; ++y) {
+              for (int x = 0; x<lX; ++x) {
+                const float mopacity = (float)(*(ptrm++)*opacity),
+                  nopacity = cimg::abs(mopacity), copacity = mask_valmax - cimg::max(mopacity,0);
+                *ptrd = (T)((nopacity*(*(ptrs++)) + *ptrd*copacity)/mask_valmax);
+                ++ptrd;
+              }
+              ptrd+=offX; ptrs+=soffX; ptrm+=soffX;
+            }
+            ptrd+=offY; ptrs+=soffY; ptrm+=soffY;
+          }
+          ptrd+=offZ; ptrs+=soffZ; ptrm+=soffZ;
+        }
+      }
+      return *this;
+    }
+
+    //! Draw an image.
+    template<typename ti, typename tm>
+    CImg<T>& draw_image(const int x0, const int y0, const int z0,
+                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+                        const float mask_valmax=1) {
+      return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_valmax);
+    }
+
+    //! Draw an image.
+    template<typename ti, typename tm>
+    CImg<T>& draw_image(const int x0, const int y0,
+                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+                        const float mask_valmax=1) {
+      return draw_image(x0,y0,0,sprite,mask,opacity,mask_valmax);
+    }
+
+    //! Draw an image.
+    template<typename ti, typename tm>
+    CImg<T>& draw_image(const int x0,
+                        const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+                        const float mask_valmax=1) {
+      return draw_image(x0,0,sprite,mask,opacity,mask_valmax);
+    }
+
+    //! Draw an image.
+    template<typename ti, typename tm>
+    CImg<T>& draw_image(const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
+                        const float mask_valmax=1) {
+      return draw_image(0,sprite,mask,opacity,mask_valmax);
+    }
+
+    //! Draw a text.
+    /**
+       \param x0 X-coordinate of the text in the instance image.
+       \param y0 Y-coordinate of the text in the instance image.
+       \param foreground_color Array of spectrum() values of type \c T, defining the foreground color (0 means 'transparent').
+       \param background_color Array of spectrum() values of type \c T, defining the background color (0 means 'transparent').
+       \param font Font used for drawing text.
+       \param opacity Drawing opacity.
+       \param format 'printf'-style format string, followed by arguments.
+       \note Clipping is supported.
+    **/
+    template<typename tc1, typename tc2, typename t>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const tc1 *const foreground_color, const tc2 *const background_color,
+                       const float opacity, const CImgList<t>& font, ...) {
+      if (!font) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font);
+      cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
+    }
+
+    template<typename tc, typename t>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const tc *const foreground_color, const int,
+                       const float opacity, const CImgList<t>& font, ...) {
+      if (!font) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font);
+      cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return _draw_text(x0,y0,tmp,foreground_color,(tc*)0,opacity,font);
+    }
+
+    template<typename tc, typename t>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const int, const tc *const background_color,
+                       const float opacity, const CImgList<t>& font, ...) {
+      if (!font) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font);
+      cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return _draw_text(x0,y0,tmp,(tc*)0,background_color,opacity,font);
+    }
+
+    //! Draw a text.
+    /**
+       \param x0 X-coordinate of the text in the instance image.
+       \param y0 Y-coordinate of the text in the instance image.
+       \param foreground_color Array of spectrum() values of type \c T, defining the foreground color (0 means 'transparent').
+       \param background_color Array of spectrum() values of type \c T, defining the background color (0 means 'transparent').
+       \param font_size Size of the font (exact match for 13,24,32,57).
+       \param opacity Drawing opacity.
+       \param format 'printf'-style format string, followed by arguments.
+       \note Clipping is supported.
+    **/
+    template<typename tc1, typename tc2>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const tc1 *const foreground_color, const tc2 *const background_color,
+                       const float opacity=1, const unsigned int font_height=13, ...) {
+      if (!font_height) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font_height); cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      static CImgList<floatT> font;
+      const unsigned int
+        ref_height = font_height<=13?13:font_height<=28?24:font_height<=32?32:57,
+        padding_x = font_height<=18?1:font_height<=32?2:3;
+      if (!font || font[0]._height!=font_height) {
+        font = CImgList<floatT>::font(ref_height,true);
+        font[0].assign(1,font_height);
+        if (ref_height==font_height) cimglist_for(font,l) font[l].resize(font[l]._width + padding_x,-100,-100,-100,0);
+      }
+      if (font[0]._spectrum<_spectrum) cimglist_for_in(font,0,255,l) font[l].resize(-100,-100,-100,_spectrum,1);
+      if (ref_height!=font_height) for (const char *ptrs = tmp; *ptrs; ++ptrs) {
+          const unsigned int _c = (unsigned int)(unsigned char)*ptrs;
+          if (_c<font._width) {
+            CImg<floatT> &c = font[_c];
+            if (c._height!=font_height) {
+              c.resize(cimg::max(1U,c._width*font_height/c._height),font_height,-100,-100,c._height>font_height?2:5);
+              c.resize(c._width + padding_x,-100,-100,-100,0);
+            }
+          }
+          if (_c+256U<font._width) {
+            CImg<floatT> &c = font[_c+256];
+            if (c._height!=font_height) {
+              c.resize(cimg::max(1U,c._width*font_height/c._height),font_height,-100,-100,c._height>font_height?2:5);
+              c.resize(c._width + padding_x,-100,-100,-100,0);
+            }
+          }
+        }
+      return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
+    }
+
+    template<typename tc>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const tc *const foreground_color, const int background_color=0,
+                       const float opacity=1, const unsigned int font_height=13, ...) {
+      if (!font_height) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font_height); cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return draw_text(x0,y0,tmp,foreground_color,(const tc*)background_color,opacity,font_height);
+    }
+
+    template<typename tc>
+    CImg<T>& draw_text(const int x0, const int y0,
+                       const char *const text,
+                       const int, const tc *const background_color,
+                       const float opacity=1, const unsigned int font_height=13, ...) {
+      if (!font_height) return *this;
+      char tmp[2048] = { 0 }; std::va_list ap; va_start(ap,font_height); cimg_vsnprintf(tmp,sizeof(tmp),text,ap); va_end(ap);
+      return draw_text(x0,y0,tmp,(tc*)0,background_color,opacity,font_height);
+    }
+
+    // Draw a text (internal routine).
+    template<typename tc1, typename tc2, typename t>
+    CImg<T>& _draw_text(const int x0, const int y0,
+                        const char *const text,
+                        const tc1 *const foreground_color, const tc2 *const background_color,
+                        const float opacity, const CImgList<t>& font) {
+      if (!text) return *this;
+      if (!font)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_text() : Empty specified font.",
+                                    cimg_instance);
+
+      const unsigned int text_length = std::strlen(text);
+      if (is_empty()) {
+        // If needed, pre-compute necessary size of the image
+        int x = 0, y = 0, w = 0;
+        unsigned char c = 0;
+        for (unsigned int i = 0; i<text_length; ++i) {
+          c = text[i];
+          switch (c) {
+          case '\n' : y+=font[' ']._height; if (x>w) w = x; x = 0; break;
+          case '\t' : x+=4*font[' ']._width; break;
+          default : if (c<font._width) x+=font[c]._width;
+          }
+        }
+        if (x!=0 || c=='\n') {
+          if (x>w) w=x;
+          y+=font[' ']._height;
+        }
+        assign(x0+w,y0+y,1,font[0]._spectrum,0);
+        if (background_color) cimg_forC(*this,c) get_shared_channel(c).fill((T)background_color[c]);
+      }
+
+      int x = x0, y = y0;
+      CImg<t> letter;
+      for (unsigned int i = 0; i<text_length; ++i) {
+        const unsigned char c = text[i];
+        switch (c) {
+        case '\n' : y+=font[' ']._height; x = x0; break;
+        case '\t' : x+=4*font[' ']._width; break;
+        default : if (c<font._width) {
+          letter = font[c];
+          const unsigned int cmin = cimg::min(_spectrum,letter._spectrum);
+          const CImg<t>& mask = (c+256)<(int)font._width?font[c+256]:font[c];
+          if (foreground_color)
+            for (unsigned int p = 0; p<letter._width*letter._height; ++p)
+              if (mask(p)) for (unsigned int c = 0; c<cmin; ++c) letter(p,0,0,c) = (t)(letter(p,0,0,c)*foreground_color[c]);
+          if (background_color)
+            for (unsigned int p = 0; p<letter._width*letter._height; ++p)
+              if (!mask(p)) for (unsigned int c = 0; c<cmin; ++c) letter(p,0,0,c) = (t)background_color[c];
+          if (!background_color && font._width>=512) draw_image(x,y,letter,mask,opacity,(T)1);
+          else draw_image(x,y,letter,opacity);
+          x+=letter._width;
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a vector field in the instance image, using a colormap.
+    /**
+       \param flow Image of 2d vectors used as input data.
+       \param color Image of spectrum()-D vectors corresponding to the color of each arrow.
+       \param sampling Length (in pixels) between each arrow.
+       \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
+       \param opacity Opacity of the drawing.
+       \param pattern Used pattern to draw lines.
+       \note Clipping is supported.
+    **/
+    template<typename t1, typename t2>
+    CImg<T>& draw_quiver(const CImg<t1>& flow,
+                         const t2 *const color, const float opacity=1,
+                         const unsigned int sampling=25, const float factor=-20,
+                         const bool arrows=true, const unsigned int pattern=~0U) {
+      return draw_quiver(flow,CImg<t2>(color,_spectrum,1,1,1,true),opacity,sampling,factor,arrows,pattern);
+    }
+
+    //! Draw a vector field in the instance image, using a colormap.
+    /**
+       \param flow Image of 2d vectors used as input data.
+       \param color Image of spectrum()-D vectors corresponding to the color of each arrow.
+       \param sampling Length (in pixels) between each arrow.
+       \param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
+       \param opacity Opacity of the drawing.
+       \param pattern Used pattern to draw lines.
+       \note Clipping is supported.
+    **/
+    template<typename t1, typename t2>
+    CImg<T>& draw_quiver(const CImg<t1>& flow,
+                         const CImg<t2>& color, const float opacity=1,
+                         const unsigned int sampling=25, const float factor=-20,
+                         const bool arrows=true, const unsigned int pattern=~0U) {
+
+      if (is_empty()) return *this;
+
+      if (!flow || flow._spectrum!=2)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_quiver() : Invalid dimensions of specified flow (%u,%u,%u,%u,%p).",
+                                    cimg_instance,
+                                    flow._width,flow._height,flow._depth,flow._spectrum,flow._data);
+      if (sampling<=0)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_quiver() : Invalid sampling value %g "
+                                    "(should be >0)",
+                                    cimg_instance,
+                                    sampling);
+
+      const bool colorfield = (color._width==flow._width && color._height==flow._height && color._depth==1 && color._spectrum==_spectrum);
+      if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,arrows,pattern);
+
+      float vmax,fact;
+      if (factor<=0) {
+        float m, M = (float)flow.get_norm(2).max_min(m);
+        vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
+        fact = -factor;
+      } else { fact = factor; vmax = 1; }
+
+      for (unsigned int y = sampling/2; y<_height; y+=sampling)
+        for (unsigned int x = sampling/2; x<_width; x+=sampling) {
+          const unsigned int X = x*flow._width/_width, Y = y*flow._height/_height;
+          float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax;
+          if (arrows) {
+            const int xx = x+(int)u, yy = y+(int)v;
+            if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y)._data,opacity,45,sampling/5.0f,pattern);
+            else draw_arrow(x,y,xx,yy,color._data,opacity,45,sampling/5.0f,pattern);
+          } else {
+            if (colorfield) draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color.get_vector_at(X,Y)._data,opacity,pattern);
+            else draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color._data,opacity,pattern);
+          }
+        }
+
+      return *this;
+    }
+
+    //! Draw a labeled horizontal axis on the 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 spectrum() values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity.
+       \param pattern Drawing pattern.
+       \param opacity_out Drawing opacity of 'outside' axes.
+       \note if \c precision==0, precision of the labels is automatically computed.
+    **/
+    template<typename t, typename tc>
+    CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int pattern=~0U) {
+      if (!is_empty()) {
+        int siz = (int)xvalues.size()-1;
+        if (siz<=0) draw_line(0,y,_width-1,y,color,opacity,pattern);
+        else {
+          if (xvalues[0]<xvalues[siz]) draw_arrow(0,y,_width-1,y,color,opacity,30,5,pattern);
+          else draw_arrow(_width-1,y,0,y,color,opacity,30,5,pattern);
+          const int yt = (y+14)<height()?(y+3):(y-14);
+          char txt[32] = { 0 };
+          cimg_foroff(xvalues,x) {
+            cimg_snprintf(txt,sizeof(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(xt<0?0:xt,yt,txt,color,(tc*)0,opacity,13);
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a labeled vertical axis on the instance image.
+    template<typename t, typename tc>
+    CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int pattern=~0U) {
+      if (!is_empty()) {
+        int siz = (int)yvalues.size()-1;
+        if (siz<=0) draw_line(x,0,x,_height-1,color,opacity,pattern);
+        else {
+          if (yvalues[0]<yvalues[siz]) draw_arrow(x,0,x,_height-1,color,opacity,30,5,pattern);
+          else draw_arrow(x,_height-1,x,0,color,opacity,30,5,pattern);
+          char txt[32] = { 0 };
+          cimg_foroff(yvalues,y) {
+            cimg_snprintf(txt,sizeof(txt),"%g",(double)yvalues(y));
+            const int
+              yi = (int)(y*(_height-1)/siz),
+              tmp = yi - 5,
+              nyi = tmp<0?0:(tmp>=height()-11?height()-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(xt,nyi,txt,color,(tc*)0,opacity,13);
+            else draw_text(x+3,nyi,txt,color,(tc*)0,opacity,13);
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a labeled horizontal+vertical axis on the instance image.
+    template<typename tx, typename ty, typename tc>
+      CImg<T>& draw_axes(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
+                         const tc *const color, const float opacity=1,
+                         const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+      if (!is_empty()) {
+        const CImg<tx> nxvalues(xvalues._data,xvalues.size(),1,1,1,true);
+        const int sizx = (int)xvalues.size()-1, wm1 = width()-1;
+        if (sizx>0) {
+          float ox = (float)nxvalues[0];
+          for (unsigned int x = 1; x<_width; ++x) {
+            const float nx = (float)nxvalues._linear_atX((float)x*sizx/wm1);
+            if (nx*ox<=0) { draw_axis(nx==0?x:x-1,yvalues,color,opacity,patterny); break; }
+            ox = nx;
+          }
+        }
+        const CImg<ty> nyvalues(yvalues._data,yvalues.size(),1,1,1,true);
+        const int sizy = (int)yvalues.size()-1, hm1 = height()-1;
+        if (sizy>0) {
+          float oy = (float)nyvalues[0];
+          for (unsigned int y = 1; y<_height; ++y) {
+            const float ny = (float)nyvalues._linear_atX((float)y*sizy/hm1);
+            if (ny*oy<=0) { draw_axis(xvalues,ny==0?y:y-1,color,opacity,patternx); break; }
+            oy = ny;
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a labeled horizontal+vertical axis on the instance image.
+    template<typename tc>
+    CImg<T>& draw_axes(const float x0, const float x1, const float y0, const float y1,
+                       const tc *const color, const float opacity=1,
+                       const int subdivisionx=-60, const int subdivisiony=-60,
+                       const float precisionx=0, const float precisiony=0,
+                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+      if (!is_empty()) {
+        const float
+          dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0),
+          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;
+        if (x0!=x1 && y0!=y1)
+          draw_axes(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),
+                    CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py),
+                    color,opacity,patternx,patterny);
+        else if (x0==x1 && y0!=y1)
+          draw_axis((int)x0,CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py),
+                    color,opacity,patterny);
+        else if (x0!=x1 && y0==y1)
+          draw_axis(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),(int)y0,
+                    color,opacity,patternx);
+      }
+      return *this;
+    }
+
+    //! Draw grid.
+    template<typename tx, typename ty, typename tc>
+    CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+      if (!is_empty()) {
+        if (xvalues) cimg_foroff(xvalues,x) {
+          const int xi = (int)xvalues[x];
+          if (xi>=0 && xi<width()) draw_line(xi,0,xi,_height-1,color,opacity,patternx);
+        }
+        if (yvalues) cimg_foroff(yvalues,y) {
+          const int yi = (int)yvalues[y];
+          if (yi>=0 && yi<height()) draw_line(0,yi,_width-1,yi,color,opacity,patterny);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw grid.
+    template<typename tc>
+    CImg<T>& draw_grid(const float deltax,  const float deltay,
+                       const float offsetx, const float offsety,
+                       const bool invertx, const bool inverty,
+                       const tc *const color, const float opacity=1,
+                       const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
+      CImg<uintT> seqx, seqy;
+      if (deltax!=0) {
+        const float dx = deltax>0?deltax:_width*-deltax/100;
+        const unsigned int nx = (unsigned int)(_width/dx);
+        seqx = CImg<uintT>::sequence(1+nx,0,(unsigned int)(dx*nx));
+        if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x)+offsetx,(float)_width);
+        if (invertx) cimg_foroff(seqx,x) seqx(x) = _width - 1 - seqx(x);
+      }
+
+      if (deltay!=0) {
+        const float dy = deltay>0?deltay:_height*-deltay/100;
+        const unsigned int ny = (unsigned int)(_height/dy);
+        seqy = CImg<uintT>::sequence(1+ny,0,(unsigned int)(dy*ny));
+        if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y)+offsety,(float)_height);
+        if (inverty) cimg_foroff(seqy,y) seqy(y) = _height - 1 - seqy(y);
+     }
+      return draw_grid(seqx,seqy,color,opacity,patternx,patterny);
+    }
+
+    //! Draw a 1d graph on the instance image.
+    /**
+       \param data Image containing the graph values I = f(x).
+       \param color Array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity Drawing opacity.
+
+       \param plot_type Define the type of the plot :
+                      - 0 = No plot.
+                      - 1 = Plot using segments.
+                      - 2 = Plot using cubic splines.
+                      - 3 = Plot with bars.
+       \param vertex_type Define the type of points :
+                      - 0 = No points.
+                      - 1 = Point.
+                      - 2 = Straight cross.
+                      - 3 = Diagonal cross.
+                      - 4 = Filled circle.
+                      - 5 = Outlined circle.
+                      - 6 = Square.
+                      - 7 = Diamond.
+       \param ymin Lower bound of the y-range.
+       \param ymax Upper bound of the y-range.
+       \param expand Expand plot along the X-axis.
+       \param pattern Drawing pattern.
+       \note
+         - if \c ymin==ymax==0, the y-range is computed automatically from the input samples.
+    **/
+    template<typename t, typename tc>
+    CImg<T>& draw_graph(const CImg<t>& data,
+                        const tc *const color, const float opacity=1,
+                        const unsigned int plot_type=1, const int vertex_type=1,
+                        const double ymin=0, const double ymax=0, const bool expand=false,
+                        const unsigned int pattern=~0U) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_graph() : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty() || _height<=1) return *this;
+      const unsigned int siz = data.size();
+      tc *color1 = 0, *color2 = 0;
+      if (plot_type==3) {
+        color1 = new tc[_spectrum]; color2 = new tc[_spectrum];
+        cimg_forC(*this,c) { color1[c] = (tc)(color[c]*0.6f); color2[c] = (tc)(color[c]*0.3f); }
+      }
+
+      double m = ymin, M = ymax;
+      if (ymin==ymax) m = (double)data.max_min(M);
+      if (m==M) { --m; ++M; }
+      const float ca = (float)(M-m)/(_height-1);
+      bool init_hatch = true;
+      const unsigned int xp = expand?1:0;
+
+      // Draw graph edges
+      switch (plot_type%4) {
+      case 1 : { // Segments
+        int oX = 0, oY = (int)((data[0]-m)/ca);
+        for (unsigned int off = 1; off<siz; ++off) {
+          const int
+            X = (int)(off*_width/(siz-xp)),
+            Y = (int)((data[off]-m)/ca);
+          draw_line(oX,oY,X,Y,color,opacity,pattern,init_hatch);
+          oX = X; oY = Y;
+          init_hatch = false;
+        }
+      } break;
+      case 2 : { // Spline
+        const CImg<t> ndata(data._data,siz,1,1,1,true);
+        int oY = (int)((data[0]-m)/ca);
+        const int xmax = (int)(_width*(ndata._width-1-xp)/ndata._width);
+        for (int x = 0; x<xmax; ++x) {
+          const int Y = (int)((ndata._cubic_atX((float)x*(ndata._width-xp)/_width)-m)/ca);
+          if (x>0) draw_line(x,oY,x+1,Y,color,opacity,pattern,init_hatch);
+          init_hatch = false;
+          oY = Y;
+        }
+      } break;
+      case 3 : { // Bars
+        const int Y0 = (int)(-m/ca);
+        int oX = 0;
+        cimg_foroff(data,off) {
+          const int
+            X = (off+1)*_width/siz-1,
+            Y = (int)((data[off]-m)/ca);
+          draw_rectangle(oX,Y0,X,Y,color1,opacity).
+            draw_line(oX,Y,oX,Y0,color2,opacity).
+            draw_line(oX,Y0,X,Y0,Y<=Y0?color2:color,opacity).
+            draw_line(X,Y,X,Y0,color,opacity).
+            draw_line(oX,Y,X,Y,Y<=Y0?color:color2,opacity);
+          oX = X+1;
+        }
+      } break;
+      default : break; // No edges
+      }
+
+      // Draw graph points
+      switch (vertex_type%8) {
+      case 1 : { // Point
+        cimg_foroff(data,off) {
+          const int X = off*_width/(siz-xp), Y = (int)((data[off]-m)/ca);
+          draw_point(X,Y,color,opacity);
+        }
+      } break;
+      case 2 : { // Straight Cross
+        cimg_foroff(data,off) {
+          const int X = off*_width/(siz-xp), Y = (int)((data[off]-m)/ca);
+          draw_line(X-3,Y,X+3,Y,color,opacity).draw_line(X,Y-3,X,Y+3,color,opacity);
+        }
+      } break;
+      case 3 : { // Diagonal Cross
+        cimg_foroff(data,off) {
+          const int X = off*_width/(siz-xp), Y = (int)((data[off]-m)/ca);
+          draw_line(X-3,Y-3,X+3,Y+3,color,opacity).draw_line(X-3,Y+3,X+3,Y-3,color,opacity);
+        }
+      } break;
+      case 4 : { // Filled Circle
+        cimg_foroff(data,off) {
+          const int X = off*_width/(siz-xp), Y = (int)((data[off]-m)/ca);
+          draw_circle(X,Y,3,color,opacity);
+        }
+      } break;
+      case 5 : { // Outlined circle
+        cimg_foroff(data,off) {
+          const int X = off*_width/(siz-xp), Y = (int)((data[off]-m)/ca);
+          draw_circle(X,Y,3,color,opacity,0U);
+        }
+      } break;
+      case 6 : { // Square
+        cimg_foroff(data,off) {
+          const int X = off*_width/(siz-xp), Y = (int)((data[off]-m)/ca);
+          draw_rectangle(X-3,Y-3,X+3,Y+3,color,opacity,~0U);
+        }
+      } break;
+      case 7 : { // Diamond
+        cimg_foroff(data,off) {
+          const int X = off*_width/(siz-xp), Y = (int)((data[off]-m)/ca);
+          draw_line(X,Y-4,X+4,Y,color,opacity).
+            draw_line(X+4,Y,X,Y+4,color,opacity).
+            draw_line(X,Y+4,X-4,Y,color,opacity).
+            draw_line(X-4,Y,X,Y-4,color,opacity);
+        }
+      } break;
+      default : break; // No points
+      }
+
+      if (color1) delete[] color1; if (color2) delete[] color2;
+      return *this;
+    }
+
+    //! 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 spectrum() values of type \c T, defining the drawing color.
+       \param region Image that will contain the mask of the filled region mask, as an output.
+       \param sigma Tolerance concerning neighborhood values.
+       \param opacity Opacity of the drawing.
+       \param high_connexity Tells if 8-connexity must be used (only for 2d images).
+       \return \p region is initialized with the binary mask of the filled region.
+    **/
+    template<typename tc, typename t>
+    CImg<T>& draw_fill(const int x, const int y, const int z,
+                       const tc *const color, const float opacity,
+                       CImg<t>& region, const float sigma=0,
+                       const bool high_connexity=false) {
+
+#define _cimg_draw_fill_test(x,y,z,res) if (region(x,y,z)) res = false; else { \
+  res = true; \
+  const T *reference_col = reference_color._data + _spectrum, *ptrs = data(x,y,z) + siz; \
+  for (unsigned int i = _spectrum; res && i; --i) { ptrs-=whd; res = (cimg::abs(*ptrs - *(--reference_col))<=sigma); } \
+  region(x,y,z) = (t)(res?1:noregion); \
+}
+
+#define _cimg_draw_fill_set(x,y,z) { \
+  const tc *col = color; \
+  T *ptrd = data(x,y,z); \
+  if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } \
+  else cimg_forC(*this,c) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whd; } \
+}
+
+#define _cimg_draw_fill_insert(x,y,z) { \
+  if (posr1>=remaining._height) remaining.resize(3,remaining._height<<1,1,1,0); \
+  unsigned int *ptrr = remaining.data(0,posr1); \
+  *(ptrr++) = x; *(ptrr++) = y; *(ptrr++) = z; ++posr1; \
+}
+
+#define _cimg_draw_fill_test_neighbor(x,y,z,cond) if (cond) { \
+  const unsigned int tx = x, ty = y, tz = z; \
+  _cimg_draw_fill_test(tx,ty,tz,res); if (res) _cimg_draw_fill_insert(tx,ty,tz); \
+}
+
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_fill() : Specified color is (null).",
+                                    cimg_instance);
+
+      region.assign(_width,_height,_depth,1,(t)0);
+      if (x>=0 && x<width() && y>=0 && y<height() && z>=0 && z<depth()) {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        const unsigned int whd = _width*_height*_depth, siz = _spectrum*whd, W1 = _width-1, H1 = _height-1, D1 = _depth-1;
+        const bool is_threed = (_depth>1);
+        const CImg<T> reference_color = get_vector_at(x,y,z);
+        CImg<uintT> remaining(3,512,1,1,0);
+        remaining(0,0) = x; remaining(1,0) = y; remaining(2,0) = z;
+        unsigned int posr0 = 0, posr1 = 1;
+        region(x,y,z) = (t)1;
+        const t noregion = ((t)1==(t)2)?(t)0:(t)(-1);
+        if (is_threed) do { // 3d version of the filling algorithm
+          const unsigned int *pcurr = remaining.data(0,posr0++), xc = *(pcurr++), yc = *(pcurr++), zc = *(pcurr++);
+          if (posr0>=512) { remaining.shift(0,-(int)posr0); posr1-=posr0; posr0 = 0; }
+          bool cont, res;
+          unsigned int nxc = xc;
+          do { // X-backward
+            _cimg_draw_fill_set(nxc,yc,zc);
+            _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
+            _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
+            _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
+            _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
+            if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
+          } while (cont);
+          nxc = xc;
+          do { // X-forward
+            if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(nxc,yc,zc);
+              _cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
+              _cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
+              _cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
+              _cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
+            }
+          } while (cont);
+          unsigned int nyc = yc;
+          do { // Y-backward
+            if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(xc,nyc,zc);
+              _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
+              _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
+              _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
+              _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
+            }
+          } while (cont);
+          nyc = yc;
+          do { // Y-forward
+            if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(xc,nyc,zc);
+              _cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
+              _cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
+              _cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
+              _cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
+            }
+          } while (cont);
+          unsigned int nzc = zc;
+          do { // Z-backward
+            if (nzc) { --nzc; _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(xc,yc,nzc);
+              _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
+              _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
+              _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
+              _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
+            }
+          } while (cont);
+          nzc = zc;
+          do { // Z-forward
+            if ((++nzc)<=D1) { _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(xc,nyc,zc);
+              _cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
+              _cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
+              _cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
+              _cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
+            }
+          } while (cont);
+        } while (posr1>posr0);
+        else do { // 2d version of the filling algorithm
+          const unsigned int *pcurr = remaining.data(0,posr0++), xc = *(pcurr++), yc = *(pcurr++);
+          if (posr0>=512) { remaining.shift(0,-(int)posr0); posr1-=posr0; posr0 = 0; }
+          bool cont, res;
+          unsigned int nxc = xc;
+          do { // X-backward
+            _cimg_draw_fill_set(nxc,yc,0);
+            _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
+            _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
+            if (high_connexity) {
+              _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
+              _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
+              _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
+              _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
+            }
+            if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
+          } while (cont);
+          nxc = xc;
+          do { // X-forward
+            if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(nxc,yc,0);
+              _cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
+              _cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
+              if (high_connexity) {
+                _cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
+                _cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
+                _cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
+                _cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
+              }
+            }
+          } while (cont);
+          unsigned int nyc = yc;
+          do { // Y-backward
+            if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(xc,nyc,0);
+              _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
+              _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
+              if (high_connexity) {
+                _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
+                _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
+                _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
+                _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
+              }
+            }
+          } while (cont);
+          nyc = yc;
+          do { // Y-forward
+            if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
+            if (cont) {
+              _cimg_draw_fill_set(xc,nyc,0);
+              _cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
+              _cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
+              if (high_connexity) {
+                _cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
+                _cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
+                _cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
+                _cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
+              }
+            }
+          } while (cont);
+        } while (posr1>posr0);
+        if (noregion) cimg_for(region,ptrd,t) if (*ptrd==noregion) *ptrd = (t)0;
+      }
+      return *this;
+    }
+
+    //! Draw a 3d filled region starting from a point (\c x,\c y,\ z) in the 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 spectrum() values of type \c T, defining the drawing color.
+       \param sigma = tolerance concerning neighborhood values.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename tc>
+    CImg<T>& draw_fill(const int x, const int y, const int z,
+                       const tc *const color, const float opacity=1,
+                       const float sigma=0, const bool high_connexity=false) {
+      CImg<boolT> tmp;
+      return draw_fill(x,y,z,color,opacity,tmp,sigma,high_connexity);
+    }
+
+    //! Draw a 2d filled region starting from a point (\c x,\c y) in the 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 spectrum() values of type \c T, defining the drawing color.
+       \param sigma = tolerance concerning neighborhood values.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename tc>
+    CImg<T>& draw_fill(const int x, const int y,
+                       const tc *const color, const float opacity=1,
+                       const float sigma=0, const bool high_connexity=false) {
+      CImg<boolT> tmp;
+      return draw_fill(x,y,0,color,opacity,tmp,sigma,high_connexity);
+    }
+
+    //! Draw a plasma random texture.
+    /**
+       \param x0 = X-coordinate of the upper-left corner of the plasma.
+       \param y0 = Y-coordinate of the upper-left corner of the plasma.
+       \param x1 = X-coordinate of the lower-right corner of the plasma.
+       \param y1 = Y-coordinate of the lower-right corner of the plasma.
+       \param alpha = Alpha-parameter of the plasma.
+       \param beta = Beta-parameter of the plasma.
+       \param opacity = opacity of the drawing.
+    **/
+    CImg<T>& draw_plasma(const int x0, const int y0, const int x1, const int y1,
+                         const float alpha=1, const float beta=1,
+                         const float opacity=1) {
+      if (!is_empty()) {
+        const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+        int nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1;
+        if (nx1<nx0) cimg::swap(nx0,nx1);
+        if (ny1<ny0) cimg::swap(ny0,ny1);
+        if (nx0<0) nx0 = 0;
+        if (nx1>=width()) nx1 = _width-1;
+        if (ny0<0) ny0 = 0;
+        if (ny1>=height()) ny1 = _height-1;
+        const int xc = (nx0+nx1)/2, yc = (ny0+ny1)/2, dx = (xc-nx0), dy = (yc-ny0);
+        const Tfloat dc = (Tfloat)(std::sqrt((float)(dx*dx+dy*dy))*alpha + beta);
+        Tfloat val = 0;
+        cimg_forC(*this,c) {
+          if (opacity>=1) {
+            const Tfloat
+              val0 = (Tfloat)((*this)(nx0,ny0,0,c)), val1 = (Tfloat)((*this)(nx1,ny0,0,c)),
+              val2 = (Tfloat)((*this)(nx0,ny1,0,c)), val3 = (Tfloat)((*this)(nx1,ny1,0,c));
+            (*this)(xc,ny0,0,c) = (T)((val0+val1)/2);
+            (*this)(xc,ny1,0,c) = (T)((val2+val3)/2);
+            (*this)(nx0,yc,0,c) = (T)((val0+val2)/2);
+            (*this)(nx1,yc,0,c) = (T)((val1+val3)/2);
+            do {
+              val = (Tfloat)(0.25f*((Tfloat)((*this)(nx0,ny0,0,c)) +
+                                    (Tfloat)((*this)(nx1,ny0,0,c)) +
+                                    (Tfloat)((*this)(nx1,ny1,0,c)) +
+                                    (Tfloat)((*this)(nx0,ny1,0,c))) +
+                             dc*cimg::grand());
+            } while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
+            (*this)(xc,yc,0,c) = (T)val;
+          } else {
+            const Tfloat
+              val0 = (Tfloat)((*this)(nx0,ny0,0,c)), val1 = (Tfloat)((*this)(nx1,ny0,0,c)),
+              val2 = (Tfloat)((*this)(nx0,ny1,0,c)), val3 = (Tfloat)((*this)(nx1,ny1,0,c));
+            (*this)(xc,ny0,0,c) = (T)(((val0+val1)*nopacity + copacity*(*this)(xc,ny0,0,c))/2);
+            (*this)(xc,ny1,0,c) = (T)(((val2+val3)*nopacity + copacity*(*this)(xc,ny1,0,c))/2);
+            (*this)(nx0,yc,0,c) = (T)(((val0+val2)*nopacity + copacity*(*this)(nx0,yc,0,c))/2);
+            (*this)(nx1,yc,0,c) = (T)(((val1+val3)*nopacity + copacity*(*this)(nx1,yc,0,c))/2);
+            do {
+              val = (Tfloat)(0.25f*(((Tfloat)((*this)(nx0,ny0,0,c)) +
+                                     (Tfloat)((*this)(nx1,ny0,0,c)) +
+                                     (Tfloat)((*this)(nx1,ny1,0,c)) +
+                                     (Tfloat)((*this)(nx0,ny1,0,c))) +
+                                    dc*cimg::grand())*nopacity + copacity*(*this)(xc,yc,0,c));
+            } while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
+            (*this)(xc,yc,0,c) = (T)val;
+          }
+        }
+        if (xc!=nx0 || yc!=ny0) {
+          draw_plasma(nx0,ny0,xc,yc,alpha,beta,opacity);
+          draw_plasma(xc,ny0,nx1,yc,alpha,beta,opacity);
+          draw_plasma(nx0,yc,xc,ny1,alpha,beta,opacity);
+          draw_plasma(xc,yc,nx1,ny1,alpha,beta,opacity);
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a plasma random texture.
+    /**
+       \param alpha = Alpha-parameter of the plasma.
+       \param beta = Beta-parameter of the plasma.
+       \param opacity = opacity of the drawing.
+    **/
+    CImg<T>& draw_plasma(const float alpha=1, const float beta=1,
+                         const float opacity=1) {
+      return draw_plasma(0,0,_width-1,_height-1,alpha,beta,opacity);
+    }
+
+    //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
+    template<typename tc>
+    CImg<T>& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1,
+                             const CImg<tc>& color_palette, const float opacity=1,
+                             const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
+                             const unsigned int iteration_max=255,
+                             const bool normalized_iteration=false,
+                             const bool julia_set=false,
+                             const double paramr=0, const double parami=0) {
+      if (is_empty()) return *this;
+      CImg<tc> palette;
+      if (color_palette) palette.assign(color_palette._data,color_palette.size()/color_palette._spectrum,1,1,color_palette._spectrum,true);
+      if (palette && palette._spectrum!=_spectrum)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_mandelbrot() : Instance and specified color palette (%u,%u,%u,%u,%p) have incompatible dimensions.",
+                                    cimg_instance,
+                                    color_palette._width,color_palette._height,color_palette._depth,color_palette._spectrum,color_palette._data);
+
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), ln2 = (float)std::log(2.0);
+      unsigned int iteration = 0;
+      cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
+        const double x = z0r + p*(z1r-z0r)/_width, y = z0i + q*(z1i-z0i)/_height;
+        double zr, zi, cr, ci;
+        if (julia_set) { zr = x; zi = y; cr = paramr; ci = parami; }
+        else { zr = paramr; zi = parami; cr = x; ci = y; }
+        for (iteration=1; zr*zr + zi*zi<=4 && iteration<=iteration_max; ++iteration) {
+          const double temp = zr*zr - zi*zi + cr;
+          zi = 2*zr*zi + ci;
+          zr = temp;
+        }
+        if (iteration>iteration_max) {
+          if (palette) {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette(0,c);
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(0,c)*nopacity + (*this)(p,q,0,c)*copacity);
+          } else {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)0;
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)((*this)(p,q,0,c)*copacity);
+          }
+        } else if (normalized_iteration) {
+          const float
+            normz = (float)cimg::abs(zr*zr+zi*zi),
+            niteration = (float)(iteration + 1 - std::log(std::log(normz))/ln2);
+          if (palette) {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._linear_atX(niteration,c);
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette._linear_atX(niteration,c)*nopacity + (*this)(p,q,0,c)*copacity);
+          } else {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)niteration;
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(niteration*nopacity + (*this)(p,q,0,c)*copacity);
+          }
+        } else {
+          if (palette) {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)palette._atX(iteration,c);
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(palette(iteration,c)*nopacity + (*this)(p,q,0,c)*copacity);
+          } else {
+            if (opacity>=1) cimg_forC(*this,c) (*this)(p,q,0,c) = (T)iteration;
+            else cimg_forC(*this,c) (*this)(p,q,0,c) = (T)(iteration*nopacity + (*this)(p,q,0,c)*copacity);
+          }
+        }
+      }
+      return *this;
+    }
+
+    //! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
+    template<typename tc>
+    CImg<T>& draw_mandelbrot(const CImg<tc>& color_palette, const float opacity=1,
+                             const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
+                             const unsigned int iteration_max=255,
+                             const bool normalized_iteration=false,
+                             const bool julia_set=false,
+                             const double paramr=0, const double parami=0) {
+      return draw_mandelbrot(0,0,_width-1,_height-1,color_palette,opacity,
+                             z0r,z0i,z1r,z1i,iteration_max,normalized_iteration,julia_set,paramr,parami);
+    }
+
+    //! Draw a 1d gaussian function in the instance image.
+    /**
+       \param xc = X-coordinate of the gaussian center.
+       \param sigma = Standard variation of the gaussian distribution.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename tc>
+    CImg<T>& draw_gaussian(const float xc, const float sigma,
+                           const tc *const color, const float opacity=1) {
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_gaussian() : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const unsigned int whd = _width*_height*_depth;
+      const tc *col = color;
+      cimg_forX(*this,x) {
+        const float dx = (x - xc), val = (float)std::exp(-dx*dx/sigma2);
+        T *ptrd = data(x,0,0,0);
+        if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; }
+        else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; }
+        col-=_spectrum;
+      }
+      return *this;
+    }
+
+    //! Draw an anisotropic 2d gaussian function.
+    /**
+       \param xc = X-coordinate of the gaussian center.
+       \param yc = Y-coordinate of the gaussian center.
+       \param tensor = 2x2 covariance matrix.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename t, typename tc>
+    CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
+                           const tc *const color, const float opacity=1) {
+      if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_gaussian() : Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.",
+                                    cimg_instance,
+                                    tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data);
+      if (!color)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_gaussian() : Specified color is (null).",
+                                    cimg_instance);
+
+      if (is_empty()) return *this;
+      typedef typename CImg<t>::Tfloat tfloat;
+      const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
+      const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = invT2(1,1);
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const unsigned int whd = _width*_height*_depth;
+      const tc *col = color;
+      float dy = -yc;
+      cimg_forY(*this,y) {
+        float dx = -xc;
+        cimg_forX(*this,x) {
+          const float val = (float)std::exp(a*dx*dx + b*dx*dy + c*dy*dy);
+          T *ptrd = data(x,y,0,0);
+          if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; }
+          else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; }
+          col-=_spectrum;
+          ++dx;
+        }
+        ++dy;
+      }
+      return *this;
+    }
+
+    //! Draw an anisotropic 2d gaussian function.
+    template<typename tc>
+    CImg<T>& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv,
+                           const tc *const color, const float opacity=1) {
+      const double
+        a = r1*ru*ru + r2*rv*rv,
+        b = (r1-r2)*ru*rv,
+        c = r1*rv*rv + r2*ru*ru;
+      const CImg<Tfloat> tensor(2,2,1,1, a,b,b,c);
+      return draw_gaussian(xc,yc,tensor,color,opacity);
+    }
+
+    //! Draw an isotropic 2d gaussian function.
+    /**
+       \param xc = X-coordinate of the gaussian center.
+       \param yc = Y-coordinate of the gaussian center.
+       \param sigma = standard variation of the gaussian distribution.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename tc>
+    CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma,
+                           const tc *const color, const float opacity=1) {
+      return draw_gaussian(xc,yc,CImg<floatT>::diagonal(sigma,sigma),color,opacity);
+    }
+
+    //! Draw an anisotropic 3d gaussian function.
+    /**
+       \param xc = X-coordinate of the gaussian center.
+       \param yc = Y-coordinate of the gaussian center.
+       \param zc = Z-coordinate of the gaussian center.
+       \param tensor = 3x3 covariance matrix.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename t, typename tc>
+    CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
+                           const tc *const color, const float opacity=1) {
+      if (is_empty()) return *this;
+      typedef typename CImg<t>::Tfloat tfloat;
+      if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1)
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_gaussian() : Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.",
+                                    cimg_instance,
+                                    tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data);
+
+      const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
+      const tfloat a = invT(0,0), b = 2*invT(1,0), c = 2*invT(2,0), d = invT(1,1), e = 2*invT(2,1), f = invT(2,2);
+      const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
+      const unsigned int whd = _width*_height*_depth;
+      const tc *col = color;
+      cimg_forXYZ(*this,x,y,z) {
+        const float
+          dx = (x - xc), dy = (y - yc), dz = (z - zc),
+          val = (float)std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz);
+        T *ptrd = data(x,y,z,0);
+        if (opacity>=1) cimg_forC(*this,c) { *ptrd = (T)(val*(*col++)); ptrd+=whd; }
+        else cimg_forC(*this,c) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; }
+        col-=_spectrum;
+      }
+      return *this;
+    }
+
+    //! Draw an isotropic 3d gaussian function.
+   /**
+       \param xc = X-coordinate of the gaussian center.
+       \param yc = Y-coordinate of the gaussian center.
+       \param zc = Z-coordinate of the gaussian center.
+       \param sigma = standard variation of the gaussian distribution.
+       \param color = array of spectrum() values of type \c T, defining the drawing color.
+       \param opacity = opacity of the drawing.
+    **/
+    template<typename tc>
+    CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const float sigma,
+                           const tc *const color, const float opacity=1) {
+      return draw_gaussian(xc,yc,zc,CImg<floatT>::diagonal(sigma,sigma,sigma),color,opacity);
+    }
+
+    //! Draw a 3d object.
+    /**
+       \param X = X-coordinate of the 3d object position
+       \param Y = Y-coordinate of the 3d object position
+       \param Z = Z-coordinate of the 3d object position
+       \param vertices = Image Nx3 describing 3d point coordinates
+       \param primitives = List of P primitives
+       \param colors = List of P color (or textures)
+       \param opacities = Image or list of P opacities
+       \param render_type = Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud)
+       \param double_sided = Tell if object faces have two sides or are oriented.
+       \param focale = length of the focale
+       \param lightx = X-coordinate of the light
+       \param lighty = Y-coordinate of the light
+       \param lightz = Z-coordinate of the light
+       \param specular_shine = Shininess of the object
+    **/
+    template<typename tp, typename tf, typename tc, typename to>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImg<to>& opacities,
+                           const unsigned int render_type=4,
+                           const bool double_sided=false, const float focale=500,
+                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImg<to>& opacities,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+
+#ifdef cimg_use_board
+    template<typename tp, typename tf, typename tc, typename to>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImg<to>& opacities,
+                           const unsigned int render_type=4,
+                           const bool double_sided=false, const float focale=500,
+                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImg<to>& opacities,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+#endif
+
+    template<typename tp, typename tf, typename tc, typename to>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImgList<to>& opacities,
+                           const unsigned int render_type=4,
+                           const bool double_sided=false, const float focale=500,
+                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImgList<to>& opacities,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+
+#ifdef cimg_use_board
+    template<typename tp, typename tf, typename tc, typename to>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImgList<to>& opacities,
+                           const unsigned int render_type=4,
+                           const bool double_sided=false, const float focale=500,
+                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,double_sided,focale,lightx,lighty,lightz,
+                           specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors, const CImgList<to>& opacities,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+                            render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,1);
+    }
+#endif
+
+    //! Draw a 3d object.
+    template<typename tp, typename tf, typename tc>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors,
+                           const unsigned int render_type=4,
+                           const bool double_sided=false, const float focale=500,
+                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename tz>
+    CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
+    }
+
+#ifdef cimg_use_board
+    template<typename tp, typename tf, typename tc, typename to>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors,
+                           const unsigned int render_type=4,
+                           const bool double_sided=false, const float focale=500,
+                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
+                           const float specular_light=0.2f, const float specular_shine=0.1f) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,CImg<floatT>::empty());
+    }
+
+    template<typename tp, typename tf, typename tc, typename to, typename tz>
+    CImg<T>& draw_object3d(LibBoard::Board& board,
+                           const float x0, const float y0, const float z0,
+                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
+                           const CImgList<tc>& colors,
+                           const unsigned int render_type,
+                           const bool double_sided, const float focale,
+                           const float lightx, const float lighty, const float lightz,
+                           const float specular_light, const float specular_shine,
+                           CImg<tz>& zbuffer) {
+      return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::empty(),
+                           render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
+    }
+#endif
+
+    template<typename tc, typename to>
+    void __draw_object3d(const unsigned int n_primitive, const CImgList<to>& opacities, const CImg<tc>& color,
+                         const int nx0, const int ny0, const CImg<T>& sprite, const float opac) {
+      if (n_primitive<opacities._width && opacities[n_primitive].is_sameXY(color))
+        draw_image(nx0,ny0,sprite,opacities[n_primitive].get_resize(sprite._width,sprite._height,1,sprite._spectrum,1));
+      else draw_image(nx0,ny0,sprite,opac);
+    }
+
+    template<typename tc, typename to>
+    void __draw_object3d(const unsigned int, const CImg<to>&, const CImg<tc>&,
+                         const int nx0, const int ny0, const CImg<T>& sprite, const float opac) {
+      draw_image(nx0,ny0,sprite,opac);
+    }
+
+    template<typename tz, typename tp, typename tf, typename tc, typename to>
+    CImg<T>& _draw_object3d(void *const pboard, CImg<tz>& zbuffer,
+                            const float X, const float Y, const float Z,
+                            const CImg<tp>& vertices,
+                            const CImgList<tf>& primitives,
+                            const CImgList<tc>& colors,
+                            const to& opacities,
+                            const unsigned int render_type,
+                            const bool double_sided, const float focale,
+                            const float lightx, const float lighty, const float lightz,
+                            const float specular_light, const float specular_shine,
+                            const float sprite_scale) {
+      typedef typename cimg::superset2<tp,tz,float>::type tpfloat;
+      if (is_empty() || !vertices || !primitives) return *this;
+      char error_message[1024] = { 0 };
+      if (!vertices.is_object3d(primitives,colors,opacities,false,error_message))
+        throw CImgArgumentException(_cimg_instance
+                                    "draw_object3d() : Invalid specified 3d object (%u,%u) (%s).",
+                                    cimg_instance,vertices._width,primitives._width,error_message);
+#ifndef cimg_use_board
+      if (pboard) return *this;
+#endif
+      const float
+        nspec = 1 - (specular_light<0.0f?0.0f:(specular_light>1.0f?1.0f:specular_light)),
+        nspec2 = 1 + (specular_shine<0.0f?0.0f:specular_shine),
+        nsl1 = (nspec2 - 1)/cimg::sqr(nspec - 1),
+        nsl2 = 1 - 2*nsl1*nspec,
+        nsl3 = nspec2 - nsl1 - nsl2;
+
+      // Create light texture for phong-like rendering.
+      CImg<floatT> light_texture;
+      if (render_type==5) {
+        if (colors._width>primitives._width) light_texture.assign(colors[primitives._width])/=255;
+        else {
+          static CImg<floatT> default_light_texture;
+          static float olightx = 0, olighty = 0, olightz = 0, ospecular_shine = 0;
+          if (!default_light_texture ||
+              lightx!=olightx || lighty!=olighty || lightz!=olightz ||
+              specular_shine!=ospecular_shine || default_light_texture._spectrum<_spectrum) {
+            default_light_texture.assign(512,512);
+            const float
+              dlx = lightx - X,
+              dly = lighty - Y,
+              dlz = lightz - Z,
+              nl = (float)std::sqrt(dlx*dlx + dly*dly + dlz*dlz),
+              nlx = default_light_texture._width/2*(1 + dlx/nl),
+              nly = default_light_texture._height/2*(1 + dly/nl),
+              white[] = { 1 };
+            default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.0f,white);
+            cimg_forXY(default_light_texture,x,y) {
+              const float factor = default_light_texture(x,y);
+              if (factor>nspec) default_light_texture(x,y) = cimg::min(2,nsl1*factor*factor + nsl2*factor + nsl3);
+            }
+            default_light_texture.resize(-100,-100,1,_spectrum);
+            olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shine = specular_shine;
+          }
+          light_texture.assign(default_light_texture,true);
+        }
+      }
+
+      // Compute 3d to 2d projection.
+      CImg<tpfloat> projections(vertices._width,2);
+      tpfloat parallzmin = cimg::type<tpfloat>::max();
+      if (focale>0) cimg_forX(projections,l) { // Perspective projection
+          const tpfloat
+            x = (tpfloat)vertices(l,0),
+            y = (tpfloat)vertices(l,1),
+            z = (tpfloat)vertices(l,2);
+          const tpfloat projectedz = z + Z + focale;
+          projections(l,1) = Y + focale*y/projectedz;
+          projections(l,0) = X + focale*x/projectedz;
+        } else cimg_forX(projections,l) { // Parallel projection
+          const tpfloat
+            x = (tpfloat)vertices(l,0),
+            y = (tpfloat)vertices(l,1),
+            z = (tpfloat)vertices(l,2);
+          if (z<parallzmin) parallzmin = z;
+          projections(l,1) = Y + y;
+          projections(l,0) = X + x;
+        }
+
+      // Compute and sort visible primitives.
+      CImg<uintT> visibles(primitives._width);
+      CImg<tpfloat> zrange(primitives._width);
+      unsigned int nb_visibles = 0;
+      const tpfloat zmin = focale>0?(tpfloat)(1.5f - focale):cimg::type<tpfloat>::min();
+      cimglist_for(primitives,l) {
+        const CImg<tf>& primitive = primitives[l];
+        switch (primitive.size()) {
+
+        case 1 : { // Point
+          const unsigned int i0 = (unsigned int)primitive(0);
+          const tpfloat z0 = Z + vertices(i0,2);
+          if (z0>zmin) {
+            visibles(nb_visibles) = (unsigned int)l;
+            zrange(nb_visibles++) = z0;
+          }
+        } break;
+        case 5 : { // Sphere
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1);
+          const tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2);
+          tpfloat xm, xM, ym, yM;
+          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
+          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
+          if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) {
+            visibles(nb_visibles) = (unsigned int)l;
+            zrange(nb_visibles++) = (z0 + z1)/2;
+          }
+        } break;
+        case 2 : // Segment
+        case 6 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1);
+          const tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2);
+          tpfloat xm, xM, ym, yM;
+          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
+          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
+          if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin) {
+            visibles(nb_visibles) = (unsigned int)l;
+            zrange(nb_visibles++) = (z0 + z1)/2;
+          }
+        } break;
+        case 3 :  // Triangle
+        case 9 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1),
+            i2 = (unsigned int)primitive(2);
+          const tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2),
+            x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2);
+          tpfloat xm, xM, ym, yM;
+          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
+          if (x2<xm) xm = x2;
+          if (x2>xM) xM = x2;
+          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
+          if (y2<ym) ym = y2;
+          if (y2>yM) yM = y2;
+          if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) {
+            const tpfloat d = (x1-x0)*(y2-y0) - (x2-x0)*(y1-y0);
+            if (double_sided || d<0) {
+              visibles(nb_visibles) = (unsigned int)l;
+              zrange(nb_visibles++) = (z0 + z1 + z2)/3;
+            }
+          }
+        } break;
+        case 4 : // Rectangle
+        case 12 : {
+          const unsigned int
+            i0 = (unsigned int)primitive(0),
+            i1 = (unsigned int)primitive(1),
+            i2 = (unsigned int)primitive(2),
+            i3 = (unsigned int)primitive(3);
+          const tpfloat
+            x0 = projections(i0,0), y0 = projections(i0,1), z0 = Z + vertices(i0,2),
+            x1 = projections(i1,0), y1 = projections(i1,1), z1 = Z + vertices(i1,2),
+            x2 = projections(i2,0), y2 = projections(i2,1), z2 = Z + vertices(i2,2),
+            x3 = projections(i3,0), y3 = projections(i3,1), z3 = Z + vertices(i3,2);
+          tpfloat xm, xM, ym, yM;
+          if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
+          if (x2<xm) xm = x2;
+          if (x2>xM) xM = x2;
+          if (x3<xm) xm = x3;
+          if (x3>xM) xM = x3;
+          if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
+          if (y2<ym) ym = y2;
+          if (y2>yM) yM = y2;
+          if (y3<ym) ym = y3;
+          if (y3>yM) yM = y3;
+          if (xM>=0 && xm<_width && yM>=0 && ym<_height && z0>zmin && z1>zmin && z2>zmin) {
+            const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0);
+            if (double_sided || d<0) {
+              visibles(nb_visibles) = (unsigned int)l;
+              zrange(nb_visibles++) = (z0 + z1 + z2 + z3)/4;
+            }
+          }
+        } break;
+        default :
+          throw CImgArgumentException(_cimg_instance
+                                      "draw_object3d() : Invalid primitive[%u] with size %u "
+                                      "(should have size 1,2,3,4,5,6,9 or 12).",
+                                      cimg_instance,
+                                      l,primitive.size());
+        }
+      }
+      if (nb_visibles<=0) return *this;
+      CImg<uintT> permutations;
+      CImg<tpfloat>(zrange._data,nb_visibles,1,1,1,true).sort(permutations,false);
+
+      // Compute light properties
+      CImg<floatT> lightprops;
+      switch (render_type) {
+      case 3 : { // Flat Shading
+        lightprops.assign(nb_visibles);
+        cimg_forX(lightprops,l) {
+          const CImg<tf>& primitive = primitives(visibles(permutations(l)));
+          const unsigned int psize = primitive.size();
+          if (psize==3 || psize==4 || psize==9 || psize==12) {
+            const unsigned int
+              i0 = (unsigned int)primitive(0),
+              i1 = (unsigned int)primitive(1),
+              i2 = (unsigned int)primitive(2);
+            const tpfloat
+              x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2),
+              x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2),
+              x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2),
+              dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
+              dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
+              nx = dy1*dz2 - dz1*dy2,
+              ny = dz1*dx2 - dx1*dz2,
+              nz = dx1*dy2 - dy1*dx2,
+              norm = (tpfloat)std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
+              lx = X + (x0 + x1 + x2)/3 - lightx,
+              ly = Y + (y0 + y1 + y2)/3 - lighty,
+              lz = Z + (z0 + z1 + z2)/3 - lightz,
+              nl = (tpfloat)std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
+              factor = cimg::max(cimg::abs(-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
+            lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
+          } else lightprops[l] = 1;
+        }
+      } break;
+
+      case 4 : // Gouraud Shading
+      case 5 : { // Phong-Shading
+        CImg<tpfloat> vertices_normals(vertices._width,3,1,1,0);
+        for (unsigned int l = 0; l<nb_visibles; ++l) {
+          const CImg<tf>& primitive = primitives[visibles(l)];
+          const unsigned int psize = primitive.size();
+          const bool
+            triangle_flag = (psize==3) || (psize==9),
+            rectangle_flag = (psize==4) || (psize==12);
+          if (triangle_flag || rectangle_flag) {
+            const unsigned int
+              i0 = (unsigned int)primitive(0),
+              i1 = (unsigned int)primitive(1),
+              i2 = (unsigned int)primitive(2),
+              i3 = rectangle_flag?(unsigned int)primitive(3):0;
+            const tpfloat
+              x0 = (tpfloat)vertices(i0,0), y0 = (tpfloat)vertices(i0,1), z0 = (tpfloat)vertices(i0,2),
+              x1 = (tpfloat)vertices(i1,0), y1 = (tpfloat)vertices(i1,1), z1 = (tpfloat)vertices(i1,2),
+              x2 = (tpfloat)vertices(i2,0), y2 = (tpfloat)vertices(i2,1), z2 = (tpfloat)vertices(i2,2),
+              dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
+              dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
+              nnx = dy1*dz2 - dz1*dy2,
+              nny = dz1*dx2 - dx1*dz2,
+              nnz = dx1*dy2 - dy1*dx2,
+              norm = (tpfloat)(1e-5f + std::sqrt(nnx*nnx + nny*nny + nnz*nnz)),
+              nx = nnx/norm,
+              ny = nny/norm,
+              nz = nnz/norm;
+            vertices_normals(i0,0)+=nx; vertices_normals(i0,1)+=ny; vertices_normals(i0,2)+=nz;
+            vertices_normals(i1,0)+=nx; vertices_normals(i1,1)+=ny; vertices_normals(i1,2)+=nz;
+            vertices_normals(i2,0)+=nx; vertices_normals(i2,1)+=ny; vertices_normals(i2,2)+=nz;
+            if (rectangle_flag) { vertices_normals(i3,0)+=nx; vertices_normals(i3,1)+=ny; vertices_normals(i3,2)+=nz; }
+          }
+        }
+
+        if (double_sided) cimg_forX(vertices_normals,p) if (vertices_normals(p,2)>0) {
+          vertices_normals(p,0) = -vertices_normals(p,0);
+          vertices_normals(p,1) = -vertices_normals(p,1);
+          vertices_normals(p,2) = -vertices_normals(p,2);
+        }
+
+        if (render_type==4) {
+          lightprops.assign(vertices._width);
+          cimg_forX(lightprops,ll) {
+            const tpfloat
+              nx = vertices_normals(ll,0),
+              ny = vertices_normals(ll,1),
+              nz = vertices_normals(ll,2),
+              norm = (tpfloat)std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
+              lx = X + vertices(ll,0) - lightx,
+              ly = Y + vertices(ll,1) - lighty,
+              lz = Z + vertices(ll,2) - lightz,
+              nl = (tpfloat)std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
+              factor = cimg::max((-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
+            lightprops[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(vertices._width,2);
+          cimg_forX(lightprops,ll) {
+            const tpfloat
+              nx = vertices_normals(ll,0),
+              ny = vertices_normals(ll,1),
+              nz = vertices_normals(ll,2),
+              norm = (tpfloat)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 float _focale = focale>0?focale:(1-parallzmin);
+      const CImg<tc> default_color(1,_spectrum,1,1,(tc)200);
+      for (unsigned int l = 0; l<nb_visibles; ++l) {
+        const unsigned int n_primitive = visibles(permutations(l));
+        const CImg<tf>& primitive = primitives[n_primitive];
+        const CImg<tc>& color = n_primitive<colors._width?colors[n_primitive]:default_color;
+        const tc *const pcolor = color._data;
+        const float opac = n_primitive<opacities.size()?opacities(n_primitive,0):1.0f;
+#ifdef cimg_use_board
+        LibBoard::Board &board = *(LibBoard::Board*)pboard;
+#endif
+
+        switch (primitive.size()) {
+        case 1 : { // Colored point or sprite
+          const unsigned int n0 = (unsigned int)primitive[0];
+          const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
+          if (color.size()==_spectrum) { // Colored point.
+            draw_point(x0,y0,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillCircle((float)x0,height()-(float)y0,0);
+            }
+#endif
+          } else { // Colored sprite.
+            const tpfloat z = Z + vertices(n0,2);
+            const float factor = sprite_scale*(focale>0?focale/(z + focale):1);
+            const unsigned int
+              _sw = (unsigned int)(color._width*factor),
+              _sh = (unsigned int)(color._height*factor),
+              sw = _sw?_sw:1,
+              sh = _sh?_sh:1;
+            const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2;
+            if (sw<_width && sh<_height && (nx0+(int)sw/2>=0 || nx0-(int)sw/2<width() || ny0+(int)sh/2>=0 || ny0-(int)sh/2<height())) {
+              const CImg<tc>
+                _sprite = (sw!=color._width || sh!=color._height)?color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<tc>(),
+                &sprite = _sprite?_sprite:color;
+              __draw_object3d(n_primitive,opacities,color,nx0,ny0,sprite,opac);
+#ifdef cimg_use_board
+              if (pboard) {
+                board.setPenColorRGBi(128,128,128);
+                board.setFillColor(LibBoard::Color::None);
+                board.drawRectangle((float)nx0,height()-(float)ny0,sw,sh);
+              }
+#endif
+            }
+          }
+        } break;
+        case 2 : { // Colored line
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1];
+          const int
+            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
+          const float
+            z0 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale;
+          if (render_type) {
+            if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opac);
+            else draw_line(x0,y0,x1,y1,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,x1,height()-(float)y1);
+            }
+#endif
+          } else {
+            draw_point(x0,y0,pcolor,opac).draw_point(x1,y1,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+            }
+#endif
+          }
+        } break;
+        case 5 : { // Colored sphere
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1];
+          const float
+            Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)),
+            Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)),
+            Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)),
+            zc = Z + Zc + _focale,
+            xc = X + Xc*(focale>0?focale/zc:1),
+            yc = Y + Yc*(focale>0?focale/zc:1),
+            radius = std::sqrt(cimg::sqr(Xc-vertices(n0,0)) + cimg::sqr(Yc-vertices(n0,1)) + cimg::sqr(Zc-vertices(n0,2)))*(focale>0?focale/zc:1);
+          switch (render_type) {
+          case 0 :
+            draw_point((int)xc,(int)yc,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillCircle(xc,height()-yc,0);
+            }
+#endif
+            break;
+          case 1 :
+            draw_circle((int)xc,(int)yc,(int)radius,pcolor,opac,~0U);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.setFillColor(LibBoard::Color::None);
+              board.drawCircle(xc,height()-yc,radius);
+            }
+#endif
+            break;
+          default :
+            draw_circle((int)xc,(int)yc,(int)radius,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillCircle(xc,height()-yc,radius);
+            }
+#endif
+            break;
+          }
+        } break;
+        case 6 : { // Textured line
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1],
+            tx0 = (unsigned int)primitive[2],
+            ty0 = (unsigned int)primitive[3],
+            tx1 = (unsigned int)primitive[4],
+            ty1 = (unsigned int)primitive[5];
+          const int
+            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
+          const float
+            z0 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale;
+          if (render_type) {
+            if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac);
+            else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,(float)x1,height()-(float)y1);
+            }
+#endif
+          } else {
+            draw_point(x0,y0,color.get_vector_at(tx0,ty0)._data,opac).
+              draw_point(x1,y1,color.get_vector_at(tx1,ty1)._data,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+            }
+#endif
+          }
+        } break;
+        case 3 : { // Colored triangle
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1],
+            n2 = (unsigned int)primitive[2];
+          const int
+            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
+          const float
+            z0 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale;
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,pcolor,opac).draw_point(x1,y1,pcolor,opac).draw_point(x2,y2,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(float)y2,0);
+            }
+#endif
+            break;
+          case 1 :
+            if (zbuffer)
+              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opac).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opac).
+                draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opac);
+            else
+              draw_line(x0,y0,x1,y1,pcolor,opac).draw_line(x0,y0,x2,y2,pcolor,opac).
+                draw_line(x1,y1,x2,y2,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x0,height()-(float)y0,(float)x2,height()-(float)y2);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 2 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac);
+            else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 3 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac,lightprops(l));
+            else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opac,lightprops(l));
+#ifdef cimg_use_board
+            if (pboard) {
+              const float lp = cimg::min(lightprops(l),1);
+              board.setPenColorRGBi((unsigned char)(color[0]*lp),
+                                     (unsigned char)(color[1]*lp),
+                                     (unsigned char)(color[2]*lp),
+                                     (unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 4 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+            else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi((unsigned char)(color[0]),
+                                     (unsigned char)(color[1]),
+                                     (unsigned char)(color[2]),
+                                     (unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprops(n0),
+                                         (float)x1,height()-(float)y1,lightprops(n1),
+                                         (float)x2,height()-(float)y2,lightprops(n2));
+            }
+#endif
+            break;
+          case 5 : {
+            const unsigned int
+              lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
+              lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
+              lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1);
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
+            else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              const float
+                l0 = light_texture((int)(light_texture.width()/2*(1+lightprops(n0,0))), (int)(light_texture.height()/2*(1+lightprops(n0,1)))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lightprops(n1,0))), (int)(light_texture.height()/2*(1+lightprops(n1,1)))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lightprops(n2,0))), (int)(light_texture.height()/2*(1+lightprops(n2,1))));
+              board.setPenColorRGBi((unsigned char)(color[0]),
+                                     (unsigned char)(color[1]),
+                                     (unsigned char)(color[2]),
+                                     (unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x1,height()-(float)y1,l1,
+                                         (float)x2,height()-(float)y2,l2);
+            }
+#endif
+          } break;
+          }
+        } break;
+        case 4 : { // Colored rectangle
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1],
+            n2 = (unsigned int)primitive[2],
+            n3 = (unsigned int)primitive[3];
+          const int
+            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
+            x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
+          const float
+            z0 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale,
+            z3 = vertices(n3,2) + Z + _focale;
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,pcolor,opac).draw_point(x1,y1,pcolor,opac).
+              draw_point(x2,y2,pcolor,opac).draw_point(x3,y3,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(float)y2,0);
+              board.drawCircle((float)x3,height()-(float)y3,0);
+            }
+#endif
+            break;
+          case 1 :
+            if (zbuffer)
+              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opac).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opac).
+                draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opac).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opac);
+            else
+              draw_line(x0,y0,x1,y1,pcolor,opac).draw_line(x1,y1,x2,y2,pcolor,opac).
+                draw_line(x2,y2,x3,y3,pcolor,opac).draw_line(x3,y3,x0,y0,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.drawLine((float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+              board.drawLine((float)x3,height()-(float)y3,(float)x0,height()-(float)y0);
+            }
+#endif
+            break;
+          case 2 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac).draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opac);
+            else
+              draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opac).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+            }
+#endif
+            break;
+          case 3 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,opac,lightprops(l)).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opac,lightprops(l));
+            else
+              _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opac,lightprops(l)).
+                _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opac,lightprops(l));
+#ifdef cimg_use_board
+            if (pboard) {
+              const float lp = cimg::min(lightprops(l),1);
+              board.setPenColorRGBi((unsigned char)(color[0]*lp),
+                                     (unsigned char)(color[1]*lp),
+                                     (unsigned char)(color[2]*lp),(unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+            }
+#endif
+            break;
+          case 4 : {
+            const float
+              lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
+              lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,lightprop0,lightprop1,lightprop2,opac).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,lightprop0,lightprop2,lightprop3,opac);
+            else
+              draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprop0,lightprop1,lightprop2,opac).
+                draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,lightprop0,lightprop2,lightprop3,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi((unsigned char)(color[0]),
+                                     (unsigned char)(color[1]),
+                                     (unsigned char)(color[2]),
+                                     (unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprop0,
+                                         (float)x1,height()-(float)y1,lightprop1,
+                                         (float)x2,height()-(float)y2,lightprop2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprop0,
+                                         (float)x2,height()-(float)y2,lightprop2,
+                                         (float)x3,height()-(float)y3,lightprop3);
+            }
+#endif
+          } break;
+          case 5 : {
+            const unsigned int
+              lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
+              lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
+              lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
+              lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+            else
+              draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+                draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              const float
+                l0 = light_texture((int)(light_texture.width()/2*(1+lx0)), (int)(light_texture.height()/2*(1+ly0))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lx1)), (int)(light_texture.height()/2*(1+ly1))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lx2)), (int)(light_texture.height()/2*(1+ly2))),
+                l3 = light_texture((int)(light_texture.width()/2*(1+lx3)), (int)(light_texture.height()/2*(1+ly3)));
+              board.setPenColorRGBi((unsigned char)(color[0]),
+                                     (unsigned char)(color[1]),
+                                     (unsigned char)(color[2]),
+                                     (unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x1,height()-(float)y1,l1,
+                                         (float)x2,height()-(float)y2,l2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x2,height()-(float)y2,l2,
+                                         (float)x3,height()-(float)y3,l3);
+            }
+#endif
+          } break;
+          }
+        } break;
+        case 9 : { // Textured triangle
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1],
+            n2 = (unsigned int)primitive[2],
+            tx0 = (unsigned int)primitive[3],
+            ty0 = (unsigned int)primitive[4],
+            tx1 = (unsigned int)primitive[5],
+            ty1 = (unsigned int)primitive[6],
+            tx2 = (unsigned int)primitive[7],
+            ty2 = (unsigned int)primitive[8];
+          const int
+            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
+          const float
+            z0 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale;
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,color.get_vector_at(tx0,ty0)._data,opac).
+              draw_point(x1,y1,color.get_vector_at(tx1,ty1)._data,opac).
+              draw_point(x2,y2,color.get_vector_at(tx2,ty2)._data,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(float)y2,0);
+            }
+#endif
+            break;
+          case 1 :
+            if (zbuffer)
+              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+                draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
+                draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
+            else
+              draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+                draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
+                draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x0,height()-(float)y0,(float)x2,height()-(float)y2);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 2 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
+            else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 3 :
+            if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
+            else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
+#ifdef cimg_use_board
+            if (pboard) {
+              const float lp = cimg::min(lightprops(l),1);
+              board.setPenColorRGBi((unsigned char)(128*lp),
+                                    (unsigned char)(128*lp),
+                                    (unsigned char)(128*lp),
+                                    (unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+            }
+#endif
+            break;
+          case 4 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+            else
+              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprops(n0),
+                                        (float)x1,height()-(float)y1,lightprops(n1),
+                                        (float)x2,height()-(float)y2,lightprops(n2));
+            }
+#endif
+            break;
+          case 5 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
+                            (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1),
+                            (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1),
+                            (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1),
+                            opac);
+            else
+              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
+                            (unsigned int)lightprops(n0,0),(unsigned int)lightprops(n0,1),
+                            (unsigned int)lightprops(n1,0),(unsigned int)lightprops(n1,1),
+                            (unsigned int)lightprops(n2,0),(unsigned int)lightprops(n2,1),
+                            opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              const float
+                l0 = light_texture((int)(light_texture.width()/2*(1+lightprops(n0,0))), (int)(light_texture.height()/2*(1+lightprops(n0,1)))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lightprops(n1,0))), (int)(light_texture.height()/2*(1+lightprops(n1,1)))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lightprops(n2,0))), (int)(light_texture.height()/2*(1+lightprops(n2,1))));
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,(float)x1,height()-(float)y1,l1,(float)x2,height()-(float)y2,l2);
+            }
+#endif
+            break;
+          }
+        } break;
+        case 12 : { // Textured quadrangle
+          const unsigned int
+            n0 = (unsigned int)primitive[0],
+            n1 = (unsigned int)primitive[1],
+            n2 = (unsigned int)primitive[2],
+            n3 = (unsigned int)primitive[3],
+            tx0 = (unsigned int)primitive[4],
+            ty0 = (unsigned int)primitive[5],
+            tx1 = (unsigned int)primitive[6],
+            ty1 = (unsigned int)primitive[7],
+            tx2 = (unsigned int)primitive[8],
+            ty2 = (unsigned int)primitive[9],
+            tx3 = (unsigned int)primitive[10],
+            ty3 = (unsigned int)primitive[11];
+          const int
+            x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
+            x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
+            x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
+            x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
+          const float
+            z0 = vertices(n0,2) + Z + _focale,
+            z1 = vertices(n1,2) + Z + _focale,
+            z2 = vertices(n2,2) + Z + _focale,
+            z3 = vertices(n3,2) + Z + _focale;
+
+          switch (render_type) {
+          case 0 :
+            draw_point(x0,y0,color.get_vector_at(tx0,ty0)._data,opac).
+              draw_point(x1,y1,color.get_vector_at(tx1,ty1)._data,opac).
+              draw_point(x2,y2,color.get_vector_at(tx2,ty2)._data,opac).
+              draw_point(x3,y3,color.get_vector_at(tx3,ty3)._data,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawCircle((float)x0,height()-(float)y0,0);
+              board.drawCircle((float)x1,height()-(float)y1,0);
+              board.drawCircle((float)x2,height()-(float)y2,0);
+              board.drawCircle((float)x3,height()-(float)y3,0);
+            }
+#endif
+            break;
+          case 1 :
+            if (zbuffer)
+              draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+                draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
+                draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
+                draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
+            else
+              draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
+                draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
+                draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
+                draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.drawLine((float)x0,height()-(float)y0,(float)x1,height()-(float)y1);
+              board.drawLine((float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.drawLine((float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+              board.drawLine((float)x3,height()-(float)y3,(float)x0,height()-(float)y0);
+            }
+#endif
+            break;
+          case 2 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
+            else
+              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
+                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+            }
+#endif
+            break;
+          case 3 :
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
+            else
+              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
+                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
+#ifdef cimg_use_board
+            if (pboard) {
+              const float lp = cimg::min(lightprops(l),1);
+              board.setPenColorRGBi((unsigned char)(128*lp),
+                                     (unsigned char)(128*lp),
+                                     (unsigned char)(128*lp),
+                                     (unsigned char)(opac*255));
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x1,height()-(float)y1,(float)x2,height()-(float)y2);
+              board.fillTriangle((float)x0,height()-(float)y0,(float)x2,height()-(float)y2,(float)x3,height()-(float)y3);
+            }
+#endif
+            break;
+          case 4 : {
+            const float
+              lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
+              lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
+            else
+              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
+                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprop0,
+                                         (float)x1,height()-(float)y1,lightprop1,
+                                         (float)x2,height()-(float)y2,lightprop2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,lightprop0,
+                                         (float)x2,height()-(float)y2,lightprop2,
+                                         (float)x3,height()-(float)y3,lightprop3);
+            }
+#endif
+          } break;
+          case 5 : {
+            const unsigned int
+              lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
+              lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
+              lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
+              lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
+            if (zbuffer)
+              draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+                draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+            else
+              draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
+                draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
+#ifdef cimg_use_board
+            if (pboard) {
+              const float
+                l0 = light_texture((int)(light_texture.width()/2*(1+lx0)), (int)(light_texture.height()/2*(1+ly0))),
+                l1 = light_texture((int)(light_texture.width()/2*(1+lx1)), (int)(light_texture.height()/2*(1+ly1))),
+                l2 = light_texture((int)(light_texture.width()/2*(1+lx2)), (int)(light_texture.height()/2*(1+ly2))),
+                l3 = light_texture((int)(light_texture.width()/2*(1+lx3)), (int)(light_texture.height()/2*(1+ly3)));
+              board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x1,height()-(float)y1,l1,
+                                         (float)x2,height()-(float)y2,l2);
+              board.fillGouraudTriangle((float)x0,height()-(float)y0,l0,
+                                         (float)x2,height()-(float)y2,l2,
+                                         (float)x3,height()-(float)y3,l3);
+            }
+#endif
+          } break;
+          }
+        } break;
+        }
+      }
+      return *this;
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name Data Input
+    //@{
+    //---------------------------
+
+    //! Simple interface to select a shape from an image.
+    /**
+       \param selection  Array of 6 values containing the selection result
+       \param coords_type Determine shape type to select (0=point, 1=vector, 2=rectangle, 3=circle)
+       \param disp       Display window used to make the selection
+       \param XYZ        Initial XYZ position (for volumetric images only)
+       \param color      Color of the shape selector.
+    **/
+    CImg<T>& select(CImgDisplay &disp,
+                    const int select_type=2, unsigned int *const XYZ=0,
+                    const unsigned char *const color=0) {
+      return get_select(disp,select_type,XYZ,color).move_to(*this);
+    }
+
+    //! Simple interface to select a shape from an image.
+    CImg<T>& select(const char *const title,
+                    const int select_type=2, unsigned int *const XYZ=0,
+                    const unsigned char *const color=0) {
+      return get_select(title,select_type,XYZ,color).move_to(*this);
+    }
+
+    //! Simple interface to select a shape from an image.
+    CImg<intT> get_select(CImgDisplay &disp,
+                          const int select_type=2, unsigned int *const XYZ=0,
+                          const unsigned char *const color=0) const {
+      return _get_select(disp,0,select_type,XYZ,color,0,0,0);
+    }
+
+    //! Simple interface to select a shape from an image.
+    CImg<intT> get_select(const char *const title,
+                          const int select_type=2, unsigned int *const XYZ=0,
+                          const unsigned char *const color=0) const {
+      CImgDisplay disp;
+      return _get_select(disp,title,select_type,XYZ,color,0,0,0);
+    }
+
+    CImg<intT> _get_select(CImgDisplay &disp, const char *const title,
+                           const int coords_type, unsigned int *const XYZ,
+                           const unsigned char *const color,
+                           const int origX, const int origY, const int origZ) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "select() : Empty instance.",
+                                    cimg_instance);
+      if (!disp) {
+        char ntitle[64] = { 0 }; if (!title) { cimg_snprintf(ntitle,sizeof(ntitle),"CImg<%s>",pixel_type()); }
+        disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:ntitle,1);
+      }
+
+      const unsigned int
+        old_normalization = disp.normalization(),
+        hatch = 0x55555555;
+
+      bool old_is_resized = disp.is_resized();
+      disp._normalization = 0;
+      disp.show().set_key(0).set_wheel();
+
+      unsigned char foreground_color[] = { 255,255,105 }, background_color[] = { 0,0,0 };
+      if (color) std::memcpy(foreground_color,color,sizeof(unsigned char)*cimg::min(3,spectrum()));
+
+      int area = 0, clicked_area = 0, phase = 0,
+        X0 = (int)((XYZ?XYZ[0]:_width/2)%_width), Y0 = (int)((XYZ?XYZ[1]:_height/2)%_height), Z0 = (int)((XYZ?XYZ[2]:_depth/2)%_depth),
+        X1 =-1, Y1 = -1, Z1 = -1,
+        X = -1, Y = -1, Z = -1,
+        oX = X, oY = Y, oZ = Z;
+      unsigned int old_button = 0, key = 0;
+
+      bool shape_selected = false, text_down = false;
+      CImg<ucharT> visu, visu0;
+      char text[1024] = { 0 };
+
+      while (!key && !disp.is_closed() && !shape_selected) {
+
+        // Handle mouse motion and selection
+        oX = X; oY = Y; oZ = Z;
+        int mx = disp.mouse_x(), my = disp.mouse_y();
+        const int mX = mx*(_width+(_depth>1?_depth:0))/disp.width(), mY = my*(_height+(_depth>1?_depth:0))/disp.height();
+
+        area = 0;
+        if (mX<width() && mY<height())  { area = 1; X = mX; Y = mY; Z = phase?Z1:Z0; }
+        if (mX<width() && mY>=height()) { area = 2; X = mX; Z = mY - _height; Y = phase?Y1:Y0; }
+        if (mX>=width() && mY<height()) { area = 3; Y = mY; Z = mX - _width; X = phase?X1:X0; }
+
+        switch (key = disp.key()) {
+#if cimg_OS!=2
+        case cimg::keyCTRLRIGHT :
+#endif
+        case 0 : case cimg::keyCTRLLEFT : key = 0; break;
+        case cimg::keyPAGEUP : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(1); key = 0; } break;
+        case cimg::keyPAGEDOWN : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { disp.set_wheel(-1); key = 0; } break;
+        case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
+                                            CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false).
+            _is_resized = true;
+          disp.set_key(key,false); key = 0;
+        } break;
+        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+          disp.set_key(key,false); key = 0; visu0.assign();
+        } break;
+        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true;
+          disp.set_key(key,false); key = 0; visu0.assign();
+        } break;
+        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
+          disp.set_key(key,false); key = 0; visu0.assign();
+        } break;
+        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          static unsigned int snap_number = 0;
+          char filename[32] = { 0 };
+          std::FILE *file;
+          do {
+            cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.bmp",snap_number++);
+            if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+          } while (file);
+          if (visu0) {
+            visu.draw_text(0,0," Saving snapshot... ",foreground_color,background_color,1,13).display(disp);
+            visu0.save(filename);
+            visu.draw_text(0,0," Snapshot '%s' saved. ",foreground_color,background_color,1,13,filename).display(disp);
+          }
+          disp.set_key(key,false); key = 0;
+        } break;
+        case cimg::keyO :
+          if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+#ifdef cimg_use_zlib
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.cimgz",snap_number++);
+#else
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.cimg",snap_number++);
+#endif
+              if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving instance... ",foreground_color,background_color,0.8f,13).display(disp);
+            save(filename);
+            visu.draw_text(0,0," Instance '%s' saved. ",foreground_color,background_color,0.8f,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        }
+
+        if (!area) mx = my = X = Y = Z = -1;
+        else {
+          if (disp.button()&1 && phase<2) { X1 = X; Y1 = Y; Z1 = Z; }
+          if (!(disp.button()&1) && phase>=2) {
+            switch (clicked_area) {
+            case 1 : Z1 = Z; break;
+            case 2 : Y1 = Y; break;
+            case 3 : X1 = X; break;
+            }
+          }
+          if (disp.button()&2) { if (phase) { X1 = X; Y1 = Y; Z1 = Z; } else { X0 = X; Y0 = Y; Z0 = Z; } }
+          if (disp.button()&4) { oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = 0; visu.assign(); }
+          if (disp.wheel()) {
+            if (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() && !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT() &&
+                !disp.is_keyALT() && !disp.is_keyALTGR()) {
+              switch (area) {
+              case 1 : if (phase) Z = (Z1+=disp.wheel()); else Z = (Z0+=disp.wheel()); break;
+              case 2 : if (phase) Y = (Y1+=disp.wheel()); else Y = (Y0+=disp.wheel()); break;
+              case 3 : if (phase) X = (X1+=disp.wheel()); else X = (X0+=disp.wheel()); break;
+              }
+              disp.set_wheel();
+            } else key = ~0U;
+          }
+          if ((disp.button()&1)!=old_button) {
+            switch (phase++) {
+            case 0 : X0 = X1 = X; Y0 = Y1 = Y; Z0 = Z1 = Z; clicked_area = area; break;
+            case 1 : X1 = X; Y1 = Y; Z1 = Z; break;
+            }
+            old_button = disp.button()&1;
+          }
+          if (_depth>1 && (X!=oX || Y!=oY || Z!=oZ)) visu0.assign();
+        }
+
+        if (phase) {
+          if (!coords_type) shape_selected = phase?true:false;
+          else {
+            if (_depth>1) shape_selected = (phase==3)?true:false;
+            else shape_selected = (phase==2)?true:false;
+          }
+        }
+
+        if (X0<0) X0 = 0; if (X0>=width()) X0 = width() - 1; if (Y0<0) Y0 = 0; if (Y0>=height()) Y0 = height() - 1;
+        if (Z0<0) Z0 = 0; if (Z0>=depth()) Z0 = depth() - 1;
+        if (X1<1) X1 = 0; if (X1>=width()) X1 = width() - 1; if (Y1<0) Y1 = 0; if (Y1>=height()) Y1 = height() - 1;
+        if (Z1<0) Z1 = 0; if (Z1>=depth()) Z1 = depth() - 1;
+
+        // Draw visualization image on the display
+        if (oX!=X || oY!=Y || oZ!=Z || !visu0) {
+          if (!visu0) {
+            CImg<Tuchar> tmp, tmp0;
+            if (_depth!=1) {
+              tmp0 = (!phase)?get_projections2d(X0,Y0,Z0):get_projections2d(X1,Y1,Z1);
+              tmp = tmp0.get_channels(0,cimg::min(2U,_spectrum - 1));
+            } else tmp = get_channels(0,cimg::min(2U,_spectrum - 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._spectrum);
+                unsigned char *ptrd = visu0.end();
+                cimg_for(tmp,ptrs,Tuchar) *(--ptrd) = (unsigned char)((*ptrs - m)*255.0f/(M - m));
+              } break;
+            default : visu0 = tmp.normalize(0,255);
+            }
+            visu0.resize(disp);
+          }
+          visu = visu0;
+          if (!color) {
+            if (visu.mean()<200) {
+              foreground_color[0] = foreground_color[1] = foreground_color[2] = 255;
+              background_color[0] = background_color[1] = background_color[2] = 0;
+            } else {
+              foreground_color[0] = foreground_color[1] = foreground_color[2] = 0;
+              background_color[0] = background_color[1] = background_color[2] = 255;
+            }
+          }
+
+          const int d = (_depth>1)?_depth:0;
+          if (phase) switch (coords_type) {
+          case 1 : {
+            const int
+              x0 = (int)((X0+0.5f)*disp.width()/(_width+d)),
+              y0 = (int)((Y0+0.5f)*disp.height()/(_height+d)),
+              x1 = (int)((X1+0.5f)*disp.width()/(_width+d)),
+              y1 = (int)((Y1+0.5f)*disp.height()/(_height+d));
+            visu.draw_arrow(x0,y0,x1,y1,foreground_color,0.6f,30,5,hatch);
+            if (d) {
+              const int
+                zx0 = (int)((_width+Z0+0.5f)*disp.width()/(_width+d)),
+                zx1 = (int)((_width+Z1+0.5f)*disp.width()/(_width+d)),
+                zy0 = (int)((_height+Z0+0.5f)*disp.height()/(_height+d)),
+                zy1 = (int)((_height+Z1+0.5f)*disp.height()/(_height+d));
+              visu.draw_arrow(zx0,y0,zx1,y1,foreground_color,0.6f,30,5,hatch).
+                draw_arrow(x0,zy0,x1,zy1,foreground_color,0.6f,30,5,hatch);
+            }
+          } break;
+          case 2 : {
+            const int
+              x0 = (X0<X1?X0:X1)*disp.width()/(_width+d),
+              y0 = (Y0<Y1?Y0:Y1)*disp.height()/(_height+d),
+              x1 = ((X0<X1?X1:X0)+1)*disp.width()/(_width+d)-1,
+              y1 = ((Y0<Y1?Y1:Y0)+1)*disp.height()/(_height+d)-1;
+            visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.2f).draw_rectangle(x0,y0,x1,y1,foreground_color,0.6f,hatch);
+            if (d) {
+              const int
+                zx0 = (int)((_width+(Z0<Z1?Z0:Z1))*disp.width()/(_width+d)),
+                zy0 = (int)((_height+(Z0<Z1?Z0:Z1))*disp.height()/(_height+d)),
+                zx1 = (int)((_width+(Z0<Z1?Z1:Z0)+1)*disp.width()/(_width+d))-1,
+                zy1 = (int)((_height+(Z0<Z1?Z1:Z0)+1)*disp.height()/(_height+d))-1;
+              visu.draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.2f).draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.6f,hatch);
+              visu.draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.2f).draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.6f,hatch);
+            }
+          } break;
+          case 3 : {
+            const int
+              x0 = X0*disp.width()/(_width+d),
+              y0 = Y0*disp.height()/(_height+d),
+              x1 = X1*disp.width()/(_width+d)-1,
+              y1 = Y1*disp.height()/(_height+d)-1;
+            visu.draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),0,foreground_color,0.2f).
+              draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),0,foreground_color,0.6f,hatch);
+            if (d) {
+              const int
+                zx0 = (int)((_width+Z0)*disp.width()/(_width+d)),
+                zy0 = (int)((_height+Z0)*disp.height()/(_height+d)),
+                zx1 = (int)((_width+Z1+1)*disp.width()/(_width+d))-1,
+                zy1 = (int)((_height+Z1+1)*disp.height()/(_height+d))-1;
+              visu.draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),0,foreground_color,0.2f).
+                draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),0,foreground_color,0.6f,hatch).
+                draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),0,foreground_color,0.2f).
+                draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),0,foreground_color,0.6f,hatch);
+            }
+          } break;
+          } else {
+            const int
+              x0 = X*disp.width()/(_width+d),
+              y0 = Y*disp.height()/(_height+d),
+              x1 = (X+1)*disp.width()/(_width+d)-1,
+              y1 = (Y+1)*disp.height()/(_height+d)-1;
+            if (x1-x0>=4 && y1-y0>=4) visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.4f,~0U);
+          }
+
+          if (my>=0 && my<13) text_down = true; else if (my>=visu.height()-13) text_down = false;
+          if (!coords_type || !phase) {
+            if (X>=0 && Y>=0 && Z>=0 && X<width() && Y<height() && Z<depth()) {
+              if (_depth>1) cimg_snprintf(text,sizeof(text)," Point (%d,%d,%d) = [ ",origX+X,origY+Y,origZ+Z);
+              else cimg_snprintf(text,sizeof(text)," Point (%d,%d) = [ ",origX+X,origY+Y);
+              char *ctext = text + std::strlen(text), *const ltext = text + 512;
+              for (unsigned int c = 0; c<_spectrum && ctext<ltext; ++c) {
+                cimg_snprintf(ctext,sizeof(text)/2,cimg::type<T>::format(),cimg::type<T>::format((*this)(X,Y,Z,c)));
+                ctext = text + std::strlen(text);
+                *(ctext++) = ' '; *ctext = 0;
+              }
+              std::strcpy(text + std::strlen(text),"] ");
+            }
+          } else switch (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) cimg_snprintf(text,sizeof(text)," Vect (%d,%d,%d)-(%d,%d,%d), Norm = %g ",
+                                        origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,norm);
+            else cimg_snprintf(text,sizeof(text)," Vect (%d,%d)-(%d,%d), Norm = %g ",
+                               origX+X0,origY+Y0,origX+X1,origY+Y1,norm);
+          } break;
+          case 2 :
+            if (_depth>1) cimg_snprintf(text,sizeof(text)," Box (%d,%d,%d)-(%d,%d,%d), Size = (%d,%d,%d) ",
+                                        origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origZ+(Z0<Z1?Z0:Z1),
+                                        origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),origZ+(Z0<Z1?Z1:Z0),
+                                        1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
+            else cimg_snprintf(text,sizeof(text)," Box (%d,%d)-(%d,%d), Size = (%d,%d) ",
+                               origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),
+                               1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
+            break;
+          default :
+            if (_depth>1) cimg_snprintf(text,sizeof(text)," Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d) ",
+                                        origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,
+                                        1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
+            else cimg_snprintf(text,sizeof(text)," Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d) ",
+                               origX+X0,origY+Y0,origX+X1,origY+Y1,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
+          }
+          if (phase || (mx>=0 && my>=0)) visu.draw_text(0,text_down?visu.height()-13:0,text,foreground_color,background_color,0.7f,13);
+          disp.display(visu).wait(25);
+        } else if (!shape_selected) disp.wait();
+        if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); }
+      }
+
+      // Return result
+      CImg<intT> res(1,6,1,1,-1);
+      if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; }
+      if (shape_selected) {
+        if (coords_type==2) {
+          if (X0>X1) cimg::swap(X0,X1);
+          if (Y0>Y1) cimg::swap(Y0,Y1);
+          if (Z0>Z1) cimg::swap(Z0,Z1);
+        }
+        if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1;
+        switch (coords_type) {
+        case 1 : case 2 : res[3] = X1; res[4] = Y1; res[5] = Z1;
+        default : res[0] = X0; res[1] = Y0; res[2] = Z0;
+        }
+      }
+      disp.set_button();
+      disp._normalization = old_normalization;
+      disp._is_resized = old_is_resized;
+      if (key!=~0U) disp.set_key(key);
+      return res;
+    }
+
+    //! Select sub-graph in a graph.
+    CImg<intT> get_select_graph(CImgDisplay &disp,
+                                const unsigned int plot_type=1, const unsigned int vertex_type=1,
+                                const char *const labelx=0, const double xmin=0, const double xmax=0,
+                                const char *const labely=0, const double ymin=0, const double ymax=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "select_graph() : Empty instance.",
+                                    cimg_instance);
+      const unsigned int siz = _width*_height*_depth, onormalization = disp.normalization();
+      if (!disp) { char ntitle[64] = { 0 }; cimg_snprintf(ntitle,sizeof(ntitle),"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
+      (disp.show().set_button().set_wheel())._normalization = 0;
+      double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax;
+      if (nymin==nymax) nymin = (Tfloat)min_max(nymax);
+      if (nymin==nymax) { --nymin; ++nymax; }
+      if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.0; }
+
+      const unsigned char black[] = { 0, 0, 0 }, white[] = { 255, 255, 255 }, gray[] = { 220, 220, 220 };
+      const unsigned char gray2[] = { 110, 110, 110 }, ngray[] = { 35, 35, 35 };
+      static unsigned int odimv = 0;
+      static CImg<ucharT> palette;
+      if (odimv!=_spectrum) {
+        odimv = _spectrum;
+        palette = CImg<ucharT>(3,_spectrum,1,1,120).noise(70,1);
+        if (_spectrum==1) { palette[0] = palette[1] = 120; palette[2] = 200; }
+        else {
+          palette(0,0) = 220; palette(1,0) = 10; palette(2,0) = 10;
+          if (_spectrum>1) { palette(0,1) = 10; palette(1,1) = 220; palette(2,1) = 10; }
+          if (_spectrum>2) { palette(0,2) = 10; palette(1,2) = 10; palette(2,2) = 220; }
+        }
+      }
+
+      CImg<ucharT> visu0, visu, graph, text, axes;
+      const unsigned int whd = _width*_height*_depth;
+      int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2;
+      char message[1024] = { 0 };
+      unsigned int okey = 0, obutton = 0;
+      CImg_3x3(I,unsigned char);
+
+      for (bool selected = false; !selected && !disp.is_closed() && !okey && !disp.wheel(); ) {
+        const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y();
+        const unsigned int key = disp.key(), button = disp.button();
+
+        // Generate graph representation.
+        if (!visu0) {
+          visu0.assign(disp.width(),disp.height(),1,3,220);
+          const int gdimx = disp.width() - 32, gdimy = disp.height() - 32;
+          if (gdimx>0 && gdimy>0) {
+            graph.assign(gdimx,gdimy,1,3,255);
+            graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333);
+            cimg_forC(*this,c) graph.draw_graph(get_shared_channel(c),&palette(0,c),(plot_type!=3 || _spectrum==1)?1:0.6f,
+                                                plot_type,vertex_type,nymax,nymin,false);
+
+            axes.assign(gdimx,gdimy,1,1,0);
+            const float
+              dx = (float)cimg::abs(nxmax-nxmin), dy = (float)cimg::abs(nymax-nymin),
+              px = (float)std::pow(10.0,(int)std::log10(dx)-2.0),
+              py = (float)std::pow(10.0,(int)std::log10(dy)-2.0);
+            const CImg<Tdouble>
+              seqx = CImg<Tdouble>::sequence(1 + gdimx/60,nxmin,nxmax).round(px),
+              seqy = CImg<Tdouble>::sequence(1 + gdimy/60,nymax,nymin).round(py);
+            axes.draw_axes(seqx,seqy,white);
+            if (nymin>0) axes.draw_axis(seqx,gdimy-1,gray);
+            if (nymax<0) axes.draw_axis(seqx,0,gray);
+            if (nxmin>0) axes.draw_axis(0,seqy,gray);
+            if (nxmax<0) axes.draw_axis(gdimx-1,seqy,gray);
+
+            cimg_for3x3(axes,x,y,0,0,I,unsigned char)
+              if (Icc) {
+                if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0;
+                else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3);
+              }
+              else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) cimg_forC(graph,c) graph(x,y,c) = (graph(x,y,c)+255)/2;
+
+            visu0.draw_image(16,16,graph);
+            visu0.draw_line(15,15,16+gdimx,15,gray2).draw_line(16+gdimx,15,16+gdimx,16+gdimy,gray2).
+              draw_line(16+gdimx,16+gdimy,15,16+gdimy,white).draw_line(15,16+gdimy,15,15,white);
+          } else graph.assign();
+          text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1,13).resize(-100,-100,1,3);
+          visu0.draw_image((visu0.width()-text.width())/2,visu0.height()-14,~text);
+          text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3);
+          visu0.draw_image(2,(visu0.height()-text.height())/2,~text);
+          visu.assign();
+        }
+
+        // Generate and display current view.
+        if (!visu) {
+          visu.assign(visu0);
+          if (graph && x0>=0 && x1>=0) {
+            const int
+              nx0 = x0<=x1?x0:x1,
+              nx1 = x0<=x1?x1:x0,
+              ny0 = y0<=y1?y0:y1,
+              ny1 = y0<=y1?y1:y0,
+              sx0 = 16 + nx0*(visu.width()-32)/whd,
+              sx1 = 15 + (nx1+1)*(visu.width()-32)/whd,
+              sy0 = 16 + ny0,
+              sy1 = 16 + ny1;
+
+            if (y0>=0 && y1>=0)
+              visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU);
+            else visu.draw_rectangle(sx0,0,sx1,visu.height()-17,gray,0.5f).
+                   draw_line(sx0,16,sx0,visu.height()-17,black,0.5f,0xCCCCCCCCU).
+                   draw_line(sx1,16,sx1,visu.height()-17,black,0.5f,0xCCCCCCCCU);
+          }
+          if (mouse_x>=16 && mouse_y>=16 && mouse_x<visu.width()-16 && mouse_y<visu.height()-16) {
+            if (graph) visu.draw_line(mouse_x,16,mouse_x,visu.height()-17,black,0.5f,0x55555555U);
+            const unsigned x = (mouse_x-16)*whd/(disp.width()-32);
+            const double cx = nxmin + x*(nxmax-nxmin)/whd;
+            if (_spectrum>=7)
+              cimg_snprintf(message,sizeof(message),"Value[%g] = ( %g %g %g ... %g %g %g )",cx,
+                            (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2),
+                            (double)(*this)(x,0,0,_spectrum-4),(double)(*this)(x,0,0,_spectrum-3),(double)(*this)(x,0,0,_spectrum-1));
+            else {
+              cimg_snprintf(message,sizeof(message),"Value[%g] = ( ",cx);
+              cimg_forC(*this,c) std::sprintf(message + std::strlen(message),"%g ",(double)(*this)(x,0,0,c));
+              std::sprintf(message + std::strlen(message),")");
+            }
+            if (x0>=0 && x1>=0) {
+              const int
+                 nx0 = x0<=x1?x0:x1,
+                 nx1 = x0<=x1?x1:x0,
+                 ny0 = y0<=y1?y0:y1,
+                 ny1 = y0<=y1?y1:y0;
+              const double
+                 cx0 = nxmin + nx0*(nxmax-nxmin)/(visu.width()-32),
+                 cx1 = nxmin + nx1*(nxmax-nxmin)/(visu.width()-32),
+                 cy0 = nymax - ny0*(nymax-nymin)/(visu.height()-32),
+                 cy1 = nymax - ny1*(nymax-nymin)/(visu.height()-32);
+              if (y0>=0 && y1>=0)
+                std::sprintf(message + std::strlen(message)," - Range ( %g, %g ) - ( %g, %g )",cx0,cy0,cx1,cy1);
+              else
+                std::sprintf(message + std::strlen(message)," - Range [ %g - %g ]",cx0,cx1);
+            }
+            text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3);
+            visu.draw_image((visu.width()-text.width())/2,1,~text);
+          }
+          visu.display(disp);
+        }
+
+        // Test keys.
+        switch (okey = key) {
+#if cimg_OS!=2
+        case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT :
+#endif
+        case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break;
+        case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
+                                            CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false).
+            _is_resized = true;
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.set_fullscreen(false).resize(cimg_fitscreen(640,480,1),false)._is_resized = true;
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+          static unsigned int snap_number = 0;
+          if (visu || visu0) {
+            CImg<ucharT> &screen = visu?visu:visu0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.bmp",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+            } while (file);
+            (+screen).draw_text(0,0," Saving snapshot... ",black,gray,1,13).display(disp);
+            screen.save(filename);
+            screen.draw_text(0,0," Snapshot '%s' saved. ",black,gray,1,13,filename).display(disp);
+          }
+          disp.set_key(key,false); okey = 0;
+        } break;
+        case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            static unsigned int snap_number = 0;
+            if (visu || visu0) {
+              CImg<ucharT> &screen = visu?visu:visu0;
+              char filename[32] = { 0 };
+              std::FILE *file;
+              do {
+#ifdef cimg_use_zlib
+                cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.cimgz",snap_number++);
+#else
+                cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.cimg",snap_number++);
+#endif
+                if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+              } while (file);
+              (+screen).draw_text(0,0," Saving instance... ",black,gray,1,13).display(disp);
+              save(filename);
+              screen.draw_text(0,0," Instance '%s' saved. ",black,gray,1,13,filename).display(disp);
+            }
+            disp.set_key(key,false); okey = 0;
+          } break;
+        }
+
+        // Handle mouse motion and mouse buttons
+        if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) {
+          visu.assign();
+          if (disp.mouse_x()>=0 && disp.mouse_y()>=0) {
+            const int
+              mx = (mouse_x-16)*(int)whd/(disp.width()-32),
+              cx = mx<0?0:(mx>=(int)whd?whd-1:mx),
+              my = mouse_y-16,
+              cy = my<=0?0:(my>=(disp.height()-32)?(disp.height()-32):my);
+            if (button&1) {
+              if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; }
+            }
+            else if (button&2) {
+              if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; }
+            }
+            else if (obutton) { x1 = cx; y1 = y1>=0?cy:-1; selected = true; }
+          } else if (!button && obutton) selected = true;
+          obutton = button; omouse_x = mouse_x; omouse_y = mouse_y;
+        }
+        if (disp.is_resized()) { disp.resize(false); visu0.assign(); }
+        if (visu && visu0) disp.wait();
+      }
+      disp._normalization = onormalization;
+      if (x1<x0) cimg::swap(x0,x1);
+      if (y1<y0) cimg::swap(y0,y1);
+      disp.set_key(okey);
+      return CImg<intT>(4,1,1,1,x0,y0,x1,y1);
+    }
+
+    //! Load an image from a file.
+    /**
+       \param filename is the name of the image file to load.
+       \note The extension of \c filename defines the file format. If no filename
+       extension is provided, CImg<T>::get_load() will try to load a .cimg file.
+    **/
+    CImg<T>& load(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load() : Specified filename is (null).",
+                                    cimg_instance);
+
+      const char *const ext = cimg::split_filename(filename);
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+#ifdef cimg_load_plugin
+        cimg_load_plugin(filename);
+#endif
+#ifdef cimg_load_plugin1
+        cimg_load_plugin1(filename);
+#endif
+#ifdef cimg_load_plugin2
+        cimg_load_plugin2(filename);
+#endif
+#ifdef cimg_load_plugin3
+        cimg_load_plugin3(filename);
+#endif
+#ifdef cimg_load_plugin4
+        cimg_load_plugin4(filename);
+#endif
+#ifdef cimg_load_plugin5
+        cimg_load_plugin5(filename);
+#endif
+#ifdef cimg_load_plugin6
+        cimg_load_plugin6(filename);
+#endif
+#ifdef cimg_load_plugin7
+        cimg_load_plugin7(filename);
+#endif
+#ifdef cimg_load_plugin8
+        cimg_load_plugin8(filename);
+#endif
+        // ASCII formats
+        if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename);
+        else if (!cimg::strcasecmp(ext,"dlm") ||
+                 !cimg::strcasecmp(ext,"txt")) load_dlm(filename);
+
+        // 2d binary formats
+        else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename);
+        else if (!cimg::strcasecmp(ext,"jpg") ||
+                 !cimg::strcasecmp(ext,"jpeg") ||
+                 !cimg::strcasecmp(ext,"jpe") ||
+                 !cimg::strcasecmp(ext,"jfif") ||
+                 !cimg::strcasecmp(ext,"jif")) load_jpeg(filename);
+        else if (!cimg::strcasecmp(ext,"png")) load_png(filename);
+        else if (!cimg::strcasecmp(ext,"ppm") ||
+                 !cimg::strcasecmp(ext,"pgm") ||
+                 !cimg::strcasecmp(ext,"pnm")) load_pnm(filename);
+        else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename);
+        else if (!cimg::strcasecmp(ext,"tif") ||
+                 !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+        else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename);
+        else if (!cimg::strcasecmp(ext,"cr2") ||
+                 !cimg::strcasecmp(ext,"crw") ||
+                 !cimg::strcasecmp(ext,"dcr") ||
+                 !cimg::strcasecmp(ext,"mrw") ||
+                 !cimg::strcasecmp(ext,"nef") ||
+                 !cimg::strcasecmp(ext,"orf") ||
+                 !cimg::strcasecmp(ext,"pix") ||
+                 !cimg::strcasecmp(ext,"ptx") ||
+                 !cimg::strcasecmp(ext,"raf") ||
+                 !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename);
+
+        // 3d binary formats
+        else if (!cimg::strcasecmp(ext,"dcm") ||
+                 !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename);
+        else if (!cimg::strcasecmp(ext,"hdr") ||
+                 !cimg::strcasecmp(ext,"nii")) load_analyze(filename);
+        else if (!cimg::strcasecmp(ext,"par") ||
+                 !cimg::strcasecmp(ext,"rec")) load_parrec(filename);
+        else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename);
+        else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename);
+        else if (!cimg::strcasecmp(ext,"cimg") ||
+                 !cimg::strcasecmp(ext,"cimgz") ||
+                 !*ext)  return load_cimg(filename);
+
+        // Archive files
+        else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+
+        // Image sequences
+        else if (!cimg::strcasecmp(ext,"avi") ||
+                 !cimg::strcasecmp(ext,"mov") ||
+                 !cimg::strcasecmp(ext,"asf") ||
+                 !cimg::strcasecmp(ext,"divx") ||
+                 !cimg::strcasecmp(ext,"flv") ||
+                 !cimg::strcasecmp(ext,"mpg") ||
+                 !cimg::strcasecmp(ext,"m1v") ||
+                 !cimg::strcasecmp(ext,"m2v") ||
+                 !cimg::strcasecmp(ext,"m4v") ||
+                 !cimg::strcasecmp(ext,"mjp") ||
+                 !cimg::strcasecmp(ext,"mkv") ||
+                 !cimg::strcasecmp(ext,"mpe") ||
+                 !cimg::strcasecmp(ext,"movie") ||
+                 !cimg::strcasecmp(ext,"ogm") ||
+                 !cimg::strcasecmp(ext,"ogg") ||
+                 !cimg::strcasecmp(ext,"qt") ||
+                 !cimg::strcasecmp(ext,"rm") ||
+                 !cimg::strcasecmp(ext,"vob") ||
+                 !cimg::strcasecmp(ext,"wmv") ||
+                 !cimg::strcasecmp(ext,"xvid") ||
+                 !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
+        else throw CImgIOException("CImg<%s>::load()",
+                                   pixel_type());
+      } catch (CImgException& e) {
+        if (!cimg::strncasecmp(e.what(),"cimg::fopen()",13)) {
+          cimg::exception_mode() = omode;
+          throw CImgIOException(_cimg_instance
+                                "load() : Failed to open file '%s'.",
+                                cimg_instance,
+                                filename);
+        } else try {
+          const char *const f_type = cimg::file_type(0,filename);
+          if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename);
+          else if (!cimg::strcasecmp(f_type,"pfm")) load_pfm(filename);
+          else if (!cimg::strcasecmp(f_type,"bmp")) load_bmp(filename);
+          else if (!cimg::strcasecmp(f_type,"jpg")) load_jpeg(filename);
+          else if (!cimg::strcasecmp(f_type,"pan")) load_pandore(filename);
+          else if (!cimg::strcasecmp(f_type,"png")) load_png(filename);
+          else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename);
+          else if (!cimg::strcasecmp(f_type,"inr")) load_inr(filename);
+          else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename);
+          else throw CImgIOException("CImg<%s>::load()",
+                                     pixel_type());
+        } catch (CImgException&) {
+          try {
+            load_other(filename);
+          } catch (CImgException&) {
+            throw CImgIOException(_cimg_instance
+                                  "load() : Failed to recognize format of file '%s'.",
+                                  cimg_instance,
+                                  filename);
+          }
+        }
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    static CImg<T> get_load(const char *const filename) {
+      return CImg<T>().load(filename);
+    }
+
+    //! Load an image from an ASCII file.
+    CImg<T>& load_ascii(const char *const filename) {
+      return _load_ascii(0,filename);
+    }
+
+    static CImg<T> get_load_ascii(const char *const filename) {
+      return CImg<T>().load_ascii(filename);
+    }
+
+    //! Load an image from an ASCII file.
+    CImg<T>& load_ascii(std::FILE *const file) {
+      return _load_ascii(file,0);
+    }
+
+    static CImg<T> get_load_ascii(std::FILE *const file) {
+      return CImg<T>().load_ascii(file);
+    }
+
+    CImg<T>& _load_ascii(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_ascii() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      char line[256] = { 0 };
+      int err = std::fscanf(nfile,"%255[^\n]",line);
+      unsigned int off, dx = 0, dy = 1, dz = 1, dc = 1;
+      std::sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc);
+      err = std::fscanf(nfile,"%*[^0-9.eE+-]");
+      if (!dx || !dy || !dz || !dc) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_ascii() : Invalid ASCII header in file '%s', image dimensions are set to (%u,%u,%u,%u).",
+                              cimg_instance,
+                              filename?filename:"(FILE*)",dx,dy,dz,dc);
+      }
+      assign(dx,dy,dz,dc);
+      const unsigned int siz = size();
+      double val;
+      T *ptr = _data;
+      for (err = 1, off = 0; off<siz && err==1; ++off) {
+        err = std::fscanf(nfile,"%lf%*[^0-9.eE+-]",&val);
+        *(ptr++) = (T)val;
+      }
+      if (err!=1)
+        cimg::warn(_cimg_instance
+                   "load_ascii() : Only %u/%u values read from file '%s'.",
+                   cimg_instance,
+                   off-1,siz,filename?filename:"(FILE*)");
+
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a DLM file.
+    CImg<T>& load_dlm(const char *const filename) {
+      return _load_dlm(0,filename);
+    }
+
+    static CImg<T> get_load_dlm(const char *const filename) {
+      return CImg<T>().load_dlm(filename);
+    }
+
+    //! Load an image from a DLM file.
+    CImg<T>& load_dlm(std::FILE *const file) {
+      return _load_dlm(file,0);
+    }
+
+    static CImg<T> get_load_dlm(std::FILE *const file) {
+      return CImg<T>().load_dlm(file);
+    }
+
+    CImg<T>& _load_dlm(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_dlm() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
+      char delimiter[256] = { 0 }, tmp[256] = { 0 };
+      unsigned int cdx = 0, dx = 0, dy = 0;
+      int err = 0;
+      double val;
+      assign(256,256);
+      while ((err = std::fscanf(nfile,"%lf%255[^0-9.+-]",&val,delimiter))>0) {
+        if (err>0) (*this)(cdx++,dy) = (T)val;
+        if (cdx>=_width) resize(3*_width/2,_height,1,1,0);
+        char c = 0;
+        if (!std::sscanf(delimiter,"%255[^\n]%c",tmp,&c) || c=='\n') {
+          dx = cimg::max(cdx,dx);
+          if (++dy>=_height) resize(_width,3*_height/2,1,1,0);
+          cdx = 0;
+        }
+      }
+      if (cdx && err==1) { dx = cdx; ++dy; }
+      if (!dx || !dy) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_dlm() : Invalid DLM file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      resize(dx,dy,1,1,0);
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a BMP file.
+    CImg<T>& load_bmp(const char *const filename) {
+      return _load_bmp(0,filename);
+    }
+
+    static CImg<T> get_load_bmp(const char *const filename) {
+      return CImg<T>().load_bmp(filename);
+    }
+
+    //! Load an image from a BMP file.
+    CImg<T>& load_bmp(std::FILE *const file) {
+      return _load_bmp(file,0);
+    }
+
+    static CImg<T> get_load_bmp(std::FILE *const file) {
+      return CImg<T>().load_bmp(file);
+    }
+
+    CImg<T>& _load_bmp(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_bmp() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      unsigned char header[64] = { 0 };
+      cimg::fread(header,54,nfile);
+      if (*header!='B' || header[1]!='M') {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_bmp() : Invalid BMP file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+
+      // Read header and pixel buffer
+      int
+        file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24),
+        offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24),
+        dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24),
+        dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24),
+        compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24),
+        nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24),
+        bpp = header[0x1C] + (header[0x1D]<<8);
+      const int
+        cimg_iobuffer = 12*1024*1024,
+        dx_bytes = (bpp==1)?(dx/8+(dx%8?1:0)):((bpp==4)?(dx/2+(dx%2?1:0)):(dx*bpp/8)),
+        align_bytes = (4-dx_bytes%4)%4,
+        buf_size = cimg::min(cimg::abs(dy)*(dx_bytes+align_bytes),file_size-offset);
+
+      CImg<intT> palette;
+      if (bpp<16) { if (!nb_colors) nb_colors = 1<<bpp; } else nb_colors = 0;
+      if (nb_colors) { palette.assign(nb_colors); cimg::fread(palette._data,nb_colors,nfile); }
+      const int xoffset = offset - 54 - 4*nb_colors;
+      if (xoffset>0) std::fseek(nfile,xoffset,SEEK_CUR);
+
+      CImg<ucharT> buffer;
+      if (buf_size<cimg_iobuffer) { buffer.assign(buf_size); cimg::fread(buffer._data,buf_size,nfile); }
+      else buffer.assign(dx_bytes + align_bytes);
+      unsigned char *ptrs = buffer;
+
+      // Decompress buffer (if necessary)
+      if (compression) {
+        if (file)
+          throw CImgIOException(_cimg_instance
+                                "load_bmp() : Unable to load compressed data from '(*FILE)' inputs.",
+                                cimg_instance);
+        else return load_other(filename);
+      }
+
+      // Read pixel data
+      assign(dx,cimg::abs(dy),1,3);
+      switch (bpp) {
+      case 1 : { // Monochrome
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          unsigned char mask = 0x80, val = 0;
+          cimg_forX(*this,x) {
+            if (mask==0x80) val = *(ptrs++);
+            const unsigned char *col = (unsigned char*)(palette._data + (val&mask?1:0));
+            (*this)(x,y,2) = (T)*(col++);
+            (*this)(x,y,1) = (T)*(col++);
+            (*this)(x,y,0) = (T)*(col++);
+            mask = cimg::ror(mask);
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      case 4 : { // 16 colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          unsigned char mask = 0xF0, val = 0;
+          cimg_forX(*this,x) {
+            if (mask==0xF0) val = *(ptrs++);
+            const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4));
+            const unsigned char *col = (unsigned char*)(palette._data + color);
+            (*this)(x,y,2) = (T)*(col++);
+            (*this)(x,y,1) = (T)*(col++);
+            (*this)(x,y,0) = (T)*(col++);
+            mask = cimg::ror(mask,4);
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      case 8 : { //  256 colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          cimg_forX(*this,x) {
+            const unsigned char *col = (unsigned char*)(palette._data + *(ptrs++));
+            (*this)(x,y,2) = (T)*(col++);
+            (*this)(x,y,1) = (T)*(col++);
+            (*this)(x,y,0) = (T)*(col++);
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      case 16 : { // 16 bits colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          cimg_forX(*this,x) {
+            const unsigned char c1 = *(ptrs++), c2 = *(ptrs++);
+            const unsigned short col = (unsigned short)(c1|(c2<<8));
+            (*this)(x,y,2) = (T)(col&0x1F);
+            (*this)(x,y,1) = (T)((col>>5)&0x1F);
+            (*this)(x,y,0) = (T)((col>>10)&0x1F);
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      case 24 : { // 24 bits colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          cimg_forX(*this,x) {
+            (*this)(x,y,2) = (T)*(ptrs++);
+            (*this)(x,y,1) = (T)*(ptrs++);
+            (*this)(x,y,0) = (T)*(ptrs++);
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      case 32 : { // 32 bits colors
+        for (int y = height()-1; y>=0; --y) {
+          if (buf_size>=cimg_iobuffer) { cimg::fread(ptrs=buffer._data,dx_bytes,nfile); std::fseek(nfile,align_bytes,SEEK_CUR); }
+          cimg_forX(*this,x) {
+            (*this)(x,y,2) = (T)*(ptrs++);
+            (*this)(x,y,1) = (T)*(ptrs++);
+            (*this)(x,y,0) = (T)*(ptrs++);
+            ++ptrs;
+          }
+          ptrs+=align_bytes;
+        }
+      } break;
+      }
+      if (dy<0) mirror('y');
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a JPEG file.
+    CImg<T>& load_jpeg(const char *const filename) {
+      return _load_jpeg(0,filename);
+    }
+
+    static CImg<T> get_load_jpeg(const char *const filename) {
+      return CImg<T>().load_jpeg(filename);
+    }
+
+    //! Load an image from a JPEG file.
+    CImg<T>& load_jpeg(std::FILE *const file) {
+      return _load_jpeg(file,0);
+    }
+
+    static CImg<T> get_load_jpeg(std::FILE *const file) {
+      return CImg<T>().load_jpeg(file);
+    }
+
+    // Custom error handler for libjpeg.
+#ifdef cimg_use_jpeg
+    struct _cimg_error_mgr {
+      struct jpeg_error_mgr original;
+      jmp_buf setjmp_buffer;
+      char message[JMSG_LENGTH_MAX];
+    };
+
+    typedef struct _cimg_error_mgr *_cimg_error_ptr;
+
+    METHODDEF(void) _cimg_jpeg_error_exit(j_common_ptr cinfo) {
+      _cimg_error_ptr c_err = (_cimg_error_ptr) cinfo->err;  // Return control to the setjmp point
+      (*cinfo->err->format_message)(cinfo,c_err->message);
+      jpeg_destroy(cinfo);  // Clean memory and temp files.
+      longjmp(c_err->setjmp_buffer,1);
+    }
+#endif
+
+    CImg<T>& _load_jpeg(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_jpeg() : Specified filename is (null).",
+                                    cimg_instance);
+
+#ifndef cimg_use_jpeg
+      if (file)
+        throw CImgIOException(_cimg_instance
+                              "load_jpeg() : Unable to load data from '(FILE*)' unless libjpeg is enabled.",
+                              cimg_instance);
+      else return load_other(filename);
+#else
+
+      struct jpeg_decompress_struct cinfo;
+      struct _cimg_error_mgr jerr;
+      cinfo.err = jpeg_std_error(&jerr.original);
+      jerr.original.error_exit = _cimg_jpeg_error_exit;
+
+      if (setjmp(jerr.setjmp_buffer)) { // JPEG error
+        throw CImgIOException(_cimg_instance
+                             "load_jpeg() : Error message returned by libjpeg : %s.",
+                             cimg_instance,jerr.message);
+      }
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      jpeg_create_decompress(&cinfo);
+      jpeg_stdio_src(&cinfo,nfile);
+      jpeg_read_header(&cinfo,TRUE);
+      jpeg_start_decompress(&cinfo);
+
+      if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) {
+        if (!file) return load_other(filename);
+        else
+          throw CImgIOException(_cimg_instance
+                                "load_jpeg() : Failed to load JPEG data from file '%s'.",
+                                cimg_instance,filename?filename:"(FILE*)");
+      }
+      CImg<ucharT> buffer(cinfo.output_width*cinfo.output_components);
+      JSAMPROW row_pointer[1];
+      assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components);
+      T *ptr_r = _data, *ptr_g = _data + _width*_height, *ptr_b = _data + 2*_width*_height, *ptr_a = _data + 3*_width*_height;
+      while (cinfo.output_scanline<cinfo.output_height) {
+        *row_pointer = buffer._data;
+        if (jpeg_read_scanlines(&cinfo,row_pointer,1)!=1) {
+          cimg::warn(_cimg_instance
+                     "load_jpeg() : Incomplete data in file '%s'.",
+                     cimg_instance,filename?filename:"(FILE*)");
+          break;
+        }
+        const unsigned char *ptrs = buffer._data;
+        switch (_spectrum) {
+        case 1 : {
+          cimg_forX(*this,x) *(ptr_r++) = (T)*(ptrs++);
+        } break;
+        case 3 : {
+          cimg_forX(*this,x) {
+            *(ptr_r++) = (T)*(ptrs++);
+            *(ptr_g++) = (T)*(ptrs++);
+            *(ptr_b++) = (T)*(ptrs++);
+          }
+        } break;
+        case 4 : {
+          cimg_forX(*this,x) {
+            *(ptr_r++) = (T)*(ptrs++);
+            *(ptr_g++) = (T)*(ptrs++);
+            *(ptr_b++) = (T)*(ptrs++);
+            *(ptr_a++) = (T)*(ptrs++);
+          }
+        } break;
+        }
+      }
+      jpeg_finish_decompress(&cinfo);
+      jpeg_destroy_decompress(&cinfo);
+      if (!file) cimg::fclose(nfile);
+      return *this;
+#endif
+    }
+
+    //! Load an image from a file, using Magick++ library.
+    // Added April/may 2006 by Christoph Hormann <chris_hormann at gmx.de>
+    //   This is experimental code, not much tested, use with care.
+    CImg<T>& load_magick(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_magick() : Specified filename is (null).",
+                                    cimg_instance);
+
+#ifdef cimg_use_magick
+      Magick::Image image(filename);
+      const unsigned int W = image.size().width(), H = image.size().height();
+      switch (image.type()) {
+      case Magick::PaletteMatteType :
+      case Magick::TrueColorMatteType :
+      case Magick::ColorSeparationType : {
+        assign(W,H,1,4);
+        T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          *(ptr_g++) = (T)(pixels->green);
+          *(ptr_b++) = (T)(pixels->blue);
+          *(ptr_a++) = (T)(pixels->opacity);
+          ++pixels;
+        }
+      } break;
+      case Magick::PaletteType :
+      case Magick::TrueColorType : {
+        assign(W,H,1,3);
+        T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          *(ptr_g++) = (T)(pixels->green);
+          *(ptr_b++) = (T)(pixels->blue);
+          ++pixels;
+        }
+      } break;
+      case Magick::GrayscaleMatteType : {
+        assign(W,H,1,2);
+        T *ptr_r = data(0,0,0,0), *ptr_a = data(0,0,0,1);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          *(ptr_a++) = (T)(pixels->opacity);
+          ++pixels;
+        }
+      } break;
+      default : {
+        assign(W,H,1,1);
+        T *ptr_r = data(0,0,0,0);
+        Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+        for (unsigned int off = W*H; off; --off) {
+          *(ptr_r++) = (T)(pixels->red);
+          ++pixels;
+        }
+      }
+      }
+#else
+      throw CImgIOException(_cimg_instance
+                            "load_magick() : Unable to load file '%s' unless libMagick++ is enabled.",
+                            cimg_instance,
+                            filename);
+#endif
+      return *this;
+    }
+
+    static CImg<T> get_load_magick(const char *const filename) {
+      return CImg<T>().load_magick(filename);
+    }
+
+    //! Load an image from a PNG file.
+    CImg<T>& load_png(const char *const filename) {
+      return _load_png(0,filename);
+    }
+
+    static CImg<T> get_load_png(const char *const filename) {
+      return CImg<T>().load_png(filename);
+    }
+
+    //! Load an image from a PNG file.
+    CImg<T>& load_png(std::FILE *const file) {
+      return _load_png(file,0);
+    }
+
+    static CImg<T> get_load_png(std::FILE *const file) {
+      return CImg<T>().load_png(file);
+    }
+
+    // (Note : Most of this function has been written by Eric Fausett)
+    CImg<T>& _load_png(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_png() : Specified filename is (null).",
+                                    cimg_instance);
+
+#ifndef cimg_use_png
+      if (file)
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Unable to load data from '(FILE*)' unless libpng is enabled.",
+                              cimg_instance);
+
+      else return load_other(filename);
+#else
+      // Open file and check for PNG validity
+      const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
+      std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb");
+
+      unsigned char pngCheck[8] = { 0 };
+      cimg::fread(pngCheck,8,(std::FILE*)nfile);
+      if (png_sig_cmp(pngCheck,0,8)) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Invalid PNG file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+
+      // Setup PNG structures for read
+      png_voidp user_error_ptr = 0;
+      png_error_ptr user_error_fn = 0, user_warning_fn = 0;
+      png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn);
+      if (!png_ptr) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Failed to initialize 'png_ptr' structure for file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_infop info_ptr = png_create_info_struct(png_ptr);
+      if (!info_ptr) {
+        if (!file) cimg::fclose(nfile);
+        png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Failed to initialize 'info_ptr' structure for file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_infop end_info = png_create_info_struct(png_ptr);
+      if (!end_info) {
+        if (!file) cimg::fclose(nfile);
+        png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Failed to initialize 'end_info' structure for file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+
+      // Error handling callback for png file reading
+      if (setjmp(png_jmpbuf(png_ptr))) {
+        if (!file) cimg::fclose((std::FILE*)nfile);
+        png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
+        throw CImgIOException(_cimg_instance
+                              "load_png() : Encountered unknown fatal error in libpng for file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_init_io(png_ptr, nfile);
+      png_set_sig_bytes(png_ptr, 8);
+
+      // Get PNG Header Info up to data block
+      png_read_info(png_ptr,info_ptr);
+      png_uint_32 W, H;
+      int bit_depth, color_type, interlace_type;
+      png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0);
+      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_instance
+                              "load_png() : Invalid bit depth %u in file '%s'.",
+                              cimg_instance,
+                              new_bit_depth,nfilename?nfilename:"(FILE*)");
+      }
+      const int byte_depth = new_bit_depth>>3;
+
+      // Allocate Memory for Image Read
+      png_bytep *const imgData = new png_bytep[H];
+      for (unsigned int row = 0; row<H; ++row) imgData[row] = new png_byte[byte_depth*4*W];
+      png_read_image(png_ptr,imgData);
+      png_read_end(png_ptr,end_info);
+
+      // Read pixel data
+      if (!(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_instance
+                              "load_png() : Invalid color coding type %u in file '%s'.",
+                              cimg_instance,
+                              new_color_type,nfilename?nfilename:"(FILE*)");
+      }
+      const bool no_alpha_channel = (new_color_type==PNG_COLOR_TYPE_RGB);
+      assign(W,H,1,no_alpha_channel?3:4);
+      T *ptr1 = data(0,0,0,0), *ptr2 = data(0,0,0,1), *ptr3 = data(0,0,0,2), *ptr4 = data(0,0,0,3);
+      switch (new_bit_depth) {
+      case 8 : {
+        cimg_forY(*this,y){
+          const unsigned char *ptrs = (unsigned char*)imgData[y];
+          cimg_forX(*this,x){
+            *(ptr1++) = (T)*(ptrs++);
+            *(ptr2++) = (T)*(ptrs++);
+            *(ptr3++) = (T)*(ptrs++);
+            if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
+          }
+        }
+      } break;
+      case 16 : {
+        cimg_forY(*this,y){
+          const unsigned short *ptrs = (unsigned short*)(imgData[y]);
+          if (!cimg::endianness()) cimg::invert_endianness(ptrs,4*_width);
+          cimg_forX(*this,x){
+            *(ptr1++) = (T)*(ptrs++);
+            *(ptr2++) = (T)*(ptrs++);
+            *(ptr3++) = (T)*(ptrs++);
+            if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
+          }
+        }
+      } break;
+      }
+      png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+
+      // Deallocate Image Read Memory
+      cimg_forY(*this,n) delete[] imgData[n];
+      delete[] imgData;
+      if (!file) cimg::fclose(nfile);
+      return *this;
+#endif
+    }
+
+    //! Load an image from a PNM file.
+    CImg<T>& load_pnm(const char *const filename) {
+      return _load_pnm(0,filename);
+    }
+
+    static CImg<T> get_load_pnm(const char *const filename) {
+      return CImg<T>().load_pnm(filename);
+    }
+
+    //! Load an image from a PNM file.
+    CImg<T>& load_pnm(std::FILE *const file) {
+      return _load_pnm(file,0);
+    }
+
+    static CImg<T> get_load_pnm(std::FILE *const file) {
+      return CImg<T>().load_pnm(file);
+    }
+
+    CImg<T>& _load_pnm(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_pnm() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      unsigned int ppm_type, W, H, 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=='#' || !err)) std::fgetc(nfile);
+      if (std::sscanf(item," P%u",&ppm_type)!=1) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pnm() : PNM header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+      if ((err=std::sscanf(item," %u %u %u",&W,&H,&colormax))<2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pnm() : WIDTH and HEIGHT fields undefined in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (err==2) {
+        while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+        if (std::sscanf(item,"%u",&colormax)!=1)
+          cimg::warn(_cimg_instance
+                     "load_pnm() : COLORMAX field is undefined in file '%s'.",
+                     cimg_instance,
+                     filename?filename:"(FILE*)");
+      }
+      std::fgetc(nfile);
+
+      switch (ppm_type) {
+      case 2 : { // Grey Ascii
+        assign(W,H,1,1);
+        T* ptr_r = _data;
+        cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptr_r++) = (T)rval; else break; }
+      } break;
+      case 3 : { // Color Ascii
+        assign(W,H,1,3);
+        T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+        cimg_forXY(*this,x,y) {
+          if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { *(ptr_r++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval; }
+          else break;
+        }
+      } break;
+      case 5 : { // Grey Binary
+        if (colormax<256) { // 8 bits
+          CImg<ucharT> raw;
+          assign(W,H,1,1);
+          T *ptrd = data(0,0,0,0);
+          for (int toread = (int)size(); toread>0; ) {
+            raw.assign(cimg::min(toread,cimg_iobuffer));
+            cimg::fread(raw._data,raw._width,nfile);
+            toread-=raw._width;
+            const unsigned char *ptrs = raw._data;
+            for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+          }
+        } else { // 16 bits
+          CImg<ushortT> raw;
+          assign(W,H,1,1);
+          T *ptrd = data(0,0,0,0);
+          for (int toread = (int)size(); toread>0; ) {
+            raw.assign(cimg::min(toread,cimg_iobuffer/2));
+            cimg::fread(raw._data,raw._width,nfile);
+            if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width);
+            toread-=raw._width;
+            const unsigned short *ptrs = raw._data;
+            for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+          }
+        }
+      } break;
+      case 6 : { // Color Binary
+        if (colormax<256) { // 8 bits
+          CImg<ucharT> raw;
+          assign(W,H,1,3);
+          T
+            *ptr_r = data(0,0,0,0),
+            *ptr_g = data(0,0,0,1),
+            *ptr_b = data(0,0,0,2);
+          for (int toread = (int)size(); toread>0; ) {
+            raw.assign(cimg::min(toread,cimg_iobuffer));
+            cimg::fread(raw._data,raw._width,nfile);
+            toread-=raw._width;
+            const unsigned char *ptrs = raw._data;
+            for (unsigned int off = raw._width/3; off; --off) {
+              *(ptr_r++) = (T)*(ptrs++);
+              *(ptr_g++) = (T)*(ptrs++);
+              *(ptr_b++) = (T)*(ptrs++);
+            }
+          }
+        } else { // 16 bits
+          CImg<ushortT> raw;
+          assign(W,H,1,3);
+          T
+            *ptr_r = data(0,0,0,0),
+            *ptr_g = data(0,0,0,1),
+            *ptr_b = data(0,0,0,2);
+          for (int toread = (int)size(); toread>0; ) {
+            raw.assign(cimg::min(toread,cimg_iobuffer/2));
+            cimg::fread(raw._data,raw._width,nfile);
+            if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width);
+            toread-=raw._width;
+            const unsigned short *ptrs = raw._data;
+            for (unsigned int off = raw._width/3; off; --off) {
+              *(ptr_r++) = (T)*(ptrs++);
+              *(ptr_g++) = (T)*(ptrs++);
+              *(ptr_b++) = (T)*(ptrs++);
+            }
+          }
+        }
+      } break;
+      default :
+        assign();
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pnm() : PNM type 'P%d' found, but type is not supported.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)",ppm_type);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a PFM file.
+    CImg<T>& load_pfm(const char *const filename) {
+      return _load_pfm(0,filename);
+    }
+
+    static CImg<T> get_load_pfm(const char *const filename) {
+      return CImg<T>().load_pfm(filename);
+    }
+
+    //! Load an image from a PFM file.
+    CImg<T>& load_pfm(std::FILE *const file) {
+      return _load_pfm(file,0);
+    }
+
+    static CImg<T> get_load_pfm(std::FILE *const file) {
+      return CImg<T>().load_pfm(file);
+    }
+
+    CImg<T>& _load_pfm(std::FILE *const file, const char *const filename) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_pfm() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      char pfm_type, item[1024] = { 0 };
+      int W = 0, H = 0, err = 0;
+      double scale = 0;
+      while ((err=std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+      if (std::sscanf(item," P%c",&pfm_type)!=1) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pfm() : PFM header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+      if ((err=std::sscanf(item," %d %d",&W,&H))<2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pfm() : WIDTH and HEIGHT fields are undefined in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (err==2) {
+        while ((err=std::fscanf(nfile," %1023[^\n]",item))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+        if (std::sscanf(item,"%lf",&scale)!=1)
+          cimg::warn(_cimg_instance
+                     "load_pfm() : SCALE field is undefined in file '%s'.",
+                     cimg_instance,
+                     filename?filename:"(FILE*)");
+      }
+      std::fgetc(nfile);
+      const bool is_color = (pfm_type=='F'), is_inverted = (scale>0)!=cimg::endianness();
+      if (is_color) {
+        assign(W,H,1,3,0);
+        CImg<floatT> buf(3*W);
+        T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+        cimg_forY(*this,y) {
+          cimg::fread(buf._data,3*W,nfile);
+          if (is_inverted) cimg::invert_endianness(buf._data,3*W);
+          const float *ptrs = buf._data;
+          cimg_forX(*this,x) {
+            *(ptr_r++) = (T)*(ptrs++);
+            *(ptr_g++) = (T)*(ptrs++);
+            *(ptr_b++) = (T)*(ptrs++);
+          }
+        }
+      } else {
+        assign(W,H,1,1,0);
+        CImg<floatT> buf(W);
+        T *ptrd = data(0,0,0,0);
+        cimg_forY(*this,y) {
+          cimg::fread(buf._data,W,nfile);
+          if (is_inverted) cimg::invert_endianness(buf._data,W);
+          const float *ptrs = buf._data;
+          cimg_forX(*this,x) *(ptrd++) = (T)*(ptrs++);
+        }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a RGB file.
+    CImg<T>& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+      return _load_rgb(0,filename,dimw,dimh);
+    }
+
+    static CImg<T> get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+      return CImg<T>().load_rgb(filename,dimw,dimh);
+    }
+
+    //! Load an image from a RGB file.
+    CImg<T>& load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+      return _load_rgb(file,0,dimw,dimh);
+    }
+
+    static CImg<T> get_load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+      return CImg<T>().load_rgb(file,dimw,dimh);
+    }
+
+    CImg<T>& _load_rgb(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_rgb() : Specified filename is (null).",
+                                    cimg_instance);
+
+      if (!dimw || !dimh) return assign();
+      const int cimg_iobuffer = 12*1024*1024;
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      CImg<ucharT> raw;
+      assign(dimw,dimh,1,3);
+      T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = data(0,0,0,1),
+        *ptr_b = data(0,0,0,2);
+      for (int toread = (int)size(); toread>0; ) {
+        raw.assign(cimg::min(toread,cimg_iobuffer));
+        cimg::fread(raw._data,raw._width,nfile);
+        toread-=raw._width;
+        const unsigned char *ptrs = raw._data;
+        for (unsigned int off = raw._width/3; off; --off) {
+          *(ptr_r++) = (T)*(ptrs++);
+          *(ptr_g++) = (T)*(ptrs++);
+          *(ptr_b++) = (T)*(ptrs++);
+        }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a RGBA file.
+    CImg<T>& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+      return _load_rgba(0,filename,dimw,dimh);
+    }
+
+    static CImg<T> get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+      return CImg<T>().load_rgba(filename,dimw,dimh);
+    }
+
+    //! Load an image from a RGBA file.
+    CImg<T>& load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+      return _load_rgba(file,0,dimw,dimh);
+    }
+
+    static CImg<T> get_load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+      return CImg<T>().load_rgba(file,dimw,dimh);
+    }
+
+    CImg<T>& _load_rgba(std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_rgba() : Specified filename is (null).",
+                                    cimg_instance);
+
+      if (!dimw || !dimh) return assign();
+      const int cimg_iobuffer = 12*1024*1024;
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      CImg<ucharT> raw;
+      assign(dimw,dimh,1,4);
+      T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = data(0,0,0,1),
+        *ptr_b = data(0,0,0,2),
+        *ptr_a = data(0,0,0,3);
+      for (int toread = (int)size(); toread>0; ) {
+        raw.assign(cimg::min(toread,cimg_iobuffer));
+        cimg::fread(raw._data,raw._width,nfile);
+        toread-=raw._width;
+        const unsigned char *ptrs = raw._data;
+        for (unsigned int off = raw._width/4; off; --off) {
+          *(ptr_r++) = (T)*(ptrs++);
+          *(ptr_g++) = (T)*(ptrs++);
+          *(ptr_b++) = (T)*(ptrs++);
+          *(ptr_a++) = (T)*(ptrs++);
+        }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a TIFF file.
+    CImg<T>& load_tiff(const char *const filename,
+                       const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                       const unsigned int step_frame=1) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_tiff() : Specified filename is (null).",
+                                    cimg_instance);
+
+      const unsigned int
+        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+        nstep_frame = step_frame?step_frame:1;
+      unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
+
+#ifndef cimg_use_tiff
+      if (nfirst_frame || nlast_frame!=~0U || nstep_frame>1)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_tiff() : Unable to read sub-images from file '%s' unless libtiff is enabled.",
+                                    cimg_instance,
+                                    filename);
+      return load_other(filename);
+#else
+      TIFF *tif = TIFFOpen(filename,"r");
+      if (tif) {
+        unsigned int nb_images = 0;
+        do ++nb_images; while (TIFFReadDirectory(tif));
+        if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
+          cimg::warn(_cimg_instance
+                     "load_tiff() : File '%s' contains %u image(s) while specified frame range is [%u,%u] (step %u).",
+                     cimg_instance,
+                     filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
+
+        if (nfirst_frame>=nb_images) return assign();
+        if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
+        TIFFSetDirectory(tif,0);
+        CImg<T> frame;
+        for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) {
+          frame._load_tiff(tif,l);
+          if (l==nfirst_frame) assign(frame._width,frame._height,1+(nlast_frame-nfirst_frame)/nstep_frame,frame._spectrum);
+          if (frame._width>_width || frame._height>_height || frame._spectrum>_spectrum)
+            resize(cimg::max(frame._width,_width),cimg::max(frame._height,_height),-100,cimg::max(frame._spectrum,_spectrum),0);
+          draw_image(0,0,(l-nfirst_frame)/nstep_frame,frame);
+        }
+        TIFFClose(tif);
+      } else throw CImgException(_cimg_instance
+                                 "load_tiff() : Failed to open file '%s'.",
+                                 cimg_instance,
+                                 filename);
+      return *this;
+#endif
+    }
+
+    static CImg<T> get_load_tiff(const char *const filename,
+                                 const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                 const unsigned int step_frame=1) {
+      return CImg<T>().load_tiff(filename,first_frame,last_frame,step_frame);
+    }
+
+    // (Original contribution by Jerome Boulanger).
+#ifdef cimg_use_tiff
+    template<typename t>
+    void _load_tiff_tiled_contig(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) {
+      t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif));
+      if (buf) {
+        for (unsigned int row = 0; row<ny; row+=th)
+          for (unsigned int col = 0; col<nx; col+=tw) {
+            if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
+              _TIFFfree(buf); TIFFClose(tif);
+              throw CImgException(_cimg_instance
+                                  "load_tiff() : Invalid tile in file '%s'.",
+                                  cimg_instance,
+                                  TIFFFileName(tif));
+            }
+            const t *ptr = buf;
+            for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+              for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+                for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+                  (*this)(cc,rr,vv) = (T)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
+          }
+        _TIFFfree(buf);
+      }
+    }
+
+    template<typename t>
+    void _load_tiff_tiled_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) {
+      t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif));
+      if (buf) {
+        for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+          for (unsigned int row = 0; row<ny; row+=th)
+            for (unsigned int col = 0; col<nx; col+=tw) {
+              if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
+                _TIFFfree(buf); TIFFClose(tif);
+                throw CImgException(_cimg_instance
+                                    "load_tiff() : Invalid tile in file '%s'.",
+                                    cimg_instance,
+                                    TIFFFileName(tif));
+              }
+              const t *ptr = buf;
+              for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
+                for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
+                  (*this)(cc,rr,vv) = (T)*(ptr++);
+            }
+        _TIFFfree(buf);
+      }
+    }
+
+    template<typename t>
+    void _load_tiff_contig(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) {
+      t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif));
+      if (buf) {
+        uint32 row, rowsperstrip = (uint32)-1;
+        TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+        for (row = 0; row<ny; row+= rowsperstrip) {
+          uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+          tstrip_t strip = TIFFComputeStrip(tif, row, 0);
+          if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+            _TIFFfree(buf); TIFFClose(tif);
+            throw CImgException(_cimg_instance
+                                "load_tiff() : Invalid strip in file '%s'.",
+                                cimg_instance,
+                                TIFFFileName(tif));
+          }
+          const t *ptr = buf;
+          for (unsigned int rr = 0; rr<nrow; ++rr)
+            for (unsigned int cc = 0; cc<nx; ++cc)
+              for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)*(ptr++);
+        }
+        _TIFFfree(buf);
+      }
+    }
+
+    template<typename t>
+    void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) {
+      t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif));
+      if (buf) {
+        uint32 row, rowsperstrip = (uint32)-1;
+        TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+        for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+          for (row = 0; row<ny; row+= rowsperstrip) {
+            uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
+            tstrip_t strip = TIFFComputeStrip(tif, row, vv);
+            if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+              _TIFFfree(buf); TIFFClose(tif);
+              throw CImgException(_cimg_instance
+                                  "load_tiff() : Invalid strip in file '%s'.",
+                                  cimg_instance,
+                                  TIFFFileName(tif));
+            }
+            const t *ptr = buf;
+            for (unsigned int rr = 0;rr<nrow; ++rr)
+              for (unsigned int cc = 0; cc<nx; ++cc)
+                (*this)(cc,row+rr,vv) = (T)*(ptr++);
+          }
+        _TIFFfree(buf);
+      }
+    }
+
+    CImg<T>& _load_tiff(TIFF *const tif, const unsigned int directory) {
+      if (!TIFFSetDirectory(tif,directory)) return assign();
+      uint16 samplesperpixel, bitspersample;
+      uint16 sampleformat = SAMPLEFORMAT_UINT;
+      uint32 nx,ny;
+      const char *const filename = TIFFFileName(tif);
+      TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx);
+      TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny);
+      TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel);
+      TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat);
+      if (samplesperpixel!=1 && samplesperpixel!=3 && samplesperpixel!=4) {
+        cimg::warn(_cimg_instance
+                   "load_tiff() : Unknow value for tag TIFFTAG_SAMPLESPERPIXEL (so assumed to be 1), in file '%s'.",
+                   cimg_instance,
+                   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 : {
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_contig<unsigned char>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_contig<signed char>(tif,samplesperpixel,nx,ny,tw,th);
+            } break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_contig<unsigned short>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_contig<short>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_contig<unsigned int>(tif,samplesperpixel,nx,ny,tw,th);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_tiled_contig<int>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_contig<float>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            } else switch (bitspersample) {
+            case 8 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_separate<unsigned char>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_separate<signed char>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_separate<unsigned short>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_separate<short>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_tiled_separate<unsigned int>(tif,samplesperpixel,nx,ny,tw,th);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_tiled_separate<int>(tif,samplesperpixel,nx,ny,tw,th);
+              else _load_tiff_tiled_separate<float>(tif,samplesperpixel,nx,ny,tw,th);
+              break;
+            }
+        } else {
+          if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
+            case 8 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig<unsigned char>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_contig<signed char>(tif,samplesperpixel,nx,ny);
+              break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig<unsigned short>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_contig<short>(tif,samplesperpixel,nx,ny);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig<unsigned int>(tif,samplesperpixel,nx,ny);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig<int>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_contig<float>(tif,samplesperpixel,nx,ny);
+              break;
+            } else switch (bitspersample){
+            case 8 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate<unsigned char>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_separate<signed char>(tif,samplesperpixel,nx,ny);
+              break;
+            case 16 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate<unsigned short>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_separate<short>(tif,samplesperpixel,nx,ny);
+              break;
+            case 32 :
+              if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate<unsigned int>(tif,samplesperpixel,nx,ny);
+              else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate<int>(tif,samplesperpixel,nx,ny);
+              else _load_tiff_separate<float>(tif,samplesperpixel,nx,ny);
+              break;
+            }
+        }
+      } else {
+        uint32 *const raster = (uint32*)_TIFFmalloc(nx*ny*sizeof(uint32));
+        if (!raster) {
+          _TIFFfree(raster); TIFFClose(tif);
+          throw CImgException(_cimg_instance
+                              "load_tiff() : Failed to allocate memory (%s) for file '%s'.",
+                              cimg_instance,
+                              cimg::strbuffersize(nx*ny*sizeof(uint32)),filename);
+        }
+        TIFFReadRGBAImage(tif,nx,ny,raster,0);
+        switch (samplesperpixel) {
+        case 1 : {
+          cimg_forXY(*this,x,y) (*this)(x,y) = (T)(float)((raster[nx*(ny-1-y)+x] + 128)/257);
+        } break;
+        case 3 : {
+          cimg_forXY(*this,x,y) {
+            (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
+            (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
+            (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
+          }
+        } break;
+        case 4 : {
+          cimg_forXY(*this,x,y) {
+            (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
+            (*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
+            (*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
+            (*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny-1-y)+x]);
+          }
+        } break;
+        }
+        _TIFFfree(raster);
+      }
+      return *this;
+    }
+#endif
+
+    //! Load an image from an ANALYZE7.5/NIFTI file.
+    CImg<T>& load_analyze(const char *const filename, float *const voxsize=0) {
+      return _load_analyze(0,filename,voxsize);
+    }
+
+    static CImg<T> get_load_analyze(const char *const filename, float *const voxsize=0) {
+      return CImg<T>().load_analyze(filename,voxsize);
+    }
+
+    //! Load an image from an ANALYZE7.5/NIFTI file.
+    CImg<T>& load_analyze(std::FILE *const file, float *const voxsize=0) {
+      return _load_analyze(file,0,voxsize);
+    }
+
+    static CImg<T> get_load_analyze(std::FILE *const file, float *const voxsize=0) {
+      return CImg<T>().load_analyze(file,voxsize);
+    }
+
+    CImg<T>& _load_analyze(std::FILE *const file, const char *const filename, float *const voxsize=0) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_analyze() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *nfile_header = 0, *nfile = 0;
+      if (!file) {
+        char body[1024] = { 0 };
+        const char *const ext = cimg::split_filename(filename,body);
+        if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file.
+          nfile_header = cimg::fopen(filename,"rb");
+          std::sprintf(body + std::strlen(body),".img");
+          nfile = cimg::fopen(body,"rb");
+        } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file.
+          nfile = cimg::fopen(filename,"rb");
+          std::sprintf(body + std::strlen(body),".hdr");
+          nfile_header = cimg::fopen(body,"rb");
+        } else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file.
+      } else nfile_header = nfile = file; // File is a Niftii file.
+      if (!nfile || !nfile_header)
+        throw CImgIOException(_cimg_instance
+                              "load_analyze() : Invalid Analyze7.5 or NIFTI header in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+
+      // Read header.
+      bool endian = false;
+      unsigned int header_size;
+      cimg::fread(&header_size,1,nfile_header);
+      if (!header_size)
+        throw CImgIOException(_cimg_instance
+                              "load_analyze() : Invalid zero-sized header in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+
+      if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); }
+      unsigned char *const header = new unsigned char[header_size];
+      cimg::fread(header+4,header_size-4,nfile_header);
+      if (!file && nfile_header!=nfile) cimg::fclose(nfile_header);
+      if (endian) {
+        cimg::invert_endianness((short*)(header+40),5);
+        cimg::invert_endianness((short*)(header+70),1);
+        cimg::invert_endianness((short*)(header+72),1);
+        cimg::invert_endianness((float*)(header+76),4);
+        cimg::invert_endianness((float*)(header+112),1);
+      }
+      unsigned short *dim = (unsigned short*)(header+40), dimx = 1, dimy = 1, dimz = 1, dimv = 1;
+      if (!dim[0])
+        cimg::warn(_cimg_instance
+                   "load_analyze() : File '%s' defines an image with zero dimensions.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (dim[0]>4)
+        cimg::warn(_cimg_instance
+                   "load_analyze() : File '%s' defines an image with %u dimensions, reading only the 4 first.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)",dim[0]);
+
+      if (dim[0]>=1) dimx = dim[1];
+      if (dim[0]>=2) dimy = dim[2];
+      if (dim[0]>=3) dimz = dim[3];
+      if (dim[0]>=4) dimv = dim[4];
+      float scalefactor = *(float*)(header+112); if (scalefactor==0) scalefactor=1;
+      const unsigned short datatype = *(short*)(header+70);
+      if (voxsize) {
+        const float *vsize = (float*)(header+76);
+        voxsize[0] = vsize[1]; voxsize[1] = vsize[2]; voxsize[2] = vsize[3];
+      }
+      delete[] header;
+
+      // Read pixel data.
+      assign(dimx,dimy,dimz,dimv);
+      switch (datatype) {
+      case 2 : {
+        unsigned char *const buffer = new unsigned char[dimx*dimy*dimz*dimv];
+        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+        cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+        delete[] buffer;
+      } break;
+      case 4 : {
+        short *const buffer = new short[dimx*dimy*dimz*dimv];
+        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+        cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+        delete[] buffer;
+      } break;
+      case 8 : {
+        int *const buffer = new int[dimx*dimy*dimz*dimv];
+        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+        cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+        delete[] buffer;
+      } break;
+      case 16 : {
+        float *const buffer = new float[dimx*dimy*dimz*dimv];
+        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+        cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+        delete[] buffer;
+      } break;
+      case 64 : {
+        double *const buffer = new double[dimx*dimy*dimz*dimv];
+        cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
+        if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
+        cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+        delete[] buffer;
+      } break;
+      default :
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_analyze() : Unable to load datatype %d in file '%s'",
+                              cimg_instance,
+                              datatype,filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image (list) from a .cimg file.
+    CImg<T>& load_cimg(const char *const filename, const char axis='z', const char align='p') {
+      CImgList<T> list;
+      list.load_cimg(filename);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(const char *const filename, const char axis='z', const char align='p') {
+      return CImg<T>().load_cimg(filename,axis,align);
+    }
+
+    //! Load an image (list) from a .cimg file.
+    CImg<T>& load_cimg(std::FILE *const file, const char axis='z', const char align='p') {
+      CImgList<T> list;
+      list.load_cimg(file);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(std::FILE *const file, const char axis='z', const char align='p') {
+      return CImg<T>().load_cimg(file,axis,align);
+    }
+
+    //! Load a sub-image (list) from a .cimg file.
+    CImg<T>& load_cimg(const char *const filename,
+                       const unsigned int n0, const unsigned int n1,
+                       const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                       const char axis='z', const char align='p') {
+      CImgList<T> list;
+      list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(const char *const filename,
+                                 const unsigned int n0, const unsigned int n1,
+                                 const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                                 const char axis='z', const char align='p') {
+      return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align);
+    }
+
+    //! Load a sub-image (list) from a non-compressed .cimg file.
+    CImg<T>& load_cimg(std::FILE *const file,
+                       const unsigned int n0, const unsigned int n1,
+                       const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                       const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                       const char axis='z', const char align='p') {
+      CImgList<T> list;
+      list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_cimg(std::FILE *const file,
+                                 const unsigned int n0, const unsigned int n1,
+                                 const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                                 const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1,
+                                 const char axis='z', const char align='p') {
+      return CImg<T>().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align);
+    }
+
+    //! Load an image from an INRIMAGE-4 file.
+    CImg<T>& load_inr(const char *const filename, float *const voxsize=0) {
+      return _load_inr(0,filename,voxsize);
+    }
+
+    static CImg<T> get_load_inr(const char *const filename, float *const voxsize=0) {
+      return CImg<T>().load_inr(filename,voxsize);
+    }
+
+    //! Load an image from an INRIMAGE-4 file.
+    CImg<T>& load_inr(std::FILE *const file, float *const voxsize=0) {
+      return _load_inr(file,0,voxsize);
+    }
+
+    static CImg<T> get_load_inr(std::FILE *const file, float *voxsize=0) {
+      return CImg<T>().load_inr(file,voxsize);
+    }
+
+    // Load an image from an INRIMAGE-4 file (internal).
+    static void _load_inr_header(std::FILE *file, int out[8], float *const voxsize) {
+      char item[1024] = { 0 }, tmp1[64] = { 0 }, tmp2[64] = { 0 };
+      out[0] = std::fscanf(file,"%63s",item);
+      out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1;
+      if(cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0)
+        throw CImgIOException("CImg<%s>::load_inr() : INRIMAGE-4 header not found.",
+                              pixel_type());
+
+      while (std::fscanf(file," %63[^\n]%*c",item)!=EOF && std::strncmp(item,"##}",3)) {
+        std::sscanf(item," XDIM%*[^0-9]%d",out);
+        std::sscanf(item," YDIM%*[^0-9]%d",out+1);
+        std::sscanf(item," ZDIM%*[^0-9]%d",out+2);
+        std::sscanf(item," VDIM%*[^0-9]%d",out+3);
+        std::sscanf(item," PIXSIZE%*[^0-9]%d",out+6);
+        if (voxsize) {
+          std::sscanf(item," VX%*[^0-9.+-]%f",voxsize);
+          std::sscanf(item," VY%*[^0-9.+-]%f",voxsize+1);
+          std::sscanf(item," VZ%*[^0-9.+-]%f",voxsize+2);
+        }
+        if (std::sscanf(item," CPU%*[ =]%s",tmp1)) out[7]=cimg::strncasecmp(tmp1,"sun",3)?0:1;
+        switch (std::sscanf(item," TYPE%*[ =]%s %s",tmp1,tmp2)) {
+        case 0 : break;
+        case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; std::strncpy(tmp1,tmp2,sizeof(tmp1)-1);
+        case 1 :
+          if (!cimg::strncasecmp(tmp1,"int",3)   || !cimg::strncasecmp(tmp1,"fixed",5))  out[4] = 0;
+          if (!cimg::strncasecmp(tmp1,"float",5) || !cimg::strncasecmp(tmp1,"double",6)) out[4] = 1;
+          if (!cimg::strncasecmp(tmp1,"packed",6))                                       out[4] = 2;
+          if (out[4]>=0) break;
+        default :
+          throw CImgIOException("CImg<%s>::load_inr() : Invalid pixel type '%s' defined in header.",
+                                pixel_type(),
+                                tmp2);
+        }
+      }
+      if(out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Invalid dimensions (%d,%d,%d,%d) defined in header.",
+                              pixel_type(),
+                              out[0],out[1],out[2],out[3]);
+      if(out[4]<0 || out[5]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Incomplete pixel type defined in header.",
+                              pixel_type());
+      if(out[6]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Incomplete PIXSIZE field defined in header.",
+                              pixel_type());
+      if(out[7]<0)
+        throw CImgIOException("CImg<%s>::load_inr() : Big/Little Endian coding type undefined in header.",
+                              pixel_type());
+    }
+
+    CImg<T>& _load_inr(std::FILE *const file, const char *const filename, float *const voxsize) {
+#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \
+     if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \
+        Ts *xval, *const val = new Ts[fopt[0]*fopt[3]]; \
+        cimg_forYZ(*this,y,z) { \
+            cimg::fread(val,fopt[0]*fopt[3],nfile); \
+            if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \
+            xval = val; cimg_forX(*this,x) cimg_forC(*this,c) (*this)(x,y,z,c) = (T)*(xval++); \
+          } \
+        delete[] val; \
+        loaded = true; \
+      }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_inr() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      int fopt[8], endian=cimg::endianness()?1:0;
+      bool loaded = false;
+      if (voxsize) voxsize[0]=voxsize[1]=voxsize[2]=1;
+      _load_inr_header(nfile,fopt,voxsize);
+      assign(fopt[0],fopt[1],fopt[2],fopt[3]);
+      _cimg_load_inr_case(0,0,8,unsigned char);
+      _cimg_load_inr_case(0,1,8,char);
+      _cimg_load_inr_case(0,0,16,unsigned short);
+      _cimg_load_inr_case(0,1,16,short);
+      _cimg_load_inr_case(0,0,32,unsigned int);
+      _cimg_load_inr_case(0,1,32,int);
+      _cimg_load_inr_case(1,0,32,float);
+      _cimg_load_inr_case(1,1,32,float);
+      _cimg_load_inr_case(1,0,64,double);
+      _cimg_load_inr_case(1,1,64,double);
+      if (!loaded) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_inr() : Unknown pixel type defined in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a EXR file.
+    CImg<T>& load_exr(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_exr() : Specified filename is (null).",
+                                    cimg_instance);
+
+#ifndef cimg_use_openexr
+      return load_other(filename);
+#else
+      Imf::RgbaInputFile file(filename);
+      Imath::Box2i dw = file.dataWindow();
+      const int
+        inwidth = dw.max.x - dw.min.x + 1,
+        inheight = dw.max.y - dw.min.y + 1;
+      Imf::Array2D<Imf::Rgba> pixels;
+      pixels.resizeErase(inheight,inwidth);
+      file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y*inwidth, 1, inwidth);
+      file.readPixels(dw.min.y, dw.max.y);
+      assign(inwidth,inheight,1,4);
+      T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3);
+      cimg_forXY(*this,x,y) {
+        *(ptr_r++) = (T)pixels[y][x].r;
+        *(ptr_g++) = (T)pixels[y][x].g;
+        *(ptr_b++) = (T)pixels[y][x].b;
+        *(ptr_a++) = (T)pixels[y][x].a;
+      }
+      return *this;
+#endif
+    }
+
+    static CImg<T> get_load_exr(const char *const filename) {
+      return CImg<T>().load_exr(filename);
+    }
+
+    //! Load an image from a PANDORE file.
+    CImg<T>& load_pandore(const char *const filename) {
+      return _load_pandore(0,filename);
+    }
+
+    static CImg<T> get_load_pandore(const char *const filename) {
+      return CImg<T>().load_pandore(filename);
+    }
+
+    //! Load an image from a PANDORE file.
+    CImg<T>& load_pandore(std::FILE *const file) {
+      return _load_pandore(file,0);
+    }
+
+    static CImg<T> get_load_pandore(std::FILE *const file) {
+      return CImg<T>().load_pandore(file);
+    }
+
+    CImg<T>& _load_pandore(std::FILE *const file, const char *const filename) {
+#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \
+        cimg::fread(dims,nbdim,nfile); \
+        if (endian) cimg::invert_endianness(dims,nbdim); \
+        assign(nwidth,nheight,ndepth,ndim); \
+        const unsigned int siz = size(); \
+        stype *buffer = new stype[siz]; \
+        cimg::fread(buffer,siz,nfile); \
+        if (endian) cimg::invert_endianness(buffer,siz); \
+        T *ptrd = _data; \
+        cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \
+        buffer-=siz; \
+        delete[] buffer
+
+#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \
+        if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \
+        else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \
+        else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \
+        else throw CImgIOException(_cimg_instance \
+                                   "load_pandore() : Unknown pixel datatype in file '%s'.", \
+                                   cimg_instance, \
+                                   filename?filename:"(FILE*)"); }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_pandore() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      char header[32] = { 0 };
+      cimg::fread(header,12,nfile);
+      if (cimg::strncasecmp("PANDORE",header,7)) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pandore() : PANDORE header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      unsigned int imageid, dims[8] = { 0 };
+      cimg::fread(&imageid,1,nfile);
+      const bool endian = (imageid>255);
+      if (endian) cimg::invert_endianness(imageid);
+      cimg::fread(header,20,nfile);
+
+      switch (imageid) {
+      case 2: _cimg_load_pandore_case(2,dims[1],1,1,1,unsigned char,unsigned char,unsigned char,1); break;
+      case 3: _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break;
+      case 4: _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break;
+      case 5: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,unsigned char,unsigned char,unsigned char,1); break;
+      case 6: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break;
+      case 7: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break;
+      case 8: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,unsigned char,unsigned char,unsigned char,1); break;
+      case 9: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break;
+      case 10: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break;
+      case 11 : { // Region 1d
+        cimg::fread(dims,3,nfile);
+        if (endian) cimg::invert_endianness(dims,3);
+        assign(dims[1],1,1,1);
+        const unsigned siz = size();
+        if (dims[2]<256) {
+          unsigned char *buffer = new unsigned char[siz];
+          cimg::fread(buffer,siz,nfile);
+          T *ptrd = _data;
+          cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+          buffer-=siz;
+          delete[] buffer;
+        } else {
+          if (dims[2]<65536) {
+            unsigned short *buffer = new unsigned short[siz];
+            cimg::fread(buffer,siz,nfile);
+            if (endian) cimg::invert_endianness(buffer,siz);
+            T *ptrd = _data;
+            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+            buffer-=siz;
+            delete[] buffer;
+          } else {
+            unsigned int *buffer = new unsigned int[siz];
+            cimg::fread(buffer,siz,nfile);
+            if (endian) cimg::invert_endianness(buffer,siz);
+            T *ptrd = _data;
+            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+            buffer-=siz;
+            delete[] buffer;
+          }
+        }
+      }
+        break;
+      case 12 : { // Region 2d
+        cimg::fread(dims,4,nfile);
+        if (endian) cimg::invert_endianness(dims,4);
+        assign(dims[2],dims[1],1,1);
+        const unsigned int siz = size();
+        if (dims[3]<256) {
+          unsigned char *buffer = new unsigned char[siz];
+          cimg::fread(buffer,siz,nfile);
+          T *ptrd = _data;
+          cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+          buffer-=siz;
+          delete[] buffer;
+        } else {
+          if (dims[3]<65536) {
+            unsigned short *buffer = new unsigned short[siz];
+            cimg::fread(buffer,siz,nfile);
+            if (endian) cimg::invert_endianness(buffer,siz);
+            T *ptrd = _data;
+            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+            buffer-=siz;
+            delete[] buffer;
+          } else {
+            unsigned long *buffer = new unsigned long[siz];
+            cimg::fread(buffer,siz,nfile);
+            if (endian) cimg::invert_endianness(buffer,siz);
+            T *ptrd = _data;
+            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+            buffer-=siz;
+            delete[] buffer;
+          }
+        }
+      }
+        break;
+      case 13 : { // Region 3d
+        cimg::fread(dims,5,nfile);
+        if (endian) cimg::invert_endianness(dims,5);
+        assign(dims[3],dims[2],dims[1],1);
+        const unsigned int siz = size();
+        if (dims[4]<256) {
+          unsigned char *buffer = new unsigned char[siz];
+          cimg::fread(buffer,siz,nfile);
+          T *ptrd = _data;
+          cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+          buffer-=siz;
+          delete[] buffer;
+        } else {
+          if (dims[4]<65536) {
+            unsigned short *buffer = new unsigned short[siz];
+            cimg::fread(buffer,siz,nfile);
+            if (endian) cimg::invert_endianness(buffer,siz);
+            T *ptrd = _data;
+            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+            buffer-=siz;
+            delete[] buffer;
+          } else {
+            unsigned int *buffer = new unsigned int[siz];
+            cimg::fread(buffer,siz,nfile);
+            if (endian) cimg::invert_endianness(buffer,siz);
+            T *ptrd = _data;
+            cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
+            buffer-=siz;
+            delete[] buffer;
+          }
+        }
+      }
+        break;
+      case 16: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,unsigned char,unsigned char,unsigned char,1); break;
+      case 17: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break;
+      case 18: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break;
+      case 19: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,unsigned char,unsigned char,unsigned char,1); break;
+      case 20: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break;
+      case 21: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break;
+      case 22: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned char,unsigned char,unsigned char,1); break;
+      case 23: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4);
+      case 24: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],unsigned long,unsigned int,unsigned short,4); break;
+      case 25: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break;
+      case 26: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned char,unsigned char,unsigned char,1); break;
+      case 27: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break;
+      case 28: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],unsigned long,unsigned int,unsigned short,4); break;
+      case 29: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break;
+      case 30: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned char,unsigned char,unsigned char,1); break;
+      case 31: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break;
+      case 32: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],unsigned long,unsigned int,unsigned short,4); break;
+      case 33: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break;
+      case 34 : { // Points 1d
+        int ptbuf[4] = { 0 };
+        cimg::fread(ptbuf,1,nfile);
+        if (endian) cimg::invert_endianness(ptbuf,1);
+        assign(1); (*this)(0) = (T)ptbuf[0];
+      } break;
+      case 35 : { // Points 2d
+        int ptbuf[4] = { 0 };
+        cimg::fread(ptbuf,2,nfile);
+        if (endian) cimg::invert_endianness(ptbuf,2);
+        assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0];
+      } break;
+      case 36 : { // Points 3d
+        int ptbuf[4] = { 0 };
+        cimg::fread(ptbuf,3,nfile);
+        if (endian) cimg::invert_endianness(ptbuf,3);
+        assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0];
+      } break;
+      default :
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_pandore() : Unable to load data with ID_type %u in file '%s'.",
+                              cimg_instance,
+                              imageid,filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a PAR-REC (Philips) file.
+    CImg<T>& load_parrec(const char *const filename, const char axis='c', const char align='p') {
+      CImgList<T> list;
+      list.load_parrec(filename);
+      if (list._width==1) return list[0].move_to(*this);
+      return assign(list.get_append(axis,align));
+    }
+
+    static CImg<T> get_load_parrec(const char *const filename, const char axis='c', const char align='p') {
+      return CImg<T>().load_parrec(filename,axis,align);
+    }
+
+    //! Load an image from a .RAW file.
+    CImg<T>& load_raw(const char *const filename,
+                      const unsigned int sizex, const unsigned int sizey=1,
+                      const unsigned int sizez=1, const unsigned int sizev=1,
+                      const bool multiplexed=false, const bool invert_endianness=false) {
+      return _load_raw(0,filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+    }
+
+    static CImg<T> get_load_raw(const char *const filename,
+                                const unsigned int sizex, const unsigned int sizey=1,
+                                const unsigned int sizez=1, const unsigned int sizev=1,
+                                const bool multiplexed=false, const bool invert_endianness=false) {
+      return CImg<T>().load_raw(filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+    }
+
+    //! Load an image from a .RAW file.
+    CImg<T>& load_raw(std::FILE *const file,
+                      const unsigned int sizex, const unsigned int sizey=1,
+                      const unsigned int sizez=1, const unsigned int sizev=1,
+                      const bool multiplexed=false, const bool invert_endianness=false) {
+      return _load_raw(file,0,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+    }
+
+    static CImg<T> get_load_raw(std::FILE *const file,
+                                const unsigned int sizex, const unsigned int sizey=1,
+                                const unsigned int sizez=1, const unsigned int sizev=1,
+                                const bool multiplexed=false, const bool invert_endianness=false) {
+      return CImg<T>().load_raw(file,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
+    }
+
+    CImg<T>& _load_raw(std::FILE *const file, const char *const filename,
+                       const unsigned int sizex, const unsigned int sizey,
+                       const unsigned int sizez, const unsigned int sizev,
+                       const bool multiplexed, const bool invert_endianness) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_raw() : Specified filename is (null).",
+                                    cimg_instance);
+
+      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 (invert_endianness) cimg::invert_endianness(_data,siz);
+        }
+        else {
+          CImg<T> buf(1,1,1,sizev);
+          cimg_forXYZ(*this,x,y,z) {
+            cimg::fread(buf._data,sizev,nfile);
+            if (invert_endianness) cimg::invert_endianness(buf._data,sizev);
+            set_vector_at(buf,x,y,z); }
+        }
+        if (!file) cimg::fclose(nfile);
+      }
+      return *this;
+    }
+
+    //! Load a video sequence using FFMPEG av's libraries.
+    CImg<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                         const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
+                         const char axis='z', const char align='p') {
+      return get_load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume,axis,align).move_to(*this);
+    }
+
+    static CImg<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                   const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
+                                   const char axis='z', const char align='p') {
+      return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume).get_append(axis,align);
+    }
+
+    //! Load an image sequence from a YUV file.
+    CImg<T>& load_yuv(const char *const filename,
+                      const unsigned int sizex, const unsigned int sizey=1,
+                      const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                      const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+      return get_load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).move_to(*this);
+    }
+
+    static CImg<T> get_load_yuv(const char *const filename,
+                                const unsigned int sizex, const unsigned int sizey=1,
+                                const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+      return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
+    }
+
+    //! Load an image sequence from a YUV file.
+    CImg<T>& load_yuv(std::FILE *const file,
+                      const unsigned int sizex, const unsigned int sizey=1,
+                      const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                      const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+      return get_load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).move_to(*this);
+    }
+
+    static CImg<T> get_load_yuv(std::FILE *const file,
+                                const unsigned int sizex, const unsigned int sizey=1,
+                                const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
+      return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
+    }
+
+    //! Load a 3d object from a .OFF file.
+    template<typename tf, typename tc>
+    CImg<T>& load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return _load_off(0,filename,primitives,colors);
+    }
+
+    template<typename tf, typename tc>
+    static CImg<T> get_load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return CImg<T>().load_off(filename,primitives,colors);
+    }
+
+    //! Load a 3d object from a .OFF file.
+    template<typename tf, typename tc>
+    CImg<T>& load_off(std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return _load_off(file,0,primitives,colors);
+    }
+
+    template<typename tf, typename tc>
+    static CImg<T> get_load_off(std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return CImg<T>().load_off(file,primitives,colors);
+    }
+
+    template<typename tf, typename tc>
+    CImg<T>& _load_off(std::FILE *const file, const char *const filename,
+                       CImgList<tf>& primitives, CImgList<tc>& colors) {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_off() : Specified filename is (null).",
+                                    cimg_instance);
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
+      unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0;
+      char line[256] = { 0 };
+      int err;
+
+      // Skip comments, and read magic string OFF
+      do { err = std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && *line=='#'));
+      if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_off() : OFF header not found in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+      do { err = std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && *line=='#'));
+      if ((err = std::sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "load_off() : Invalid number of vertices or primitives specified in file '%s'.",
+                              cimg_instance,
+                              filename?filename:"(FILE*)");
+      }
+
+      // Read points data
+      assign(nb_points,3);
+      float X = 0, Y = 0, Z = 0;
+      cimg_forX(*this,l) {
+        do { err = std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && *line=='#'));
+        if ((err = std::sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) {
+          if (!file) cimg::fclose(nfile);
+          throw CImgIOException(_cimg_instance
+                                "load_off() : Failed to read vertex %u/%u in file '%s'.",
+                                cimg_instance,
+                                l+1,nb_points,filename?filename:"(FILE*)");
+        }
+        (*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z;
+      }
+
+      // Read primitive data
+      primitives.assign();
+      colors.assign();
+      bool stopflag = false;
+      while (!stopflag) {
+        float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f;
+        unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0;
+        *line = 0;
+        if ((err = std::fscanf(nfile,"%u",&prim))!=1) stopflag=true;
+        else {
+          ++nb_read;
+          switch (prim) {
+          case 1 : {
+            if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line))<2) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 2 : {
+            if ((err = std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line))<2) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i1).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 3 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line))<3) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i2,i1).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 4 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line))<4) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)).move_to(colors);
+            }
+          } break;
+          case 5 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line))<5) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i4,i3).move_to(primitives);
+              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+              ++nb_primitives;
+            }
+          } break;
+          case 6 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line))<6) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i5,i4,i3).move_to(primitives);
+              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+              ++nb_primitives;
+            }
+          } break;
+          case 7 : {
+            if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line))<7) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i4,i3,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i6,i5,i4).move_to(primitives);
+              CImg<tf>::vector(i3,i2,i1).move_to(primitives);
+              colors.insert(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(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line))<7) {
+              cimg::warn(_cimg_instance
+                         "load_off() : Failed to read primitive %u/%u from file '%s'.",
+                         cimg_instance,
+                         nb_read,nb_primitives,filename?filename:"(FILE*)");
+
+              err = std::fscanf(nfile,"%*[^\n] ");
+            } else {
+              err = std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
+              CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+              CImg<tf>::vector(i0,i5,i4,i3).move_to(primitives);
+              CImg<tf>::vector(i0,i7,i6,i5).move_to(primitives);
+              colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
+              ++(++nb_primitives);
+            }
+          } break;
+          default :
+            cimg::warn(_cimg_instance
+                       "load_off() : Failed to read primitive %u/%u (%u vertices) from file '%s'.",
+                       cimg_instance,
+                       nb_read,nb_primitives,prim,filename?filename:"(FILE*)");
+
+            err = std::fscanf(nfile,"%*[^\n] ");
+          }
+        }
+      }
+      if (!file) cimg::fclose(nfile);
+      if (primitives._width!=nb_primitives)
+        cimg::warn(_cimg_instance
+                   "load_off() : Only %u/%u primitives read from file '%s'.",
+                   cimg_instance,
+                   primitives._width,nb_primitives,filename?filename:"(FILE*)");
+
+      return *this;
+    }
+
+    //! Load a video sequence using FFMPEG's external tool 'ffmpeg'.
+    CImg<T>& load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
+      return get_load_ffmpeg_external(filename,axis,align).move_to(*this);
+    }
+
+    static CImg<T> get_load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
+      return CImgList<T>().load_ffmpeg_external(filename).get_append(axis,align);
+    }
+
+    //! Load an image using GraphicsMagick's external tool 'gm'.
+    CImg<T>& load_graphicsmagick_external(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_graphicsmagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file = 0;
+#if cimg_OS==1
+      cimg_snprintf(command,sizeof(command),"%s convert \"%s\" pnm:-",cimg::graphicsmagick_path(),filename);
+      file = popen(command,"r");
+      if (file) { load_pnm(file); pclose(file); return *this; }
+#endif
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.pnm",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s convert \"%s\" \"%s\"",cimg::graphicsmagick_path(),filename,filetmp);
+      cimg::system(command,cimg::graphicsmagick_path());
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_graphicsmagick_external() : Failed to load file '%s' with external command 'gm'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load_pnm(filetmp);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    static CImg<T> get_load_graphicsmagick_external(const char *const filename) {
+      return CImg<T>().load_graphicsmagick_external(filename);
+    }
+
+    //! Load a gzipped image file, using external tool 'gunzip'.
+    CImg<T>& load_gzip_external(const char *const filename) {
+      if (!filename)
+        throw CImgIOException(_cimg_instance
+                              "load_gzip_external() : Specified filename is (null).",
+                              cimg_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *const ext = cimg::split_filename(filename,body),
+        *const ext2 = cimg::split_filename(body,0);
+
+      std::FILE *file = 0;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+
+      cimg_snprintf(command,sizeof(command),"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
+      cimg::system(command);
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_gzip_external() : Failed to load file '%s' with external command 'gunzip'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load(filetmp);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    static CImg<T> get_load_gzip_external(const char *const filename) {
+      return CImg<T>().load_gzip_external(filename);
+    }
+
+    //! Load an image using ImageMagick's external tool 'convert'.
+    CImg<T>& load_imagemagick_external(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_imagemagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file = 0;
+#if cimg_OS==1
+      cimg_snprintf(command,sizeof(command),"%s \"%s\" pnm:-",cimg::imagemagick_path(),filename);
+      file = popen(command,"r");
+      if (file) { load_pnm(file); pclose(file); return *this; }
+#endif
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.pnm",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s \"%s\" \"%s\"",cimg::imagemagick_path(),filename,filetmp);
+      cimg::system(command,cimg::imagemagick_path());
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_imagemagick_external() : Failed to load file '%s' with external command 'convert'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load_pnm(filetmp);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    static CImg<T> get_load_imagemagick_external(const char *const filename) {
+      return CImg<T>().load_imagemagick_external(filename);
+    }
+
+    //! Load a DICOM image file, using XMedcon's external tool 'medcon'.
+    CImg<T>& load_medcon_external(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_medcon_external() : Specified filename is (null).",
+                                    cimg_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      cimg::fclose(cimg::fopen(filename,"r"));
+      std::FILE *file = 0;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s.hdr",cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s -w -c anlz -o %s -f %s",cimg::medcon_path(),filetmp,filename);
+      cimg::system(command);
+      cimg::split_filename(filetmp,body);
+      cimg_snprintf(command,sizeof(command),"m000-%s.hdr",body);
+      file = std::fopen(command,"rb");
+      if (!file) {
+        throw CImgIOException(_cimg_instance
+                              "load_medcon_external() : Failed to load file '%s' with external command 'medcon'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load_analyze(command);
+      std::remove(command);
+      cimg_snprintf(command,sizeof(command),"m000-%s.img",body);
+      std::remove(command);
+      return *this;
+    }
+
+    static CImg<T> get_load_medcon_external(const char *const filename) {
+      return CImg<T>().load_medcon_external(filename);
+    }
+
+    //! Load a RAW Color Camera image file, using external tool 'dcraw'.
+    CImg<T>& load_dcraw_external(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_dcraw_external() : Specified filename is (null).",
+                                    cimg_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file = 0;
+#if cimg_OS==1
+      cimg_snprintf(command,sizeof(command),"%s -w -4 -c \"%s\"",cimg::dcraw_path(),filename);
+      file = popen(command,"r");
+      if (file) { load_pnm(file); pclose(file); return *this; }
+#endif
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.ppm",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s -w -4 -c \"%s\" > %s",cimg::dcraw_path(),filename,filetmp);
+      cimg::system(command,cimg::dcraw_path());
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "load_dcraw_external() : Failed to load file '%s' with external command 'dcraw'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load_pnm(filetmp);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    static CImg<T> get_load_dcraw_external(const char *const filename) {
+      return CImg<T>().load_dcraw_external(filename);
+    }
+
+    //! Load an image using ImageMagick's or GraphicsMagick's executables.
+    CImg<T>& load_other(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "load_other() : Specified filename is (null).",
+                                    cimg_instance);
+
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try { load_magick(filename); }
+      catch (CImgException&) {
+        try { load_imagemagick_external(filename); }
+        catch (CImgException&) {
+          try { load_graphicsmagick_external(filename); }
+          catch (CImgException&) {
+            assign();
+          }
+        }
+      }
+      cimg::exception_mode() = omode;
+      if (is_empty())
+        throw CImgIOException(_cimg_instance
+                              "load_other() : Failed to load file '%s'. Format is not natively supported, and no external commands succeeded.",
+                              cimg_instance,
+                              filename);
+      return *this;
+    }
+
+    static CImg<T> get_load_other(const char *const filename) {
+      return CImg<T>().load_other(filename);
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name Data Output
+    //@{
+    //---------------------------
+
+    //! Display informations about the image on the standard error output.
+    /**
+       \param title Name for the considered image (optional).
+       \param display_stats Compute and display image statistics (optional).
+    **/
+    const CImg<T>& print(const char *const title=0, const bool display_stats=true) const {
+      int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0;
+      static CImg<doubleT> st;
+      if (!is_empty() && display_stats) {
+        st = get_stats();
+        xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7];
+        xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11];
+      }
+      const unsigned int siz = size(), msiz = siz*sizeof(T), siz1 = siz-1;
+      const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2), width1 = _width-1;
+      char ntitle[64] = { 0 };
+      if (!title) cimg_snprintf(ntitle,sizeof(ntitle),"CImg<%s>",pixel_type());
+
+      std::fprintf(cimg::output(),"%s: this = %p, size = (%u,%u,%u,%u) [%u %s], data = (%s*)%p",
+                   title?title:ntitle,(void*)this,_width,_height,_depth,_spectrum,
+                   mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
+                   mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
+                   pixel_type(),(void*)begin());
+      if (_data) std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end()-1),_is_shared?"shared":"non-shared");
+      else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared");
+
+      if (!is_empty()) cimg_foroff(*this,off) {
+        std::fprintf(cimg::output(),cimg::type<T>::format(),cimg::type<T>::format(_data[off]));
+        if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" ");
+        if (off==7 && siz>16) { off = siz1-8; if (off!=7) std::fprintf(cimg::output(),"... "); }
+      }
+      if (!is_empty() && display_stats)
+        std::fprintf(cimg::output()," ], 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(cimg::output(),"%s].\n",is_empty()?"":" ");
+      std::fflush(cimg::output());
+      return *this;
+    }
+
+    //! Display an image into a CImgDisplay window.
+    const CImg<T>& display(CImgDisplay& disp) const {
+      disp.display(*this);
+      return *this;
+    }
+
+    //! Display an image in a window with a title \p title, and wait a '_is_closed' or 'keyboard' event.\n
+    const CImg<T>& display(CImgDisplay &disp, const bool display_info) const {
+      return _display(disp,0,display_info);
+    }
+
+    //! Display an image in a window with a title \p title, and wait a '_is_closed' or 'keyboard' event.\n
+    const CImg<T>& display(const char *const title=0, const bool display_info=true) const {
+      CImgDisplay disp;
+      return _display(disp,title,display_info);
+    }
+
+    const CImg<T>& _display(CImgDisplay &disp, const char *const title, const bool display_info) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "display() : Empty instance.",
+                                    cimg_instance);
+
+      unsigned int oldw = 0, oldh = 0, XYZ[3], key = 0;
+      int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1;
+      char ntitle[256] = { 0 };
+      if (!disp) {
+        if (!title) cimg_snprintf(ntitle,sizeof(ntitle),"CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum);
+        disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:ntitle,1);
+      }
+      std::strncpy(ntitle,disp.title(),255);
+      if (display_info) print(ntitle);
+      disp.show().flush();
+
+      CImg<T> zoom;
+      for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed(); ) {
+        if (reset_view) {
+          XYZ[0] = (x0 + x1)/2; XYZ[1] = (y0 + y1)/2; XYZ[2] = (z0 + z1)/2;
+          x0 = 0; y0 = 0; z0 = 0; x1 = _width - 1; y1 = _height-1; z1 = _depth-1;
+          oldw = disp.width(); oldh = disp.height();
+          reset_view = false;
+        }
+        if (!x0 && !y0 && !z0 && x1==width()-1 && y1==height()-1 && z1==depth()-1) zoom.assign();
+        else zoom = get_crop(x0,y0,z0,x1,y1,z1);
+
+        const unsigned int
+          dx = 1 + x1 - x0, dy = 1 + y1 - y0, dz = 1 + z1 - z0,
+          tw = dx + (dz>1?dz:0), th = dy + (dz>1?dz:0);
+        if (resize_disp) {
+          const unsigned int
+            ttw = tw*disp.width()/oldw, tth = th*disp.height()/oldh,
+            dM = cimg::max(ttw,tth), diM = (unsigned int)cimg::max(disp.width(),disp.height()),
+            imgw = cimg::max(16U,ttw*diM/dM), imgh = cimg::max(16U,tth*diM/dM);
+          disp.set_fullscreen(false).resize(cimg_fitscreen(imgw,imgh,1),false);
+          resize_disp = false;
+        }
+        oldw = tw; oldh = th;
+
+        bool
+          go_up = false, go_down = false, go_left = false, go_right = false,
+          go_inc = false, go_dec = false, go_in = false, go_out = false,
+          go_in_center = false;
+        const CImg<T>& visu = zoom?zoom:*this;
+        const CImg<intT> selection = visu._get_select(disp,0,2,XYZ,0,x0,y0,z0);
+
+        if (disp.wheel()) {
+          if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { go_out = !(go_in = disp.wheel()>0); go_in_center = false; }
+          else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) { go_right = !(go_left = disp.wheel()>0); }
+          else if (disp.is_keyALT() || disp.is_keyALTGR() || _depth==1) { go_down = !(go_up = disp.wheel()>0); }
+          disp.set_wheel();
+        }
+
+        const int
+          sx0 = selection(0), sy0 = selection(1), sz0 = selection(2),
+          sx1 = selection(3), sy1 = selection(4), sz1 = selection(5);
+        if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) {
+          x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; x0+=sx0; y0+=sy0; z0+=sz0;
+          if (sx0==sx1 && sy0==sy1 && sz0==sz1) reset_view = true;
+          resize_disp = true;
+        } else switch (key = disp.key()) {
+#if cimg_OS!=2
+          case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT :
+#endif
+          case 0 : case cimg::keyCTRLLEFT : case cimg::keyPAD5 : case cimg::keySHIFTLEFT :
+#if cimg_OS!=2
+          case cimg::keyALTGR :
+#endif
+          case cimg::keyALT : key = 0; break;
+          case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) { // Special mode : play stack of frames
+          const unsigned int
+            w1 = visu._width*disp.width()/(visu._width+(visu._depth>1?visu._depth:0)),
+            h1 = visu._height*disp.height()/(visu._height+(visu._depth>1?visu._depth:0));
+          float frametiming = 5;
+          bool is_stopped = false;
+          disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0;
+          for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) {
+            if (disp.is_resized()) disp.resize(false);
+            if (!timer) {
+              visu.get_slice(XYZ[2]).display(disp.set_title("%s | z=%d",ntitle,XYZ[2]));
+              (++XYZ[2])%=visu._depth;
+            }
+            if (!is_stopped) { if (++timer>(unsigned int)frametiming) timer = 0; } else timer = ~0U;
+            if (disp.wheel()) { frametiming-=disp.wheel()/3.0f; disp.set_wheel(); }
+            switch (key = disp.key()) {
+#if cimg_OS!=2
+            case cimg::keyCTRLRIGHT :
+#endif
+            case cimg::keyCTRLLEFT :key = 0; break;
+            case cimg::keyPAGEUP : frametiming-=0.3f; key = 0; break;
+            case cimg::keyPAGEDOWN : frametiming+=0.3f; key = 0; break;
+            case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break;
+            case cimg::keyARROWLEFT : case cimg::keyARROWUP :
+              is_stopped = true; timer = 0; key = 0; break;
+            case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN :
+              is_stopped = true; (XYZ[2]+=visu._depth-2)%=visu._depth; timer = 0; key = 0; break;
+            case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+                disp.set_fullscreen(false).resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
+                                                  CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false);
+                disp.set_key(key,false); key = 0;
+              } break;
+            case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+              disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0;
+            } break;
+            case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+              disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0;
+            } break;
+            case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+              disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen().set_key(key,false); key = 0;
+            } break;
+            }
+            frametiming = frametiming<1?1:(frametiming>39?39:frametiming);
+            disp.wait(20);
+          }
+          const unsigned int
+            w2 = (visu._width + (visu._depth>1?visu._depth:0))*disp.width()/visu._width,
+            h2 = (visu._height + (visu._depth>1?visu._depth:0))*disp.height()/visu._height;
+          disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(ntitle).set_key().set_button().set_wheel();
+          key = 0;
+        } break;
+        case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break;
+        case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break;
+        case cimg::keyPADSUB : go_out = true; key = 0; break;
+        case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break;
+        case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break;
+        case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break;
+        case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break;
+        case cimg::keyPAD7 : go_up = go_left = true; key = 0; break;
+        case cimg::keyPAD9 : go_up = go_right = true; key = 0; break;
+        case cimg::keyPAD1 : go_down = go_left = true; key = 0; break;
+        case cimg::keyPAD3 : go_down = go_right = true; key = 0; break;
+        case cimg::keyPAGEUP : go_inc = true; key = 0; break;
+        case cimg::keyPAGEDOWN : go_dec = true; key = 0; break;
+        }
+        if (go_in) {
+          const int
+            mx = go_in_center?disp.width()/2:disp.mouse_x(),
+            my = go_in_center?disp.height()/2:disp.mouse_y(),
+            mX = mx*(_width+(_depth>1?_depth:0))/disp.width(),
+            mY = my*(_height+(_depth>1?_depth:0))/disp.height();
+          int X = XYZ[0], Y = XYZ[1], Z = XYZ[2];
+          if (mX<width() && mY<height())  { X = x0 + mX*(1+x1-x0)/_width; Y = y0 + mY*(1+y1-y0)/_height; Z = XYZ[2]; }
+          if (mX<width() && mY>=height()) { X = x0 + mX*(1+x1-x0)/_width; Z = z0 + (mY-_height)*(1+z1-z0)/_depth; Y = XYZ[1]; }
+          if (mX>=width() && mY<height()) { Y = y0 + mY*(1+y1-y0)/_height; Z = z0 + (mX-_width)*(1+z1-z0)/_depth; X = XYZ[0]; }
+          if (x1-x0>4) { x0 = X - 7*(X-x0)/8; x1 = X + 7*(x1-X)/8; }
+          if (y1-y0>4) { y0 = Y - 7*(Y-y0)/8; y1 = Y + 7*(y1-Y)/8; }
+          if (z1-z0>4) { z0 = Z - 7*(Z-z0)/8; z1 = Z + 7*(z1-Z)/8; }
+        }
+        if (go_out) {
+          const int
+            deltax = (x1-x0)/8, deltay = (y1-y0)/8, deltaz = (z1-z0)/8,
+            ndeltax = deltax?deltax:(_width>1?1:0),
+            ndeltay = deltay?deltay:(_height>1?1:0),
+            ndeltaz = deltaz?deltaz:(_depth>1?1:0);
+          x0-=ndeltax; y0-=ndeltay; z0-=ndeltaz;
+          x1+=ndeltax; y1+=ndeltay; z1+=ndeltaz;
+          if (x0<0) { x1-=x0; x0 = 0; if (x1>=width()) x1 = width() - 1; }
+          if (y0<0) { y1-=y0; y0 = 0; if (y1>=height()) y1 = height() - 1; }
+          if (z0<0) { z1-=z0; z0 = 0; if (z1>=depth()) z1 = depth() - 1; }
+          if (x1>=width()) { x0-=(x1-width()+1); x1 = width()-1; if (x0<0) x0 = 0; }
+          if (y1>=height()) { y0-=(y1-height()+1); y1 = height()-1; if (y0<0) y0 = 0; }
+          if (z1>=depth()) { z0-=(z1-depth()+1); z1 = depth()-1; if (z0<0) z0 = 0; }
+        }
+        if (go_left) {
+          const int delta = (x1-x0)/5, ndelta = delta?delta:(_width>1?1:0);
+          if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
+          else { x1-=x0; x0 = 0; }
+        }
+        if (go_right) {
+          const int delta = (x1-x0)/5, ndelta = delta?delta:(_width>1?1:0);
+          if (x1+ndelta<width()) { x0+=ndelta; x1+=ndelta; }
+          else { x0+=(width()-1-x1); x1 = width()-1; }
+        }
+        if (go_up) {
+          const int delta = (y1-y0)/5, ndelta = delta?delta:(_height>1?1:0);
+          if (y0-ndelta>=0) { y0-=ndelta; y1-=ndelta; }
+          else { y1-=y0; y0 = 0; }
+        }
+        if (go_down) {
+          const int delta = (y1-y0)/5, ndelta = delta?delta:(_height>1?1:0);
+          if (y1+ndelta<height()) { y0+=ndelta; y1+=ndelta; }
+          else { y0+=(height()-1-y1); y1 = height()-1; }
+        }
+        if (go_inc) {
+          const int delta = (z1-z0)/5, ndelta = delta?delta:(_depth>1?1:0);
+          if (z0-ndelta>=0) { z0-=ndelta; z1-=ndelta; }
+          else { z1-=z0; z0 = 0; }
+        }
+        if (go_dec) {
+          const int delta = (z1-z0)/5, ndelta = delta?delta:(_depth>1?1:0);
+          if (z1+ndelta<depth()) { z0+=ndelta; z1+=ndelta; }
+          else { z0+=(depth()-1-z1); z1 = depth()-1; }
+        }
+      }
+      disp.set_key(key);
+      return *this;
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp, typename tf, typename tc, typename to>
+    const CImg<T>& display_object3d(CImgDisplay& disp,
+                                    const CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const CImgList<tc>& colors,
+                                    const to& opacities,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      return _display_object3d(disp,0,vertices,primitives,colors,opacities,centering,render_static,
+                               render_motion,double_sided,focale,
+                               light_x,light_y,light_z,specular_light,specular_shine,
+                               display_axes,pose_matrix);
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp, typename tf, typename tc, typename to>
+    const CImg<T>& display_object3d(const char *const title,
+                                    const CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const CImgList<tc>& colors,
+                                    const to& opacities,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      CImgDisplay disp;
+      return _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,render_static,
+                               render_motion,double_sided,focale,
+                               light_x,light_y,light_z,specular_light,specular_shine,
+                               display_axes,pose_matrix);
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp, typename tf, typename tc>
+    const CImg<T>& display_object3d(CImgDisplay &disp,
+                                    const CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const CImgList<tc>& colors,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      return display_object3d(disp,vertices,primitives,colors,CImgList<floatT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,specular_light,specular_shine,
+                              display_axes,pose_matrix);
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp, typename tf, typename tc>
+    const CImg<T>& display_object3d(const char *const title,
+                                    const CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const CImgList<tc>& colors,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      return display_object3d(title,vertices,primitives,colors,CImgList<floatT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,specular_light,specular_shine,
+                              display_axes,pose_matrix);
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp, typename tf>
+    const CImg<T>& display_object3d(CImgDisplay &disp,
+                                    const CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      return display_object3d(disp,vertices,primitives,CImgList<T>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,specular_light,specular_shine,
+                              display_axes,pose_matrix);
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp, typename tf>
+    const CImg<T>& display_object3d(const char *const title,
+                                    const CImg<tp>& vertices,
+                                    const CImgList<tf>& primitives,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      return display_object3d(title,vertices,primitives,CImgList<T>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,specular_light,specular_shine,
+                              display_axes,pose_matrix);
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp>
+    const CImg<T>& display_object3d(CImgDisplay &disp,
+                                    const CImg<tp>& vertices,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      return display_object3d(disp,vertices,CImgList<uintT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,specular_light,specular_shine,
+                              display_axes,pose_matrix);
+    }
+
+    //! High-level interface for displaying a 3d object.
+    template<typename tp>
+    const CImg<T>& display_object3d(const char *const title,
+                                    const CImg<tp>& vertices,
+                                    const bool centering=true,
+                                    const int render_static=4, const int render_motion=1,
+                                    const bool double_sided=true, const float focale=500,
+                                    const float light_x=0, const float light_y=0, const float light_z=-5000,
+                                    const float specular_light=0.2f, const float specular_shine=0.1f,
+                                    const bool display_axes=true, float *const pose_matrix=0) const {
+      return display_object3d(title,vertices,CImgList<uintT>(),centering,
+                              render_static,render_motion,double_sided,focale,
+                              light_x,light_y,light_z,specular_light,specular_shine,
+                              display_axes,pose_matrix);
+    }
+
+    template<typename tp, typename tf, typename tc, typename to>
+    const CImg<T>& _display_object3d(CImgDisplay& disp, const char *const title,
+                                     const CImg<tp>& vertices,
+                                     const CImgList<tf>& primitives,
+                                     const CImgList<tc>& colors,
+                                     const to& opacities,
+                                     const bool centering,
+                                     const int render_static, const int render_motion,
+                                     const bool double_sided, const float focale,
+                                     const float light_x, const float light_y, const float light_z,
+                                     const float specular_light, const float specular_shine,
+                                     const bool display_axes, float *const pose_matrix) const {
+      typedef typename cimg::superset<tp,float>::type tpfloat;
+
+      // Check input arguments
+      if (is_empty()) {
+        if (disp) return CImg<T>(disp.width(),disp.height(),1,(colors && colors[0].size()==1)?1:3,0).
+                    _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,
+                                      render_static,render_motion,double_sided,focale,
+                                      light_x,light_y,light_z,specular_light,specular_shine,
+                                      display_axes,pose_matrix);
+        else return CImg<T>(cimg_fitscreen(640,480,1),1,(colors && colors[0].size()==1)?1:3,0).
+               _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,
+                                 render_static,render_motion,double_sided,focale,
+                                 light_x,light_y,light_z,specular_light,specular_shine,
+                                 display_axes,pose_matrix);
+      } else { if (disp) disp.resize(*this,false); }
+      char error_message[1024] = { 0 };
+      if (!vertices.is_object3d(primitives,colors,opacities,true,error_message))
+        throw CImgArgumentException(_cimg_instance
+                                    "display_object3d() : Invalid specified 3d object (%u,%u) (%s).",
+                                    cimg_instance,vertices._width,primitives._width,error_message);
+      if (vertices._width && !primitives) {
+        CImgList<tf> nprimitives(vertices._width,1,1,1,1);
+        cimglist_for(nprimitives,l) nprimitives(l,0) = l;
+        return _display_object3d(disp,title,vertices,nprimitives,colors,opacities,centering,
+                                 render_static,render_motion,double_sided,focale,
+                                 light_x,light_y,light_z,specular_light,specular_shine,
+                                 display_axes,pose_matrix);
+      }
+      if (!disp) {
+        char ntitle[256] = { 0 };
+        if (!title) { cimg_snprintf(ntitle,sizeof(ntitle),"CImg<%s> (%u vertices, %u primitives)",pixel_type(),vertices._width,primitives._width); }
+        disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:ntitle,3);
+      }
+
+      // Init 3d objects and compute object statistics
+      CImg<floatT>
+        pose,
+        rotated_vertices(vertices._width,3),
+        bbox_vertices, rotated_bbox_vertices,
+        axes_vertices, rotated_axes_vertices,
+        bbox_opacities, axes_opacities;
+      CImgList<uintT> bbox_primitives, axes_primitives;
+      CImgList<tf> reverse_primitives;
+      CImgList<T> bbox_colors, bbox_colors2, axes_colors;
+
+      T minval = (T)0, maxval = (T)255;
+      if (disp.normalization() && colors) {
+        minval = colors.min_max(maxval);
+        if (minval==maxval) { minval = (T)0; maxval = (T)255; }
+      }
+      unsigned int ns_width = 0, ns_height = 0;
+      int _double_sided = (int)double_sided;
+      bool color_model = true, ndisplay_axes = display_axes;
+      const float meanval = (float)mean();
+      if (cimg::abs(meanval-minval)>cimg::abs(meanval-maxval)) color_model = false;
+      const CImg<T>
+        background_color(1,1,1,_spectrum,color_model?minval:maxval),
+        foreground_color(1,1,1,_spectrum,color_model?maxval:minval);
+      float
+        Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1,
+        xm = 0, xM = vertices?vertices.get_shared_line(0).max_min(xm):0,
+        ym = 0, yM = vertices?vertices.get_shared_line(1).max_min(ym):0,
+        zm = 0, zM = vertices?vertices.get_shared_line(2).max_min(zm):0;
+      const float delta = cimg::max(xM-xm,yM-ym,zM-zm);
+
+      rotated_bbox_vertices = bbox_vertices.assign(8,3,1,1,
+                                                   xm,xM,xM,xm,xm,xM,xM,xm,
+                                                   ym,ym,yM,yM,ym,ym,yM,yM,
+                                                   zm,zm,zm,zm,zM,zM,zM,zM);
+      bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6);
+      bbox_colors.assign(6,_spectrum,1,1,1,background_color[0]);
+      bbox_colors2.assign(6,_spectrum,1,1,1,foreground_color[0]);
+      bbox_opacities.assign(bbox_colors._width,1,1,1,0.3f);
+
+      rotated_axes_vertices = axes_vertices.assign(7,3,1,1,
+                                                   0,20,0,0,22,-6,-6,
+                                                   0,0,20,0,-6,22,-6,
+                                                   0,0,0,20,0,0,22);
+      axes_opacities.assign(3,1,1,1,1);
+      axes_colors.assign(3,_spectrum,1,1,1,foreground_color[0]);
+      axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3);
+
+      // Begin user interaction loop
+      CImg<T> visu0(*this), visu;
+      CImg<tpfloat> zbuffer(visu0.width(),visu0.height(),1,1,0);
+      bool init_pose = true, clicked = false, redraw = true;
+      unsigned int key = 0;
+      int
+        x0 = 0, y0 = 0, x1 = 0, y1 = 0,
+        nrender_static = render_static,
+        nrender_motion = render_motion;
+      disp.show().flush();
+
+      while (!disp.is_closed() && !key) {
+
+        // Init object pose
+        if (init_pose) {
+          const float
+            ratio = delta>0?(2.0f*cimg::min(disp.width(),disp.height())/(3.0f*delta)):1,
+            dx = (xM + xm)/2, dy = (yM + ym)/2, dz = (zM + zm)/2;
+          if (centering)
+            pose = CImg<floatT>(4,3,1,1, ratio,0.,0.,-ratio*dx, 0.,ratio,0.,-ratio*dy, 0.,0.,ratio,-ratio*dz);
+          else pose = CImg<floatT>(4,3,1,1, 1.,0.,0.,0., 0.,1.,0.,0., 0.,0.,1.,0.);
+          if (pose_matrix) {
+            CImg<floatT> pose0(pose_matrix,4,3,1,1,false);
+            pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0);
+            pose0(3,3) = pose(3,3) = 1;
+            (pose0*pose).get_crop(0,0,3,2).move_to(pose);
+            Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15];
+          } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; }
+          init_pose = false;
+          redraw = true;
+        }
+
+        // Rotate and draw 3d object
+        if (redraw) {
+          const float
+            r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0),
+            r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1),
+            r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2);
+          if ((clicked && nrender_motion>=0) || (!clicked && nrender_static>=0))
+            cimg_forX(vertices,l) {
+              const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2);
+              rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30;
+              rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31;
+              rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32;
+            }
+          else cimg_forX(bbox_vertices,l) {
+              const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2);
+              rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30;
+              rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31;
+              rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32;
+            }
+
+          // Draw object
+          visu = visu0;
+          if ((clicked && nrender_motion<0) || (!clicked && nrender_static<0))
+            visu.draw_object3d(Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                               rotated_bbox_vertices,bbox_primitives,bbox_colors,bbox_opacities,2,false,focale).
+              draw_object3d(Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                            rotated_bbox_vertices,bbox_primitives,bbox_colors2,1,false,focale);
+          else visu._draw_object3d((void*)0,(!clicked && nrender_static>0)?zbuffer.fill(0):CImg<tpfloat>::empty(),
+                                   Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                                   rotated_vertices,reverse_primitives?reverse_primitives:primitives,
+                                   colors,opacities,clicked?nrender_motion:nrender_static,_double_sided==1,focale,
+                                   width()/2.0f+light_x,height()/2.0f+light_y,light_z,specular_light,specular_shine,
+                                   sprite_scale);
+          // Draw axes
+          if (ndisplay_axes) {
+            const float
+              n = (float)std::sqrt(1e-8 + r00*r00 + r01*r01 + r02*r02),
+              _r00 = r00/n, _r10 = r10/n, _r20 = r20/n,
+              _r01 = r01/n, _r11 = r11/n, _r21 = r21/n,
+              _r02 = r01/n, _r12 = r12/n, _r22 = r22/n,
+              Xaxes = 25, Yaxes = visu._height - 38.0f;
+            cimg_forX(axes_vertices,l) {
+              const float
+                x = axes_vertices(l,0),
+                y = axes_vertices(l,1),
+                z = axes_vertices(l,2);
+              rotated_axes_vertices(l,0) = _r00*x + _r10*y + _r20*z;
+              rotated_axes_vertices(l,1) = _r01*x + _r11*y + _r21*z;
+              rotated_axes_vertices(l,2) = _r02*x + _r12*y + _r22*z;
+            }
+            axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.0f;
+            axes_opacities(1,0) = (rotated_axes_vertices(2,2)>0)?0.5f:1.0f;
+            axes_opacities(2,0) = (rotated_axes_vertices(3,2)>0)?0.5f:1.0f;
+            visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_vertices,axes_primitives,axes_colors,axes_opacities,1,false,focale).
+              draw_text((int)(Xaxes+rotated_axes_vertices(4,0)),
+                        (int)(Yaxes+rotated_axes_vertices(4,1)),
+                        "X",axes_colors[0]._data,0,axes_opacities(0,0),13).
+              draw_text((int)(Xaxes+rotated_axes_vertices(5,0)),
+                        (int)(Yaxes+rotated_axes_vertices(5,1)),
+                        "Y",axes_colors[1]._data,0,axes_opacities(1,0),13).
+              draw_text((int)(Xaxes+rotated_axes_vertices(6,0)),
+                        (int)(Yaxes+rotated_axes_vertices(6,1)),
+                        "Z",axes_colors[2]._data,0,axes_opacities(2,0),13);
+          }
+          visu.display(disp);
+          if (!clicked || nrender_motion==nrender_static) redraw = false;
+        }
+
+        // Handle user interaction
+        disp.wait();
+        if ((disp.button() || disp.wheel()) && disp.mouse_x()>=0 && disp.mouse_y()>=0) {
+          redraw = true;
+          if (!clicked) { x0 = x1 = disp.mouse_x(); y0 = y1 = disp.mouse_y(); if (!disp.wheel()) clicked = true; }
+          else { x1 = disp.mouse_x(); y1 = disp.mouse_y(); }
+          if (disp.button()&1) {
+            const float
+              R = 0.45f*cimg::min(disp.width(),disp.height()),
+              R2 = R*R,
+              u0 = (float)(x0-disp.width()/2),
+              v0 = (float)(y0-disp.height()/2),
+              u1 = (float)(x1-disp.width()/2),
+              v1 = (float)(y1-disp.height()/2),
+              n0 = (float)std::sqrt(u0*u0+v0*v0),
+              n1 = (float)std::sqrt(u1*u1+v1*v1),
+              nu0 = n0>R?(u0*R/n0):u0,
+              nv0 = n0>R?(v0*R/n0):v0,
+              nw0 = (float)std::sqrt(cimg::max(0,R2-nu0*nu0-nv0*nv0)),
+              nu1 = n1>R?(u1*R/n1):u1,
+              nv1 = n1>R?(v1*R/n1):v1,
+              nw1 = (float)std::sqrt(cimg::max(0,R2-nu1*nu1-nv1*nv1)),
+              u = nv0*nw1-nw0*nv1,
+              v = nw0*nu1-nu0*nw1,
+              w = nv0*nu1-nu0*nv1,
+              n = (float)std::sqrt(u*u+v*v+w*w),
+              alpha = (float)std::asin(n/R2);
+            (CImg<floatT>::rotation_matrix(u,v,w,alpha)*pose).move_to(pose);
+            x0 = x1; y0 = y1;
+          }
+          if (disp.button()&2) {
+            if (focale>0) Zoff-=(y0-y1)*focale/400;
+            else { const float s = std::exp((y0-y1)/400.0f); pose*=s; sprite_scale*=s; }
+            x0 = x1; y0 = y1;
+          }
+          if (disp.wheel()) {
+            if (focale>0) Zoff-=disp.wheel()*focale/20;
+            else { const float s = std::exp(disp.wheel()/20.0f); pose*=s; sprite_scale*=s; }
+            disp.set_wheel();
+          }
+          if (disp.button()&4) { Xoff+=(x1-x0); Yoff+=(y1-y0); x0 = x1; y0 = y1; }
+          if ((disp.button()&1) && (disp.button()&2)) {
+            init_pose = true; disp.set_button(); x0 = x1; y0 = y1;
+            pose = CImg<floatT>(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0);
+          }
+        } else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; }
+
+        switch (key = disp.key()) {
+#if cimg_OS!=2
+        case cimg::keyCTRLRIGHT :
+#endif
+        case 0 : case cimg::keyCTRLLEFT : key = 0; break;
+        case cimg::keyD: if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
+                                              CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false).
+              _is_resized = true;
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true;
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+            if (!ns_width || !ns_height ||
+                ns_width>(unsigned int)disp.screen_width() || ns_height>(unsigned int)disp.screen_height()) {
+              ns_width = disp.screen_width()*3U/4;
+              ns_height = disp.screen_height()*3U/4;
+            }
+            if (disp.is_fullscreen()) disp.resize(ns_width,ns_height,false);
+            else {
+              ns_width = (unsigned int)disp.width(); ns_height = disp.height();
+              disp.resize(disp.screen_width(),disp.screen_height(),false);
+            }
+            disp.toggle_fullscreen()._is_resized = true;
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyT : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Switch single/double-sided primitives.
+            if (--_double_sided==-2) _double_sided = 1;
+            if (_double_sided>=0) reverse_primitives.assign();
+            else primitives.get_reverse_object3d().move_to(reverse_primitives);
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyZ : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Enable/disable Z-buffer
+            if (zbuffer) zbuffer.assign();
+            else zbuffer.assign(visu0.width(),visu0.height(),1,1,0);
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Show/hide 3d axes.
+            ndisplay_axes = !ndisplay_axes;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF1 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to points.
+            nrender_motion = (nrender_static==0 && nrender_motion!=0)?0:-1; nrender_static = 0;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF2 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to lines.
+            nrender_motion = (nrender_static==1 && nrender_motion!=1)?1:-1; nrender_static = 1;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF3 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat.
+            nrender_motion = (nrender_static==2 && nrender_motion!=2)?2:-1; nrender_static = 2;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF4 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to flat-shaded.
+            nrender_motion = (nrender_static==3 && nrender_motion!=3)?3:-1; nrender_static = 3;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF5 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to gouraud-shaded.
+            nrender_motion = (nrender_static==4 && nrender_motion!=4)?4:-1; nrender_static = 4;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyF6 : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Set rendering mode to phong-shaded.
+            nrender_motion = (nrender_static==5 && nrender_motion!=5)?5:-1; nrender_static = 5;
+            disp.set_key(key,false); key = 0; redraw = true;
+          } break;
+        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save snapshot
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.bmp",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+            } while (file);
+            (+visu).draw_text(0,0," Saving snapshot... ",foreground_color._data,background_color._data,1,13).display(disp);
+            visu.save(filename);
+            visu.draw_text(0,0," Snapshot '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyG : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .off file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.off",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving object... ",foreground_color._data,background_color._data,1,13).display(disp);
+            vertices.save_off(filename,reverse_primitives?reverse_primitives:primitives,colors);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+#ifdef cimg_use_zlib
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.cimgz",snap_number++);
+#else
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.cimg",snap_number++);
+#endif
+              if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving object... ",foreground_color._data,background_color._data,1,13).display(disp);
+            vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities).save(filename);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+#ifdef cimg_use_board
+        case cimg::keyP : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .EPS file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.eps",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving EPS snapshot... ",foreground_color._data,background_color._data,1,13).display(disp);
+            LibBoard::Board board;
+            (+visu)._draw_object3d(&board,zbuffer.fill(0),
+                                   Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                                   rotated_vertices,reverse_primitives?reverse_primitives:primitives,
+                                   colors,opacities,clicked?nrender_motion:nrender_static,
+                                   _double_sided==1,focale,
+                                   visu.width()/2.0f+light_x,visu.height()/2.0f+light_y,light_z,specular_light,specular_shine,
+                                   sprite_scale);
+            board.saveEPS(filename);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+        case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .SVG file
+            static unsigned int snap_number = 0;
+            char filename[32] = { 0 };
+            std::FILE *file;
+            do {
+              cimg_snprintf(filename,sizeof(filename),"CImg_%.4u.svg",snap_number++);
+              if ((file=std::fopen(filename,"r"))!=0) std::fclose(file);
+            } while (file);
+            visu.draw_text(0,0," Saving SVG snapshot... ",foreground_color._data,background_color._data,1,13).display(disp);
+            LibBoard::Board board;
+            (+visu)._draw_object3d(&board,zbuffer.fill(0),
+                                   Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff,
+                                   rotated_vertices,reverse_primitives?reverse_primitives:primitives,
+                                   colors,opacities,clicked?nrender_motion:nrender_static,
+                                   _double_sided==1,focale,
+                                   visu.width()/2.0f+light_x,visu.height()/2.0f+light_y,light_z,specular_light,specular_shine,
+                                   sprite_scale);
+            board.saveSVG(filename);
+            visu.draw_text(0,0," Object '%s' saved. ",foreground_color._data,background_color._data,1,13,filename).display(disp);
+            disp.set_key(key,false); key = 0;
+          } break;
+#endif
+        }
+        if (disp.is_resized()) {
+          disp.resize(false); visu0 = get_resize(disp,1);
+          if (zbuffer) zbuffer.assign(disp.width(),disp.height());
+          redraw = true;
+        }
+      }
+      if (pose_matrix) {
+        std::memcpy(pose_matrix,pose._data,12*sizeof(float));
+        pose_matrix[12] = Xoff; pose_matrix[13] = Yoff; pose_matrix[14] = Zoff; pose_matrix[15] = sprite_scale;
+      }
+      disp.set_button().set_key(key);
+      return *this;
+    }
+
+    //! High-level interface for displaying a graph.
+    const CImg<T>& display_graph(CImgDisplay &disp,
+                                 const unsigned int plot_type=1, const unsigned int vertex_type=1,
+                                 const char *const labelx=0, const double xmin=0, const double xmax=0,
+                                 const char *const labely=0, const double ymin=0, const double ymax=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "display_graph() : Empty instance.",
+                                    cimg_instance);
+      const unsigned int siz = _width*_height*_depth, onormalization = disp.normalization();
+      if (!disp) { char ntitle[64] = { 0 }; cimg_snprintf(ntitle,sizeof(ntitle),"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
+      disp.show().flush()._normalization = 0;
+      double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax;
+      if (nxmin==nxmax) { nxmin = 0; nxmax = siz; }
+      int x0 = 0, x1 = width()*height()*depth() - 1, key = 0;
+
+      for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed(); ) {
+        if (reset_view) { x0 = 0; x1 = width()*height()*depth()-1; y0 = ymin; y1 = ymax; reset_view = false; }
+        CImg<T> zoom(x1-x0+1,1,1,spectrum());
+        cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg<T>(data(x0,0,0,c),x1-x0+1,1,1,1,true);
+
+        if (y0==y1) y0 = zoom.min_max(y1);
+        if (y0==y1) { --y0; ++y1; }
+        const CImg<intT> selection = zoom.get_select_graph(disp,plot_type,vertex_type,
+                                                           labelx,nxmin + x0*(nxmax-nxmin)/siz,nxmin + (x1+1)*(nxmax-nxmin)/siz,
+                                                           labely,y0,y1);
+
+        const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y();
+        if (selection[0]>=0 && selection[2]>=0) {
+          x1 = x0 + selection[2];
+          x0+=selection[0];
+          if (x0==x1) reset_view = true;
+          if (selection[1]>=0 && selection[3]>=0) {
+            y0 = y1 - selection[3]*(y1-y0)/(disp.height()-32);
+            y1-=selection[1]*(y1-y0)/(disp.height()-32);
+          }
+        } else {
+          bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false;
+          switch (key = disp.key()) {
+          case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; disp.set_key(); break;
+          case cimg::keyPADADD : go_in = true; go_out = false; key = 0; disp.set_key(); break;
+          case cimg::keyPADSUB : go_out = true; go_in = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; go_right = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; go_left = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; go_down = false; key = 0; disp.set_key(); break;
+          case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; go_up = false; key = 0; disp.set_key(); break;
+          case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; disp.set_key(); break;
+          case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; disp.set_key(); break;
+          case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; disp.set_key(); break;
+          case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; disp.set_key(); break;
+          }
+          if (disp.wheel()) {
+            if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) go_out = !(go_in = disp.wheel()>0);
+            else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0);
+            else go_up = !(go_down = disp.wheel()<0);
+            key = 0;
+          }
+
+          if (go_in) {
+            const int
+              xsiz = x1 - x0,
+              mx = (mouse_x-16)*xsiz/(disp.width()-32),
+              cx = x0 + (mx<0?0:(mx>=xsiz?xsiz:mx));
+            if (x1-x0>4) {
+              x0 = cx - 7*(cx-x0)/8; x1 = cx + 7*(x1-cx)/8;
+              if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+                const double
+                  ysiz = y1 - y0,
+                  my = (mouse_y-16)*ysiz/(disp.height()-32),
+                  cy = y1 - (my<0?0:(my>=ysiz?ysiz:my));
+                y0 = cy - 7*(cy-y0)/8; y1 = cy + 7*(y1-cy)/8;
+              } else y0 = y1 = 0;
+            }
+          }
+          if (go_out) {
+            if (x0>0 || x1<(int)siz-1) {
+              const int deltax = (x1-x0)/8, ndeltax = deltax?deltax:(siz>1?1:0);
+              const double ndeltay = (y1-y0)/8;
+              x0-=ndeltax; x1+=ndeltax;
+              y0-=ndeltay; y1+=ndeltay;
+              if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz-1; }
+              if (x1>=(int)siz) { x0-=(x1-siz+1); x1 = (int)siz-1; if (x0<0) x0 = 0; }
+            }
+          }
+          if (go_left) {
+            const int delta = (x1-x0)/5, ndelta = delta?delta:1;
+            if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
+            else { x1-=x0; x0 = 0; }
+            go_left = false;
+          }
+          if (go_right) {
+            const int delta = (x1-x0)/5, ndelta = delta?delta:1;
+            if (x1+ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; }
+            else { x0+=(siz-1-x1); x1 = siz-1; }
+            go_right = false;
+          }
+          if (go_up) {
+            const double delta = (y1-y0)/10, ndelta = delta?delta:1;
+            y0+=ndelta; y1+=ndelta;
+            go_up = false;
+          }
+          if (go_down) {
+            const double delta = (y1-y0)/10, ndelta = delta?delta:1;
+            y0-=ndelta; y1-=ndelta;
+            go_down = false;
+          }
+        }
+      }
+      disp._normalization = onormalization;
+      return *this;
+    }
+
+    //! High-level interface for displaying a graph.
+    const CImg<T>& display_graph(const char *const title=0,
+                                 const unsigned int plot_type=1, const unsigned int vertex_type=1,
+                                 const char *const labelx=0, const double xmin=0, const double xmax=0,
+                                 const char *const labely=0, const double ymin=0, const double ymax=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "display_graph() : Empty instance.",
+                                    cimg_instance);
+
+      char ntitle[64] = { 0 }; if (!title) cimg_snprintf(ntitle,sizeof(ntitle),"CImg<%s>",pixel_type());
+      CImgDisplay disp(cimg_fitscreen(640,480,1),title?title:ntitle,0);
+      return display_graph(disp,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax);
+    }
+
+    //! Save the image as a file.
+    /**
+       The used file format is defined by the file extension in the filename \p filename.
+       Parameter \p number can be used to add a 6-digit number to the filename before saving.
+    **/
+    const CImg<T>& save(const char *const filename, const int number=-1) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save() : Specified filename is (null).",
+                                    cimg_instance);
+      // Do not test for empty instances, since .cimg format is able to manage empty instances.
+      const char *const ext = cimg::split_filename(filename);
+      char nfilename[1024] = { 0 };
+      const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
+#ifdef cimg_save_plugin
+      cimg_save_plugin(fn);
+#endif
+#ifdef cimg_save_plugin1
+      cimg_save_plugin1(fn);
+#endif
+#ifdef cimg_save_plugin2
+      cimg_save_plugin2(fn);
+#endif
+#ifdef cimg_save_plugin3
+      cimg_save_plugin3(fn);
+#endif
+#ifdef cimg_save_plugin4
+      cimg_save_plugin4(fn);
+#endif
+#ifdef cimg_save_plugin5
+      cimg_save_plugin5(fn);
+#endif
+#ifdef cimg_save_plugin6
+      cimg_save_plugin6(fn);
+#endif
+#ifdef cimg_save_plugin7
+      cimg_save_plugin7(fn);
+#endif
+#ifdef cimg_save_plugin8
+      cimg_save_plugin8(fn);
+#endif
+      // ASCII formats
+      if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn);
+      else if (!cimg::strcasecmp(ext,"dlm") ||
+               !cimg::strcasecmp(ext,"txt")) return save_dlm(fn);
+      else if (!cimg::strcasecmp(ext,"cpp") ||
+               !cimg::strcasecmp(ext,"hpp") ||
+               !cimg::strcasecmp(ext,"h") ||
+               !cimg::strcasecmp(ext,"c")) return save_cpp(fn);
+
+      // 2d binary formats
+      else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn);
+      else if (!cimg::strcasecmp(ext,"jpg") ||
+               !cimg::strcasecmp(ext,"jpeg") ||
+               !cimg::strcasecmp(ext,"jpe") ||
+               !cimg::strcasecmp(ext,"jfif") ||
+               !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn);
+      else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn);
+      else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn);
+      else if (!cimg::strcasecmp(ext,"png")) return save_png(fn);
+      else if (!cimg::strcasecmp(ext,"pgm") ||
+               !cimg::strcasecmp(ext,"ppm") ||
+               !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn);
+      else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn);
+      else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn);
+      else if (!cimg::strcasecmp(ext,"tif") ||
+               !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
+
+      // 3d binary formats
+      else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
+      else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false);
+      else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn);
+      else if (!cimg::strcasecmp(ext,"hdr") ||
+               !cimg::strcasecmp(ext,"nii")) return save_analyze(fn);
+      else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn);
+      else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn);
+      else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn);
+
+      // Archive files
+      else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
+
+      // Image sequences
+      else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
+      else if (!cimg::strcasecmp(ext,"avi") ||
+               !cimg::strcasecmp(ext,"mov") ||
+               !cimg::strcasecmp(ext,"asf") ||
+               !cimg::strcasecmp(ext,"divx") ||
+               !cimg::strcasecmp(ext,"flv") ||
+               !cimg::strcasecmp(ext,"mpg") ||
+               !cimg::strcasecmp(ext,"m1v") ||
+               !cimg::strcasecmp(ext,"m2v") ||
+               !cimg::strcasecmp(ext,"m4v") ||
+               !cimg::strcasecmp(ext,"mjp") ||
+               !cimg::strcasecmp(ext,"mkv") ||
+               !cimg::strcasecmp(ext,"mpe") ||
+               !cimg::strcasecmp(ext,"movie") ||
+               !cimg::strcasecmp(ext,"ogm") ||
+               !cimg::strcasecmp(ext,"ogg") ||
+               !cimg::strcasecmp(ext,"qt") ||
+               !cimg::strcasecmp(ext,"rm") ||
+               !cimg::strcasecmp(ext,"vob") ||
+               !cimg::strcasecmp(ext,"wmv") ||
+               !cimg::strcasecmp(ext,"xvid") ||
+               !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
+      return save_other(fn);
+    }
+
+    // Save the image as an ASCII file (ASCII Raw + simple header) (internal).
+    const CImg<T>& _save_ascii(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ascii() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_ascii() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      std::fprintf(nfile,"%u %u %u %u\n",_width,_height,_depth,_spectrum);
+      const T* ptrs = _data;
+      cimg_forYZC(*this,y,z,c) {
+        cimg_forX(*this,x) std::fprintf(nfile,"%g ",(double)*(ptrs++));
+        std::fputc('\n',nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as an ASCII file (ASCII Raw + simple header).
+    const CImg<T>& save_ascii(const char *const filename) const {
+      return _save_ascii(0,filename);
+    }
+
+    //! Save the image as an ASCII file (ASCII Raw + simple header).
+    const CImg<T>& save_ascii(std::FILE *const file) const {
+      return _save_ascii(file,0);
+    }
+
+    // Save the image as a C or CPP source file (internal).
+    const CImg<T>& _save_cpp(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_cpp() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_cpp() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      char varname[1024] = { 0 };
+      if (filename) std::sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname);
+      if (!*varname) cimg_snprintf(varname,sizeof(varname),"unnamed");
+      std::fprintf(nfile,
+                   "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n"
+                   "%s data_%s[] = { \n  ",
+                   varname,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname);
+      for (unsigned int off = 0, siz = size()-1; off<=siz; ++off) {
+        std::fprintf(nfile,cimg::type<T>::format(),cimg::type<T>::format((*this)[off]));
+        if (off==siz) std::fprintf(nfile," };\n");
+        else if (!((off+1)%16)) std::fprintf(nfile,",\n  ");
+        else std::fprintf(nfile,", ");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a CPP source file.
+    const CImg<T>& save_cpp(const char *const filename) const {
+      return _save_cpp(0,filename);
+    }
+
+    //! Save the image as a CPP source file.
+    const CImg<T>& save_cpp(std::FILE *const file) const {
+      return _save_cpp(file,0);
+    }
+
+    // Save the image as a DLM file (internal).
+    const CImg<T>& _save_dlm(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_dlm() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_dlm() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_dlm() : Instance is volumetric, values along Z will be unrolled in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>1)
+        cimg::warn(_cimg_instance
+                   "save_dlm() : Instance is multispectral, values along C will be unrolled in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      const T* ptrs = _data;
+      cimg_forYZC(*this,y,z,c) {
+        cimg_forX(*this,x) std::fprintf(nfile,"%g%s",(double)*(ptrs++),(x==width()-1)?"":",");
+        std::fputc('\n',nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a DLM file.
+    const CImg<T>& save_dlm(const char *const filename) const {
+      return _save_dlm(0,filename);
+    }
+
+    //! Save the image as a DLM file.
+    const CImg<T>& save_dlm(std::FILE *const file) const {
+      return _save_dlm(file,0);
+    }
+
+   // Save the image as a BMP file (internal).
+    const CImg<T>& _save_bmp(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_bmp() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_bmp() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_bmp() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_bmp() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      unsigned char header[54] = { 0 }, align_buf[4] = { 0 };
+      const unsigned int
+        align = (4 - (3*_width)%4)%4,
+        buf_size = (3*_width+align)*height(),
+        file_size = 54 + buf_size;
+      header[0] = 'B'; header[1] = 'M';
+      header[0x02] = file_size&0xFF;
+      header[0x03] = (file_size>>8)&0xFF;
+      header[0x04] = (file_size>>16)&0xFF;
+      header[0x05] = (file_size>>24)&0xFF;
+      header[0x0A] = 0x36;
+      header[0x0E] = 0x28;
+      header[0x12] = _width&0xFF;
+      header[0x13] = (_width>>8)&0xFF;
+      header[0x14] = (_width>>16)&0xFF;
+      header[0x15] = (_width>>24)&0xFF;
+      header[0x16] = _height&0xFF;
+      header[0x17] = (_height>>8)&0xFF;
+      header[0x18] = (_height>>16)&0xFF;
+      header[0x19] = (_height>>24)&0xFF;
+      header[0x1A] = 1;
+      header[0x1B] = 0;
+      header[0x1C] = 24;
+      header[0x1D] = 0;
+      header[0x22] = buf_size&0xFF;
+      header[0x23] = (buf_size>>8)&0xFF;
+      header[0x24] = (buf_size>>16)&0xFF;
+      header[0x25] = (buf_size>>24)&0xFF;
+      header[0x27] = 0x1;
+      header[0x2B] = 0x1;
+      cimg::fwrite(header,54,nfile);
+
+      const T
+        *ptr_r = data(0,_height-1,0,0),
+        *ptr_g = (_spectrum>=2)?data(0,_height-1,0,1):0,
+        *ptr_b = (_spectrum>=3)?data(0,_height-1,0,2):0;
+
+      switch (_spectrum) {
+      case 1 : {
+        cimg_forY(*this,y) {
+          cimg_forX(*this,x) {
+            const unsigned char val = (unsigned char)*(ptr_r++);
+            std::fputc(val,nfile); std::fputc(val,nfile); std::fputc(val,nfile);
+          }
+          cimg::fwrite(align_buf,align,nfile);
+          ptr_r-=2*_width;
+        }
+      } break;
+      case 2 : {
+        cimg_forY(*this,y) {
+          cimg_forX(*this,x) {
+            std::fputc(0,nfile);
+            std::fputc((unsigned char)(*(ptr_g++)),nfile);
+            std::fputc((unsigned char)(*(ptr_r++)),nfile);
+          }
+          cimg::fwrite(align_buf,align,nfile);
+          ptr_r-=2*_width; ptr_g-=2*_width;
+        }
+      } break;
+      default : {
+        cimg_forY(*this,y) {
+          cimg_forX(*this,x) {
+            std::fputc((unsigned char)(*(ptr_b++)),nfile);
+            std::fputc((unsigned char)(*(ptr_g++)),nfile);
+            std::fputc((unsigned char)(*(ptr_r++)),nfile);
+          }
+          cimg::fwrite(align_buf,align,nfile);
+          ptr_r-=2*_width; ptr_g-=2*_width; ptr_b-=2*_width;
+        }
+      }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a BMP file.
+    const CImg<T>& save_bmp(const char *const filename) const {
+      return _save_bmp(0,filename);
+    }
+
+    //! Save the image as a BMP file.
+    const CImg<T>& save_bmp(std::FILE *const file) const {
+      return _save_bmp(file,0);
+    }
+
+    // Save a file in JPEG format (internal).
+    const CImg<T>& _save_jpeg(std::FILE *const file, const char *const filename, const unsigned int quality) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_jpeg() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_jpeg() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_jpeg() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+#ifndef cimg_use_jpeg
+      if (!file) return save_other(filename,quality);
+      else throw CImgIOException(_cimg_instance
+                                 "save_jpeg() : Unable to save data in '(*FILE)' unless libjpeg is enabled.",
+                                 cimg_instance);
+#else
+      unsigned int dimbuf = 0;
+      J_COLOR_SPACE colortype = JCS_RGB;
+
+      switch(_spectrum) {
+      case 1 : dimbuf = 1; colortype = JCS_GRAYSCALE; break;
+      case 2 : dimbuf = 3; colortype = JCS_RGB; break;
+      case 3 : dimbuf = 3; colortype = JCS_RGB; break;
+      default: dimbuf = 4; colortype = JCS_CMYK; break;
+      }
+
+      // Call libjpeg functions
+      struct jpeg_compress_struct cinfo;
+      struct jpeg_error_mgr jerr;
+      cinfo.err = jpeg_std_error(&jerr);
+      jpeg_create_compress(&cinfo);
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      jpeg_stdio_dest(&cinfo,nfile);
+      cinfo.image_width = _width;
+      cinfo.image_height = _height;
+      cinfo.input_components = dimbuf;
+      cinfo.in_color_space = colortype;
+      jpeg_set_defaults(&cinfo);
+      jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE);
+      jpeg_start_compress(&cinfo,TRUE);
+
+      JSAMPROW row_pointer[1];
+      CImg<ucharT> buffer(_width*dimbuf);
+
+      while (cinfo.next_scanline < cinfo.image_height) {
+        unsigned char *ptrd = buffer._data;
+
+        // Fill pixel buffer
+        switch (_spectrum) {
+        case 1 : { // Greyscale images
+          const T *ptr_g = data(0, cinfo.next_scanline);
+          for(unsigned int b = 0; b < cinfo.image_width; b++)
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+        } break;
+        case 2 : { // RG images
+          const T *ptr_r = data(0,cinfo.next_scanline,0,0),
+            *ptr_g = data(0,cinfo.next_scanline,0,1);
+          for(unsigned int b = 0; b < cinfo.image_width; ++b) {
+            *(ptrd++) = (unsigned char)*(ptr_r++);
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+            *(ptrd++) = 0;
+          }
+        } break;
+        case 3 : { // RGB images
+          const T *ptr_r = data(0,cinfo.next_scanline,0,0),
+            *ptr_g = data(0,cinfo.next_scanline,0,1),
+            *ptr_b = data(0,cinfo.next_scanline,0,2);
+          for(unsigned int b = 0; b < cinfo.image_width; ++b) {
+            *(ptrd++) = (unsigned char)*(ptr_r++);
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+            *(ptrd++) = (unsigned char)*(ptr_b++);
+          }
+        } break;
+        default : { // CMYK images
+          const T *ptr_r = data(0,cinfo.next_scanline,0,0),
+            *ptr_g = data(0,cinfo.next_scanline,0,1),
+            *ptr_b = data(0,cinfo.next_scanline,0,2),
+            *ptr_a = data(0,cinfo.next_scanline,0,3);
+          for(unsigned int b = 0; b < cinfo.image_width; ++b) {
+            *(ptrd++) = (unsigned char)*(ptr_r++);
+            *(ptrd++) = (unsigned char)*(ptr_g++);
+            *(ptrd++) = (unsigned char)*(ptr_b++);
+            *(ptrd++) = (unsigned char)*(ptr_a++);
+          }
+        }
+        }
+        *row_pointer = buffer._data;
+        jpeg_write_scanlines(&cinfo,row_pointer,1);
+      }
+      jpeg_finish_compress(&cinfo);
+      if (!file) cimg::fclose(nfile);
+      jpeg_destroy_compress(&cinfo);
+      return *this;
+#endif
+    }
+
+    //! Save a file in JPEG format.
+    const CImg<T>& save_jpeg(const char *const filename, const unsigned int quality=100) const {
+      return _save_jpeg(0,filename,quality);
+    }
+
+    //! Save a file in JPEG format.
+    const CImg<T>& save_jpeg(std::FILE *const file, const unsigned int quality=100) const {
+      return _save_jpeg(file,0,quality);
+    }
+
+    //! Save the image using built-in ImageMagick++ library.
+    const CImg<T>& save_magick(const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_magick() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_magick() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      unsigned int foo = bytes_per_pixel; foo = 0;
+#ifdef cimg_use_magick
+      double stmin, stmax = (double)max_min(stmin);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
+                   cimg_instance,
+                   filename,stmin,stmax);
+
+      Magick::Image image(Magick::Geometry(_width,_height),"black");
+      image.type(Magick::TrueColorType);
+      image.depth(bytes_per_pixel?(8*bytes_per_pixel):(stmax>=256?16:8));
+      const T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = _spectrum>1?data(0,0,0,1):0,
+        *ptr_b = _spectrum>2?data(0,0,0,2):0;
+      Magick::PixelPacket *pixels = image.getPixels(0,0,_width,_height);
+      switch (_spectrum) {
+      case 1 : // Scalar images
+        for (unsigned int off = _width*_height; off; --off) {
+          pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++);
+          ++pixels;
+        }
+        break;
+      case 2 : // RG images
+        for (unsigned int off = _width*_height; off; --off) {
+          pixels->red = (Magick::Quantum)*(ptr_r++);
+          pixels->green = (Magick::Quantum)*(ptr_g++);
+          pixels->blue = 0; ++pixels;
+        }
+        break;
+      default : // RGB images
+        for (unsigned int off = _width*_height; off; --off) {
+          pixels->red = (Magick::Quantum)*(ptr_r++);
+          pixels->green = (Magick::Quantum)*(ptr_g++);
+          pixels->blue = (Magick::Quantum)*(ptr_b++);
+          ++pixels;
+        }
+      }
+      image.syncPixels();
+      image.write(filename);
+#else
+      throw CImgIOException(_cimg_instance
+                            "save_magick() : Unable to save file '%s' unless libMagick++ is enabled.",
+                            cimg_instance,
+                            filename);
+#endif
+      return *this;
+    }
+
+    // Save an image to a PNG file (internal).
+    // Most of this function has been written by Eric Fausett
+    const CImg<T>& _save_png(std::FILE *const file, const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_png() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_png() : Empty image, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      unsigned int foo = bytes_per_pixel; foo = 0;
+#ifndef cimg_use_png
+      if (!file) return save_other(filename);
+      else throw CImgIOException(_cimg_instance
+                                 "save_png() : Unable to save data in '(*FILE)' unless libpng is enabled.",
+                                 cimg_instance);
+#else
+      const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
+      std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb");
+
+      double stmin, stmax = (double)max_min(stmin);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
+        cimg::warn(_cimg_instance
+                   "save_magick() : Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
+                   cimg_instance,
+                   filename,stmin,stmax);
+
+      // Setup PNG structures for write
+      png_voidp user_error_ptr = 0;
+      png_error_ptr user_error_fn = 0, user_warning_fn = 0;
+      png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, user_warning_fn);
+      if(!png_ptr){
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Failed to initialize 'png_ptr' structure when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_infop info_ptr = png_create_info_struct(png_ptr);
+      if (!info_ptr) {
+        png_destroy_write_struct(&png_ptr,(png_infopp)0);
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Failed to initialize 'info_ptr' structure when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      if (setjmp(png_jmpbuf(png_ptr))) {
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Encountered unknown fatal error in libpng when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_init_io(png_ptr, nfile);
+      const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8);
+      int color_type;
+      switch (spectrum()) {
+      case 1 : color_type = PNG_COLOR_TYPE_GRAY; break;
+      case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
+      case 3 : color_type = PNG_COLOR_TYPE_RGB; break;
+      default : color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+      }
+      const int interlace_type = PNG_INTERLACE_NONE;
+      const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
+      const int filter_method = PNG_FILTER_TYPE_DEFAULT;
+      png_set_IHDR(png_ptr,info_ptr,_width,_height,bit_depth,color_type,interlace_type,compression_type,filter_method);
+      png_write_info(png_ptr,info_ptr);
+      const int byte_depth = bit_depth>>3;
+      const int numChan = spectrum()>4?4:spectrum();
+      const int pixel_bit_depth_flag = numChan * (bit_depth-1);
+
+      // Allocate Memory for Image Save and Fill pixel data
+      png_bytep *const imgData = new png_byte*[_height];
+      for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width];
+      const T *pC0 = data(0,0,0,0);
+      switch (pixel_bit_depth_flag) {
+      case 7 :  { // Gray 8-bit
+        cimg_forY(*this,y) {
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++);
+        }
+      } break;
+      case 14 : { // Gray w/ Alpha 8-bit
+        const T *pC1 = data(0,0,0,1);
+        cimg_forY(*this,y) {
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned char)*(pC0++);
+            *(ptrd++) = (unsigned char)*(pC1++);
+          }
+        }
+      } break;
+      case 21 :  { // RGB 8-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2);
+        cimg_forY(*this,y) {
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned char)*(pC0++);
+            *(ptrd++) = (unsigned char)*(pC1++);
+            *(ptrd++) = (unsigned char)*(pC2++);
+          }
+        }
+      } break;
+      case 28 : { // RGB x/ Alpha 8-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3);
+        cimg_forY(*this,y){
+          unsigned char *ptrd = imgData[y];
+          cimg_forX(*this,x){
+            *(ptrd++) = (unsigned char)*(pC0++);
+            *(ptrd++) = (unsigned char)*(pC1++);
+            *(ptrd++) = (unsigned char)*(pC2++);
+            *(ptrd++) = (unsigned char)*(pC3++);
+          }
+        }
+      } break;
+      case 15 : { // Gray 16-bit
+        cimg_forY(*this,y){
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++);
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width);
+        }
+      } break;
+      case 30 : { // Gray w/ Alpha 16-bit
+        const T *pC1 = data(0,0,0,1);
+        cimg_forY(*this,y){
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned short)*(pC0++);
+            *(ptrd++) = (unsigned short)*(pC1++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width);
+        }
+      } break;
+      case 45 : { // RGB 16-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2);
+        cimg_forY(*this,y) {
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned short)*(pC0++);
+            *(ptrd++) = (unsigned short)*(pC1++);
+            *(ptrd++) = (unsigned short)*(pC2++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width);
+        }
+      } break;
+      case 60 : { // RGB w/ Alpha 16-bit
+        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3);
+        cimg_forY(*this,y) {
+          unsigned short *ptrd = (unsigned short*)(imgData[y]);
+          cimg_forX(*this,x) {
+            *(ptrd++) = (unsigned short)*(pC0++);
+            *(ptrd++) = (unsigned short)*(pC1++);
+            *(ptrd++) = (unsigned short)*(pC2++);
+            *(ptrd++) = (unsigned short)*(pC3++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width);
+        }
+      } break;
+      default :
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimg_instance
+                              "save_png() : Encountered unknown fatal error in libpng when saving file '%s'.",
+                              cimg_instance,
+                              nfilename?nfilename:"(FILE*)");
+      }
+      png_write_image(png_ptr,imgData);
+      png_write_end(png_ptr,info_ptr);
+      png_destroy_write_struct(&png_ptr, &info_ptr);
+
+      // Deallocate Image Write Memory
+      cimg_forY(*this,n) delete[] imgData[n];
+      delete[] imgData;
+      if (!file) cimg::fclose(nfile);
+      return *this;
+#endif
+    }
+
+    //! Save a file in PNG format
+    const CImg<T>& save_png(const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      return _save_png(0,filename,bytes_per_pixel);
+    }
+
+    //! Save a file in PNG format
+    const CImg<T>& save_png(std::FILE *const file, const unsigned int bytes_per_pixel=0) const {
+      return _save_png(file,0,bytes_per_pixel);
+    }
+
+    //! Save the image as a PNM file.
+    const CImg<T>& save_pnm(const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      return _save_pnm(0,filename,bytes_per_pixel);
+    }
+
+    //! Save the image as a PNM file.
+    const CImg<T>& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const {
+      return _save_pnm(file,0,bytes_per_pixel);
+    }
+
+    // Save the image as a PNM file (internal function).
+    const CImg<T>& _save_pnm(std::FILE *const file, const char *const filename, const unsigned int bytes_per_pixel=0) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_pnm() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_pnm() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      double stmin, stmax = (double)max_min(stmin);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_pnm() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_pnm() : Instance is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
+        cimg::warn(_cimg_instance
+                   "save_pnm() : Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
+                   cimg_instance,
+                   stmin,stmax,filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = (_spectrum>=2)?data(0,0,0,1):0,
+        *ptr_b = (_spectrum>=3)?data(0,0,0,2):0;
+      const unsigned int buf_size = cimg::min(1024*1024U,_width*_height*(_spectrum==1?1:3));
+
+      std::fprintf(nfile,"P%c\n%u %u\n%u\n",
+                   (_spectrum==1?'5':'6'),_width,_height,stmax<256?255:(stmax<4096?4095:65535));
+
+      switch (_spectrum) {
+      case 1 : { // Scalar image
+        if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PGM 8 bits
+          CImg<ucharT> buf(buf_size);
+          for (int to_write = _width*_height; to_write>0; ) {
+            const unsigned int N = cimg::min((unsigned int)to_write,buf_size);
+            unsigned char *ptrd = buf._data;
+            for (int i = (int)N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr_r++);
+            cimg::fwrite(buf._data,N,nfile);
+            to_write-=N;
+          }
+        } else {             // Binary PGM 16 bits
+          CImg<ushortT> buf(buf_size);
+          for (int to_write = _width*_height; to_write>0; ) {
+            const unsigned int N = cimg::min((unsigned int)to_write,buf_size);
+            unsigned short *ptrd = buf._data;
+            for (int i = (int)N; i>0; --i) *(ptrd++) = (unsigned short)*(ptr_r++);
+            if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+            cimg::fwrite(buf._data,N,nfile);
+            to_write-=N;
+          }
+        }
+      } break;
+      case 2 : { // RG image
+        if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits
+          CImg<ucharT> buf(buf_size);
+          for (int to_write = _width*_height; to_write>0; ) {
+            const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+            unsigned char *ptrd = buf._data;
+            for (int i = (int)N; i>0; --i) {
+              *(ptrd++) = (unsigned char)*(ptr_r++);
+              *(ptrd++) = (unsigned char)*(ptr_g++);
+              *(ptrd++) = 0;
+            }
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        } else {             // Binary PPM 16 bits
+          CImg<ushortT> buf(buf_size);
+          for (int to_write = _width*_height; to_write>0; ) {
+            const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+            unsigned short *ptrd = buf._data;
+            for (int i = (int)N; i>0; --i) {
+              *(ptrd++) = (unsigned short)*(ptr_r++);
+              *(ptrd++) = (unsigned short)*(ptr_g++);
+              *(ptrd++) = 0;
+            }
+            if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        }
+      } break;
+      default : { // RGB image
+        if (bytes_per_pixel==1 || (!bytes_per_pixel && stmax<256)) { // Binary PPM 8 bits
+          CImg<ucharT> buf(buf_size);
+          for (int to_write = _width*_height; to_write>0; ) {
+            const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+            unsigned char *ptrd = buf._data;
+            for (int i = (int)N; i>0; --i) {
+              *(ptrd++) = (unsigned char)*(ptr_r++);
+              *(ptrd++) = (unsigned char)*(ptr_g++);
+              *(ptrd++) = (unsigned char)*(ptr_b++);
+            }
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        } else {             // Binary PPM 16 bits
+          CImg<ushortT> buf(buf_size);
+          for (int to_write = _width*_height; to_write>0; ) {
+            const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+            unsigned short *ptrd = buf._data;
+            for (int i = (int)N; i>0; --i) {
+              *(ptrd++) = (unsigned short)*(ptr_r++);
+              *(ptrd++) = (unsigned short)*(ptr_g++);
+              *(ptrd++) = (unsigned short)*(ptr_b++);
+            }
+            if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+            cimg::fwrite(buf._data,3*N,nfile);
+            to_write-=N;
+          }
+        }
+      }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a PFM file.
+    const CImg<T>& save_pfm(const char *const filename) const {
+      return _save_pfm(0,filename);
+    }
+
+    //! Save the image as a PFM file.
+    const CImg<T>& save_pfm(std::FILE *const file) const {
+      return _save_pfm(file,0);
+    }
+
+    // Save the image as a PFM file (internal function).
+    const CImg<T>& _save_pfm(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_pfm() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_pfm() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_pfm() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      if (_spectrum>3)
+        cimg::warn(_cimg_instance
+                   "save_pfm() : Instance image is multispectral, only the three first channels will be saved in file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const T
+        *ptr_r = data(0,0,0,0),
+        *ptr_g = (_spectrum>=2)?data(0,0,0,1):0,
+        *ptr_b = (_spectrum>=3)?data(0,0,0,2):0;
+      const unsigned int buf_size = cimg::min(1024*1024U,_width*_height*(_spectrum==1?1:3));
+
+      std::fprintf(nfile,"P%c\n%u %u\n1.0\n",
+                   (_spectrum==1?'f':'F'),_width,_height);
+
+      switch (_spectrum) {
+      case 1 : { // Scalar image
+        CImg<floatT> buf(buf_size);
+        for (int to_write = _width*_height; to_write>0; ) {
+          const unsigned int N = cimg::min((unsigned int)to_write,buf_size);
+          float *ptrd = buf._data;
+          for (int i = (int)N; i>0; --i) *(ptrd++) = (float)*(ptr_r++);
+          if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+          cimg::fwrite(buf._data,N,nfile);
+          to_write-=N;
+        }
+      } break;
+      case 2 : { // RG image
+        CImg<floatT> buf(buf_size);
+        for (int to_write = _width*_height; to_write>0; ) {
+          const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+          float *ptrd = buf._data;
+          for (int i = (int)N; i>0; --i) {
+            *(ptrd++) = (float)*(ptr_r++);
+            *(ptrd++) = (float)*(ptr_g++);
+            *(ptrd++) = 0;
+          }
+          if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+          cimg::fwrite(buf._data,3*N,nfile);
+          to_write-=N;
+        }
+      } break;
+      default : { // RGB image
+        CImg<floatT> buf(buf_size);
+        for (int to_write = _width*_height; to_write>0; ) {
+          const unsigned int N = cimg::min((unsigned int)to_write,buf_size/3);
+          float *ptrd = buf._data;
+          for (int i = (int)N; i>0; --i) {
+            *(ptrd++) = (float)*(ptr_r++);
+            *(ptrd++) = (float)*(ptr_g++);
+            *(ptrd++) = (float)*(ptr_b++);
+          }
+          if (!cimg::endianness()) cimg::invert_endianness(buf._data,buf_size);
+          cimg::fwrite(buf._data,3*N,nfile);
+          to_write-=N;
+        }
+      }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    // Save the image as a RGB file (internal).
+    const CImg<T>& _save_rgb(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_rgb() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_rgb() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_spectrum!=3)
+        cimg::warn(_cimg_instance
+                   "save_rgb() : Instance image has not exactly 3 channels, for file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const unsigned int wh = _width*_height;
+      unsigned char *const buffer = new unsigned char[3*wh], *nbuffer = buffer;
+      const T
+        *ptr1 = data(0,0,0,0),
+        *ptr2 = _spectrum>1?data(0,0,0,1):0,
+        *ptr3 = _spectrum>2?data(0,0,0,2):0;
+      switch (_spectrum) {
+      case 1 : { // Scalar image
+        for (unsigned int k = 0; k<wh; ++k) {
+          const unsigned char val = (unsigned char)*(ptr1++);
+          *(nbuffer++) = val;
+          *(nbuffer++) = val;
+          *(nbuffer++) = val;
+        }
+      } break;
+      case 2 : { // RG image
+        for (unsigned int k = 0; k<wh; ++k) {
+          *(nbuffer++) = (unsigned char)(*(ptr1++));
+          *(nbuffer++) = (unsigned char)(*(ptr2++));
+          *(nbuffer++) = 0;
+        }
+      } break;
+      default : { // RGB image
+        for (unsigned int k = 0; k<wh; ++k) {
+          *(nbuffer++) = (unsigned char)(*(ptr1++));
+          *(nbuffer++) = (unsigned char)(*(ptr2++));
+          *(nbuffer++) = (unsigned char)(*(ptr3++));
+        }
+      }
+      }
+      cimg::fwrite(buffer,3*wh,nfile);
+      if (!file) cimg::fclose(nfile);
+      delete[] buffer;
+      return *this;
+    }
+
+    //! Save the image as a RGB file.
+    const CImg<T>& save_rgb(const char *const filename) const {
+      return _save_rgb(0,filename);
+    }
+
+    //! Save the image as a RGB file.
+    const CImg<T>& save_rgb(std::FILE *const file) const {
+      return _save_rgb(file,0);
+    }
+
+    // Save the image as a RGBA file (internal).
+    const CImg<T>& _save_rgba(std::FILE *const file, const char *const filename) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_rgba() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_rgba() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+      if (_spectrum!=4)
+        cimg::warn(_cimg_instance
+                   "save_rgba() : Instance image has not exactly 4 channels, for file '%s'.",
+                   cimg_instance,
+                   filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const unsigned int wh = _width*_height;
+      unsigned char *const buffer = new unsigned char[4*wh], *nbuffer = buffer;
+      const T
+        *ptr1 = data(0,0,0,0),
+        *ptr2 = _spectrum>1?data(0,0,0,1):0,
+        *ptr3 = _spectrum>2?data(0,0,0,2):0,
+        *ptr4 = _spectrum>3?data(0,0,0,3):0;
+      switch (_spectrum) {
+      case 1 : { // Scalar images
+        for (unsigned int k = 0; k<wh; ++k) {
+          const unsigned char val = (unsigned char)*(ptr1++);
+          *(nbuffer++) = val;
+          *(nbuffer++) = val;
+          *(nbuffer++) = val;
+          *(nbuffer++) = 255;
+        }
+      } break;
+      case 2 : { // RG images
+        for (unsigned int k = 0; k<wh; ++k) {
+          *(nbuffer++) = (unsigned char)(*(ptr1++));
+          *(nbuffer++) = (unsigned char)(*(ptr2++));
+          *(nbuffer++) = 0;
+          *(nbuffer++) = 255;
+        }
+      } break;
+      case 3 : { // RGB images
+        for (unsigned int k = 0; k<wh; ++k) {
+          *(nbuffer++) = (unsigned char)(*(ptr1++));
+          *(nbuffer++) = (unsigned char)(*(ptr2++));
+          *(nbuffer++) = (unsigned char)(*(ptr3++));
+          *(nbuffer++) = 255;
+        }
+      } break;
+      default : { // RGBA images
+        for (unsigned int k = 0; k<wh; ++k) {
+          *(nbuffer++) = (unsigned char)(*(ptr1++));
+          *(nbuffer++) = (unsigned char)(*(ptr2++));
+          *(nbuffer++) = (unsigned char)(*(ptr3++));
+          *(nbuffer++) = (unsigned char)(*(ptr4++));
+        }
+      }
+      }
+      cimg::fwrite(buffer,4*wh,nfile);
+      if (!file) cimg::fclose(nfile);
+      delete[] buffer;
+      return *this;
+    }
+
+    //! Save the image as a RGBA file.
+    const CImg<T>& save_rgba(const char *const filename) const {
+      return _save_rgba(0,filename);
+    }
+
+    //! Save the image as a RGBA file.
+    const CImg<T>& save_rgba(std::FILE *const file) const {
+      return _save_rgba(file,0);
+    }
+
+    // Save a plane into a tiff file
+#ifdef cimg_use_tiff
+
+#define _cimg_save_tif(types,typed) \
+    if (!std::strcmp(types,pixel_type())) { const typed foo = (typed)0; return _save_tiff(tif,directory,foo); }
+
+    template<typename t>
+    const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory, const t& pixel_t) const {
+      if (is_empty() || !tif || pixel_t) return *this;
+      const char *const filename = TIFFFileName(tif);
+      uint32 rowsperstrip = (uint32)-1;
+      uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric, 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 *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif));
+      if (buf) {
+        for (unsigned int row = 0; row<_height; row+=rowsperstrip) {
+          uint32 nrow = (row + rowsperstrip>_height?_height-row:rowsperstrip);
+          tstrip_t strip = TIFFComputeStrip(tif,row,0);
+          tsize_t i = 0;
+          for (unsigned int rr = 0; rr<nrow; ++rr)
+            for (unsigned int cc = 0; cc<_width; ++cc)
+              for (unsigned int vv = 0; vv<spp; ++vv)
+                buf[i++] = (t)(*this)(cc,row + rr,0,vv);
+          if (TIFFWriteEncodedStrip(tif,strip,buf,i*sizeof(t))<0)
+            throw CImgException(_cimg_instance
+                                "save_tiff() : Invalid strip writting when saving file '%s'.",
+                                cimg_instance,
+                                filename?filename:"(FILE*)");
+        }
+        _TIFFfree(buf);
+      }
+      TIFFWriteDirectory(tif);
+      return (*this);
+    }
+
+    const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory) const {
+      _cimg_save_tif("bool",unsigned char);
+      _cimg_save_tif("char",char);
+      _cimg_save_tif("unsigned char",unsigned char);
+      _cimg_save_tif("short",short);
+      _cimg_save_tif("unsigned short",unsigned short);
+      _cimg_save_tif("int",int);
+      _cimg_save_tif("unsigned int",unsigned int);
+      _cimg_save_tif("long",int);
+      _cimg_save_tif("unsigned long",unsigned int);
+      _cimg_save_tif("float",float);
+      _cimg_save_tif("double",float);
+      const char *const filename = TIFFFileName(tif);
+      throw CImgException(_cimg_instance
+                          "save_tiff() : Unsupported pixel type '%s' for file '%s'.",
+                          cimg_instance,
+                          pixel_type(),filename?filename:"(FILE*)");
+      return *this;
+    }
+#endif
+
+    //! Save a file in TIFF format.
+    const CImg<T>& save_tiff(const char *const filename) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_tiff() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_tiff() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+#ifdef cimg_use_tiff
+      TIFF *tif = TIFFOpen(filename,"w");
+      if (tif) {
+        cimg_forZ(*this,z) get_slice(z)._save_tiff(tif,z);
+        TIFFClose(tif);
+      } else throw CImgException(_cimg_instance
+                                 "save_tiff() : Failed to open file '%s' for writing.",
+                                 cimg_instance,
+                                 filename);
+#else
+      return save_other(filename);
+#endif
+      return *this;
+    }
+
+    //! Save the image as an ANALYZE7.5 or NIFTI file.
+    const CImg<T>& save_analyze(const char *const filename, const float *const voxsize=0) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_analyze() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_analyze() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      std::FILE *file;
+      char header[348] = { 0 }, hname[1024] = { 0 }, iname[1024] = { 0 };
+      const char *const ext = cimg::split_filename(filename);
+      short datatype=-1;
+      std::memset(header,0,348);
+      if (!*ext) { cimg_snprintf(hname,sizeof(hname),"%s.hdr",filename); cimg_snprintf(iname,sizeof(iname),"%s.img",filename); }
+      if (!cimg::strncasecmp(ext,"hdr",3)) {
+        std::strcpy(hname,filename); std::strncpy(iname,filename,sizeof(iname)-1); std::sprintf(iname + std::strlen(iname)-3,"img");
+      }
+      if (!cimg::strncasecmp(ext,"img",3)) {
+        std::strcpy(hname,filename); std::strncpy(iname,filename,sizeof(iname)-1); std::sprintf(hname + std::strlen(iname)-3,"hdr");
+      }
+      if (!cimg::strncasecmp(ext,"nii",3)) {
+        std::strncpy(hname,filename,sizeof(hname)-1); *iname = 0;
+      }
+      int *const iheader = (int*)header;
+      *iheader = 348;
+      std::strcpy(header + 4,"CImg");
+      std::strcpy(header + 14," ");
+      ((short*)(header + 36))[0] = 4096;
+      ((char*)(header + 38))[0] = 114;
+      ((short*)(header + 40))[0] = 4;
+      ((short*)(header + 40))[1] = _width;
+      ((short*)(header + 40))[2] = _height;
+      ((short*)(header + 40))[3] = _depth;
+      ((short*)(header + 40))[4] = _spectrum;
+      if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2;
+      if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2;
+      if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2;
+      if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4;
+      if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4;
+      if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8;
+      if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8;
+      if (!cimg::strcasecmp(pixel_type(),"unsigned long")) datatype = 8;
+      if (!cimg::strcasecmp(pixel_type(),"long")) datatype = 8;
+      if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16;
+      if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64;
+      if (datatype<0)
+        throw CImgIOException(_cimg_instance
+                              "save_analyze() : Unsupported pixel type '%s' for file '%s'.",
+                              cimg_instance,
+                              pixel_type(),filename);
+
+      ((short*)(header+70))[0] = datatype;
+      ((short*)(header+72))[0] = sizeof(T);
+      ((float*)(header+112))[0] = 1;
+      ((float*)(header+76))[0] = 0;
+      if (voxsize) {
+        ((float*)(header+76))[1] = voxsize[0];
+        ((float*)(header+76))[2] = voxsize[1];
+        ((float*)(header+76))[3] = voxsize[2];
+      } else ((float*)(header+76))[1] = ((float*)(header+76))[2] = ((float*)(header+76))[3] = 1;
+      file = cimg::fopen(hname,"wb");
+      cimg::fwrite(header,348,file);
+      if (*iname) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); }
+      cimg::fwrite(_data,size(),file);
+      cimg::fclose(file);
+      return *this;
+    }
+
+    //! Save the image as a .cimg file.
+    const CImg<T>& save_cimg(const char *const filename, const bool compress=false) const {
+      CImgList<T>(*this,true).save_cimg(filename,compress);
+      return *this;
+    }
+
+    // Save the image as a .cimg file.
+    const CImg<T>& save_cimg(std::FILE *const file, const bool compress=false) const {
+      CImgList<T>(*this,true).save_cimg(file,compress);
+      return *this;
+    }
+
+    //! Insert the image into an existing .cimg file, at specified coordinates.
+    const CImg<T>& save_cimg(const char *const filename,
+                             const unsigned int n0,
+                             const unsigned int x0, const unsigned int y0,
+                             const unsigned int z0, const unsigned int c0) const {
+      CImgList<T>(*this,true).save_cimg(filename,n0,x0,y0,z0,c0);
+      return *this;
+    }
+
+    //! Insert the image into an existing .cimg file, at specified coordinates.
+    const CImg<T>& save_cimg(std::FILE *const file,
+                             const unsigned int n0,
+                             const unsigned int x0, const unsigned int y0,
+                             const unsigned int z0, const unsigned int c0) const {
+      CImgList<T>(*this,true).save_cimg(file,n0,x0,y0,z0,c0);
+      return *this;
+    }
+
+    //! Save an empty .cimg file with specified dimensions.
+    static void save_empty_cimg(const char *const filename,
+                                const unsigned int dx, const unsigned int dy=1,
+                                const unsigned int dz=1, const unsigned int dc=1) {
+      return CImgList<T>::save_empty_cimg(filename,1,dx,dy,dz,dc);
+    }
+
+    //! Save an empty .cimg file with specified dimensions.
+    static void save_empty_cimg(std::FILE *const file,
+                                const unsigned int dx, const unsigned int dy=1,
+                                const unsigned int dz=1, const unsigned int dc=1) {
+      return CImgList<T>::save_empty_cimg(file,1,dx,dy,dz,dc);
+    }
+
+    // Save the image as an INRIMAGE-4 file (internal).
+    const CImg<T>& _save_inr(std::FILE *const file, const char *const filename, const float *const voxsize) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_inr() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_inr() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      int inrpixsize=-1;
+      const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0";
+      if (!cimg::strcasecmp(pixel_type(),"unsigned char"))  { inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
+      if (!cimg::strcasecmp(pixel_type(),"char"))           { inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
+      if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; }
+      if (!cimg::strcasecmp(pixel_type(),"short"))          { inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; }
+      if (!cimg::strcasecmp(pixel_type(),"unsigned int"))   { inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; }
+      if (!cimg::strcasecmp(pixel_type(),"int"))            { inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; }
+      if (!cimg::strcasecmp(pixel_type(),"float"))          { inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; }
+      if (!cimg::strcasecmp(pixel_type(),"double"))         { inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; }
+      if (inrpixsize<=0)
+        throw CImgIOException(_cimg_instance
+                              "save_inr() : Unsupported pixel type '%s' for file '%s'",
+                              cimg_instance,
+                              pixel_type(),filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      char header[257] = { 0 };
+      int err = cimg_snprintf(header,sizeof(header),"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",_width,_height,_depth,_spectrum);
+      if (voxsize) err+=std::sprintf(header + err,"VX=%g\nVY=%g\nVZ=%g\n",voxsize[0],voxsize[1],voxsize[2]);
+      err+=std::sprintf(header + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm");
+      std::memset(header + err,'\n',252 - err);
+      std::memcpy(header + 252,"##}\n",4);
+      cimg::fwrite(header,256,nfile);
+      cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) cimg::fwrite(&((*this)(x,y,z,c)),1,nfile);
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as an INRIMAGE-4 file.
+    const CImg<T>& save_inr(const char *const filename, const float *const voxsize=0) const {
+      return _save_inr(0,filename,voxsize);
+    }
+
+    //! Save the image as an INRIMAGE-4 file.
+    const CImg<T>& save_inr(std::FILE *const file, const float *const voxsize=0) const {
+      return _save_inr(file,0,voxsize);
+    }
+
+    //! Save the image as a EXR file.
+    const CImg<T>& save_exr(const char *const filename) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_exr() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_exr() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+      if (_depth>1)
+        cimg::warn(_cimg_instance
+                   "save_exr() : Instance is volumetric, only the first slice will be saved in file '%s'.",
+                   cimg_instance,
+                   filename);
+
+#ifndef cimg_use_openexr
+      return save_other(filename);
+#else
+      Imf::Rgba *const ptrd0 = new Imf::Rgba[_width*_height], *ptrd = ptrd0, rgba;
+      switch (_spectrum) {
+      case 1 : { // Grayscale image.
+        for (const T *ptr_r = data(), *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e;) {
+          rgba.r = rgba.g = rgba.b = (half)(*(ptr_r++));
+          rgba.a = (half)1;
+          *(ptrd++) = rgba;
+        }
+      } break;
+      case 2 : { // RG image.
+        for (const T *ptr_r = data(), *ptr_g = data(0,0,0,1), *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e; ) {
+          rgba.r = (half)(*(ptr_r++));
+          rgba.g = (half)(*(ptr_g++));
+          rgba.b = (half)0;
+          rgba.a = (half)1;
+          *(ptrd++) = rgba;
+        }
+      } break;
+      case 3 : { // RGB image.
+        for (const T *ptr_r = data(), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e;) {
+          rgba.r = (half)(*(ptr_r++));
+          rgba.g = (half)(*(ptr_g++));
+          rgba.b = (half)(*(ptr_b++));
+          rgba.a = (half)1;
+          *(ptrd++) = rgba;
+        }
+      } break;
+      default : { // RGBA image.
+        for (const T *ptr_r = data(), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3),
+               *const ptr_e = ptr_r + _width*_height; ptr_r<ptr_e;) {
+          rgba.r = (half)(*(ptr_r++));
+          rgba.g = (half)(*(ptr_g++));
+          rgba.b = (half)(*(ptr_b++));
+          rgba.a = (half)(*(ptr_a++));
+          *(ptrd++) = rgba;
+        }
+      } break;
+      }
+      Imf::RgbaOutputFile outFile(filename,_width,_height,
+                                  _spectrum==1?Imf::WRITE_Y:_spectrum==2?Imf::WRITE_YA:_spectrum==3?Imf::WRITE_RGB:Imf::WRITE_RGBA);
+      outFile.setFrameBuffer(ptrd0,1,_width);
+      outFile.writePixels(_height);
+      delete[] ptrd0;
+      return *this;
+#endif
+    }
+
+    // Save the image as a PANDORE-5 file (internal).
+    unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const {
+      unsigned int nbdims = 0;
+      if (id==2 || id==3 || id==4) { dims[0] = 1; dims[1] = _width;  nbdims = 2; }
+      if (id==5 || id==6 || id==7) { dims[0] = 1; dims[1] = _height; dims[2] = _width;  nbdims=3; }
+      if (id==8 || id==9 || id==10) { dims[0] = _spectrum; dims[1] = _depth;  dims[2] = _height; dims[3] = _width; nbdims = 4; }
+      if (id==16 || id==17 || id==18) { dims[0] = 3; dims[1] = _height; dims[2] = _width;  dims[3] = colorspace; nbdims = 4; }
+      if (id==19 || id==20 || id==21) { dims[0] = 3; dims[1] = _depth;  dims[2] = _height; dims[3] = _width; dims[4] = colorspace; nbdims = 5; }
+      if (id==22 || id==23 || id==25) { dims[0] = _spectrum; dims[1] = _width;  nbdims = 2; }
+      if (id==26 || id==27 || id==29) { dims[0] = _spectrum; dims[1] = _height; dims[2] = _width;  nbdims=3; }
+      if (id==30 || id==31 || id==33) { dims[0] = _spectrum; dims[1] = _depth;  dims[2] = _height; dims[3] = _width; nbdims = 4; }
+      return nbdims;
+    }
+
+    const CImg<T>& _save_pandore(std::FILE *const file, const char *const filename, const unsigned int colorspace) const {
+
+#define __cimg_save_pandore_case(dtype) \
+       dtype *buffer = new dtype[size()]; \
+       const T *ptrs = _data; \
+       cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \
+       buffer-=size(); \
+       cimg::fwrite(buffer,size(),nfile); \
+       delete[] buffer
+
+#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \
+      if (!saved && (sy?(sy==_height):true) && (sz?(sz==_depth):true) && (sv?(sv==_spectrum):true) && !std::strcmp(stype,pixel_type())) { \
+        unsigned int *iheader = (unsigned int*)(header+12); \
+        nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \
+        cimg::fwrite(header,36,nfile); \
+        if (sizeof(unsigned long)==4) { unsigned long ndims[5] = { 0 }; for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+        else if (sizeof(unsigned int)==4) { unsigned int ndims[5] = { 0 }; for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+        else if (sizeof(unsigned short)==4) { unsigned short ndims[5] = { 0 }; for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
+        else throw CImgIOException(_cimg_instance \
+                                   "save_pandore() : Unsupported datatype for file '%s'.",\
+                                   cimg_instance, \
+                                   filename?filename:"(FILE*)"); \
+        if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \
+          __cimg_save_pandore_case(unsigned char); \
+        } else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \
+          if (sizeof(unsigned long)==4) { __cimg_save_pandore_case(unsigned long); } \
+          else if (sizeof(unsigned int)==4) { __cimg_save_pandore_case(unsigned int); } \
+          else if (sizeof(unsigned short)==4) { __cimg_save_pandore_case(unsigned short); } \
+          else throw CImgIOException(_cimg_instance \
+                                     "save_pandore() : Unsupported datatype for file '%s'.",\
+                                     cimg_instance, \
+                                     filename?filename:"(FILE*)"); \
+        } else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \
+          if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \
+          else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \
+          else throw CImgIOException(_cimg_instance \
+                                     "save_pandore() : Unsupported datatype for file '%s'.",\
+                                     cimg_instance, \
+                                     filename?filename:"(FILE*)"); \
+        } \
+        saved = true; \
+      }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_pandore() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_pandore() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0,
+                                   0,0,0,0,'C','I','m','g',0,0,0,0,0,'N','o',' ','d','a','t','e',0,0,0,0 };
+      unsigned int nbdims, dims[5] = { 0 };
+      bool saved = false;
+      _cimg_save_pandore_case(1,1,1,"unsigned char",2);
+      _cimg_save_pandore_case(1,1,1,"char",3);
+      _cimg_save_pandore_case(1,1,1,"short",3);
+      _cimg_save_pandore_case(1,1,1,"unsigned short",3);
+      _cimg_save_pandore_case(1,1,1,"unsigned int",3);
+      _cimg_save_pandore_case(1,1,1,"int",3);
+      _cimg_save_pandore_case(1,1,1,"unsigned long",4);
+      _cimg_save_pandore_case(1,1,1,"long",3);
+      _cimg_save_pandore_case(1,1,1,"float",4);
+      _cimg_save_pandore_case(1,1,1,"double",4);
+
+      _cimg_save_pandore_case(0,1,1,"unsigned char",5);
+      _cimg_save_pandore_case(0,1,1,"char",6);
+      _cimg_save_pandore_case(0,1,1,"short",6);
+      _cimg_save_pandore_case(0,1,1,"unsigned short",6);
+      _cimg_save_pandore_case(0,1,1,"unsigned int",6);
+      _cimg_save_pandore_case(0,1,1,"int",6);
+      _cimg_save_pandore_case(0,1,1,"unsigned long",7);
+      _cimg_save_pandore_case(0,1,1,"long",6);
+      _cimg_save_pandore_case(0,1,1,"float",7);
+      _cimg_save_pandore_case(0,1,1,"double",7);
+
+      _cimg_save_pandore_case(0,0,1,"unsigned char",8);
+      _cimg_save_pandore_case(0,0,1,"char",9);
+      _cimg_save_pandore_case(0,0,1,"short",9);
+      _cimg_save_pandore_case(0,0,1,"unsigned short",9);
+      _cimg_save_pandore_case(0,0,1,"unsigned int",9);
+      _cimg_save_pandore_case(0,0,1,"int",9);
+      _cimg_save_pandore_case(0,0,1,"unsigned long",10);
+      _cimg_save_pandore_case(0,0,1,"long",9);
+      _cimg_save_pandore_case(0,0,1,"float",10);
+      _cimg_save_pandore_case(0,0,1,"double",10);
+
+      _cimg_save_pandore_case(0,1,3,"unsigned char",16);
+      _cimg_save_pandore_case(0,1,3,"char",17);
+      _cimg_save_pandore_case(0,1,3,"short",17);
+      _cimg_save_pandore_case(0,1,3,"unsigned short",17);
+      _cimg_save_pandore_case(0,1,3,"unsigned int",17);
+      _cimg_save_pandore_case(0,1,3,"int",17);
+      _cimg_save_pandore_case(0,1,3,"unsigned long",18);
+      _cimg_save_pandore_case(0,1,3,"long",17);
+      _cimg_save_pandore_case(0,1,3,"float",18);
+      _cimg_save_pandore_case(0,1,3,"double",18);
+
+      _cimg_save_pandore_case(0,0,3,"unsigned char",19);
+      _cimg_save_pandore_case(0,0,3,"char",20);
+      _cimg_save_pandore_case(0,0,3,"short",20);
+      _cimg_save_pandore_case(0,0,3,"unsigned short",20);
+      _cimg_save_pandore_case(0,0,3,"unsigned int",20);
+      _cimg_save_pandore_case(0,0,3,"int",20);
+      _cimg_save_pandore_case(0,0,3,"unsigned long",21);
+      _cimg_save_pandore_case(0,0,3,"long",20);
+      _cimg_save_pandore_case(0,0,3,"float",21);
+      _cimg_save_pandore_case(0,0,3,"double",21);
+
+      _cimg_save_pandore_case(1,1,0,"unsigned char",22);
+      _cimg_save_pandore_case(1,1,0,"char",23);
+      _cimg_save_pandore_case(1,1,0,"short",23);
+      _cimg_save_pandore_case(1,1,0,"unsigned short",23);
+      _cimg_save_pandore_case(1,1,0,"unsigned int",23);
+      _cimg_save_pandore_case(1,1,0,"int",23);
+      _cimg_save_pandore_case(1,1,0,"unsigned long",25);
+      _cimg_save_pandore_case(1,1,0,"long",23);
+      _cimg_save_pandore_case(1,1,0,"float",25);
+      _cimg_save_pandore_case(1,1,0,"double",25);
+
+      _cimg_save_pandore_case(0,1,0,"unsigned char",26);
+      _cimg_save_pandore_case(0,1,0,"char",27);
+      _cimg_save_pandore_case(0,1,0,"short",27);
+      _cimg_save_pandore_case(0,1,0,"unsigned short",27);
+      _cimg_save_pandore_case(0,1,0,"unsigned int",27);
+      _cimg_save_pandore_case(0,1,0,"int",27);
+      _cimg_save_pandore_case(0,1,0,"unsigned long",29);
+      _cimg_save_pandore_case(0,1,0,"long",27);
+      _cimg_save_pandore_case(0,1,0,"float",29);
+      _cimg_save_pandore_case(0,1,0,"double",29);
+
+      _cimg_save_pandore_case(0,0,0,"unsigned char",30);
+      _cimg_save_pandore_case(0,0,0,"char",31);
+      _cimg_save_pandore_case(0,0,0,"short",31);
+      _cimg_save_pandore_case(0,0,0,"unsigned short",31);
+      _cimg_save_pandore_case(0,0,0,"unsigned int",31);
+      _cimg_save_pandore_case(0,0,0,"int",31);
+      _cimg_save_pandore_case(0,0,0,"unsigned long",33);
+      _cimg_save_pandore_case(0,0,0,"long",31);
+      _cimg_save_pandore_case(0,0,0,"float",33);
+      _cimg_save_pandore_case(0,0,0,"double",33);
+
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a PANDORE-5 file.
+    const CImg<T>& save_pandore(const char *const filename, const unsigned int colorspace=0) const {
+      return _save_pandore(0,filename,colorspace);
+    }
+
+    //! Save the image as a PANDORE-5 file.
+    const CImg<T>& save_pandore(std::FILE *const file, const unsigned int colorspace=0) const {
+      return _save_pandore(file,0,colorspace);
+    }
+
+   // Save the image as a RAW file (internal).
+    const CImg<T>& _save_raw(std::FILE *const file, const char *const filename, const bool multiplexed) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_raw() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_raw() : empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      if (!multiplexed) cimg::fwrite(_data,size(),nfile);
+      else {
+        CImg<T> buf(_spectrum);
+        cimg_forXYZ(*this,x,y,z) {
+          cimg_forC(*this,c) buf[c] = (*this)(x,y,z,c);
+          cimg::fwrite(buf._data,_spectrum,nfile);
+        }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save the image as a RAW file.
+    const CImg<T>& save_raw(const char *const filename, const bool multiplexed=false) const {
+      return _save_raw(0,filename,multiplexed);
+    }
+
+    //! Save the image as a RAW file.
+    const CImg<T>& save_raw(std::FILE *const file, const bool multiplexed=false) const {
+      return _save_raw(file,0,multiplexed);
+    }
+
+    //! Save the image as a video sequence file, using FFMPEG library.
+    const CImg<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                               const unsigned int fps=25, const unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ffmpeg() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_ffmpeg() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+      if (!fps)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ffmpeg() : Invalid specified framerate 0, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+#ifndef cimg_use_ffmpeg
+      return save_ffmpeg_external(filename,first_frame,last_frame,"mpeg2video",fps,bitrate);
+#else
+      get_split('z').save_ffmpeg(filename,first_frame,last_frame,fps,bitrate);
+#endif
+      return *this;
+    }
+
+    //! Save the image as a YUV video sequence file.
+    const CImg<T>& save_yuv(const char *const filename, const bool rgb2yuv=true) const {
+      get_split('z').save_yuv(filename,rgb2yuv);
+      return *this;
+    }
+
+    //! Save the image as a YUV video sequence file.
+    const CImg<T>& save_yuv(std::FILE *const file, const bool rgb2yuv=true) const {
+      get_split('z').save_yuv(file,rgb2yuv);
+      return *this;
+    }
+
+    // Save OFF files (internal).
+    template<typename tf, typename tc>
+    const CImg<T>& _save_off(std::FILE *const file, const char *const filename,
+                             const CImgList<tf>& primitives, const CImgList<tc>& colors) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_off() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_off() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)");
+
+      CImgList<T> opacities;
+      char error_message[1024] = { 0 };
+      if (!is_object3d(primitives,colors,opacities,true,error_message))
+        throw CImgInstanceException(_cimg_instance
+                                    "save_off() : Invalid specified 3d object, for file '%s' (%s).",
+                                    cimg_instance,
+                                    filename?filename:"(FILE*)",error_message);
+
+      const CImg<tc> default_color(1,3,1,1,200);
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+      unsigned int supported_primitives = 0;
+      cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives;
+      std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width);
+      cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n",(float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2)));
+      cimglist_for(primitives,l) {
+        const CImg<tc>& color = l<colors.width()?colors[l]:default_color;
+        const unsigned int psiz = primitives[l].size(), csiz = color.size();
+        const float r = color[0]/255.0f, g = (csiz>1?color[1]:r)/255.0f, b = (csiz>2?color[2]:g)/255.0f;
+        switch (psiz) {
+        case 1 : std::fprintf(nfile,"1 %u %f %f %f\n",(unsigned int)primitives(l,0),r,g,b); break;
+        case 2 : std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break;
+        case 3 : std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),
+                              (unsigned int)primitives(l,1),r,g,b); break;
+        case 4 : std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,3),
+                              (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); break;
+        case 6 : {
+          const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3);
+          const float rt = color.atXY(xt,yt,0)/255.0f, gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+          std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),rt,gt,bt);
+        } break;
+        case 9 : {
+          const unsigned int xt = (unsigned int)primitives(l,3), yt = (unsigned int)primitives(l,4);
+          const float rt = color.atXY(xt,yt,0)/255.0f, gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+          std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),
+                       (unsigned int)primitives(l,1),rt,gt,bt);
+        } break;
+        case 12 : {
+          const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5);
+          const float rt = color.atXY(xt,yt,0)/255.0f, gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f, bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+          std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,3),
+                       (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),rt,gt,bt);
+        } break;
+        }
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save OFF files.
+    template<typename tf, typename tc>
+    const CImg<T>& save_off(const char *const filename,
+                            const CImgList<tf>& primitives, const CImgList<tc>& colors) const {
+      return _save_off(0,filename,primitives,colors);
+    }
+
+    //! Save OFF files.
+    template<typename tf, typename tc>
+    const CImg<T>& save_off(std::FILE *const file,
+                            const CImgList<tf>& primitives, const CImgList<tc>& colors) const {
+      return _save_off(file,0,primitives,colors);
+    }
+
+    //! Save the image as a video sequence file, using the external tool 'ffmpeg'.
+    const CImg<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                        const char *const codec="mpeg2video", const unsigned int fps=25, const unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_ffmpeg_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_ffmpeg_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      get_split('z').save_ffmpeg_external(filename,first_frame,last_frame,codec,fps,bitrate);
+      return *this;
+    }
+
+    //! Save the image using GraphicsMagick's gm.
+    /** Function that saves the image for other file formats that are not natively handled by CImg,
+        using the tool 'gm' from the GraphicsMagick package.\n
+        This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
+        the GraphicsMagick package in order to get
+        this function working properly (see http://www.graphicsmagick.org ).
+    **/
+    const CImg<T>& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_graphicsmagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_graphicsmagick_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),_spectrum==1?"pgm":"ppm");
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      save_pnm(filetmp);
+      cimg_snprintf(command,sizeof(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_instance
+                              "save_graphicsmagick_external() : Failed to save file '%s' with external command 'gm'.",
+                              cimg_instance,
+                              filename);
+
+      if (file) cimg::fclose(file);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    //! Save an image as a gzipped file, using external tool 'gzip'.
+    const CImg<T>& save_gzip_external(const char *const filename) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_gzip_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_gzip_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *ext = cimg::split_filename(filename,body),
+        *ext2 = cimg::split_filename(body,0);
+      std::FILE *file;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      save(filetmp);
+      cimg_snprintf(command,sizeof(command),"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
+      cimg::system(command);
+      file = std::fopen(filename,"rb");
+      if (!file)
+        throw CImgIOException(_cimg_instance
+                              "save_gzip_external() : Failed to save file '%s' with external command 'gzip'.",
+                              cimg_instance,
+                              filename);
+
+      else cimg::fclose(file);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    //! Save the image using ImageMagick's convert.
+    /** Function that saves the image for other file formats that are not natively handled by CImg,
+        using the tool 'convert' from the ImageMagick package.\n
+        This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
+        the ImageMagick package in order to get
+        this function working properly (see http://www.imagemagick.org ).
+    **/
+    const CImg<T>& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_imagemagick_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_imagemagick_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 };
+      std::FILE *file;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),_spectrum==1?"pgm":"ppm");
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      save_pnm(filetmp);
+      cimg_snprintf(command,sizeof(command),"%s -quality %u%% \"%s\" \"%s\"",cimg::imagemagick_path(),quality,filetmp,filename);
+      cimg::system(command);
+      file = std::fopen(filename,"rb");
+      if (!file)
+        throw CImgIOException(_cimg_instance
+                              "save_imagemagick_external() : Failed to save file '%s' with external command 'convert'.",
+                              cimg_instance,
+                              filename);
+
+      if (file) cimg::fclose(file);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    //! Save an image as a Dicom file (need '(X)Medcon' : http://xmedcon.sourceforge.net )
+    const CImg<T>& save_medcon_external(const char *const filename) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_medcon_external() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_medcon_external() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      std::FILE *file;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s.hdr",cimg::filenamerand());
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      save_analyze(filetmp);
+      cimg_snprintf(command,sizeof(command),"%s -w -c dicom -o %s -f %s",cimg::medcon_path(),filename,filetmp);
+      cimg::system(command);
+      std::remove(filetmp);
+      cimg::split_filename(filetmp,body);
+      cimg_snprintf(filetmp,sizeof(filetmp),"%s.img",body);
+      std::remove(filetmp);
+      cimg_snprintf(command,sizeof(command),"m000-%s",filename);
+      file = std::fopen(command,"rb");
+      if (!file) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimg_instance
+                              "save_medcon_external() : Failed to save file '%s' with external command 'medcon'.",
+                              cimg_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      std::rename(command,filename);
+      return *this;
+    }
+
+    // Try to save the image if other extension is provided.
+    const CImg<T>& save_other(const char *const filename, const unsigned int quality=100) const {
+      if (!filename)
+        throw CImgArgumentException(_cimg_instance
+                                    "save_other() : Specified filename is (null).",
+                                    cimg_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimg_instance
+                                    "save_other() : Empty instance, for file '%s'.",
+                                    cimg_instance,
+                                    filename);
+
+      const unsigned int omode = cimg::exception_mode();
+      bool is_saved = true;
+      cimg::exception_mode() = 0;
+      try { save_magick(filename); }
+      catch (CImgException&) {
+        try { save_imagemagick_external(filename,quality); }
+        catch (CImgException&) {
+          try { save_graphicsmagick_external(filename,quality); }
+          catch (CImgException&) {
+            is_saved = false;
+          }
+        }
+      }
+      cimg::exception_mode() = omode;
+      if (!is_saved)
+        throw CImgIOException(_cimg_instance
+                              "save_other() : Failed to save file '%s'. Format is not natively supported, and no external commands succeeded.",
+                              cimg_instance,
+                              filename);
+      return *this;
+    }
+
+    // Get a 40x38 color logo of a 'danger' item (internal).
+    static CImg<T> logo40x38() {
+      static bool first_time = true;
+      static CImg<T> res(40,38,1,3);
+      if (first_time) {
+        const unsigned char *ptrs = cimg::logo40x38;
+        T *ptr1 = res.data(0,0,0,0), *ptr2 = res.data(0,0,0,1), *ptr3 = res.data(0,0,0,2);
+        for (unsigned int off = 0; off<res._width*res._height;) {
+          const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++);
+          for (unsigned int l = 0; l<n; ++off, ++l) { *(ptr1++) = (T)r; *(ptr2++) = (T)g; *(ptr3++) = (T)b; }
+        }
+        first_time = false;
+      }
+      return res;
+    }
+
+    //@}
+  };
+
+  /*
+   #-----------------------------------------
+   #
+   #
+   #
+   # Definition of the CImgList<T> structure
+   #
+   #
+   #
+   #------------------------------------------
+   */
+
+  //! Class representing list of images CImg<T>.
+  template<typename T>
+  struct CImgList {
+
+    //! Size of the list (number of images).
+    unsigned int _width;
+
+    //! Allocation size of the list.
+    unsigned int _allocated_width;
+
+    //! 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;
+
+    //! Value type.
+    typedef T value_type;
+
+    // Define common T-dependant types.
+    typedef typename cimg::superset<T,bool>::type Tbool;
+    typedef typename cimg::superset<T,unsigned char>::type Tuchar;
+    typedef typename cimg::superset<T,char>::type Tchar;
+    typedef typename cimg::superset<T,unsigned short>::type Tushort;
+    typedef typename cimg::superset<T,short>::type Tshort;
+    typedef typename cimg::superset<T,unsigned int>::type Tuint;
+    typedef typename cimg::superset<T,int>::type Tint;
+    typedef typename cimg::superset<T,unsigned long>::type Tulong;
+    typedef typename cimg::superset<T,long>::type Tlong;
+    typedef typename cimg::superset<T,float>::type Tfloat;
+    typedef typename cimg::superset<T,double>::type Tdouble;
+    typedef typename cimg::last<T,bool>::type boolT;
+    typedef typename cimg::last<T,unsigned char>::type ucharT;
+    typedef typename cimg::last<T,char>::type charT;
+    typedef typename cimg::last<T,unsigned short>::type ushortT;
+    typedef typename cimg::last<T,short>::type shortT;
+    typedef typename cimg::last<T,unsigned int>::type uintT;
+    typedef typename cimg::last<T,int>::type intT;
+    typedef typename cimg::last<T,unsigned long>::type ulongT;
+    typedef typename cimg::last<T,long>::type longT;
+    typedef typename cimg::last<T,float>::type floatT;
+    typedef typename cimg::last<T,double>::type doubleT;
+
+    //@}
+    //---------------------------
+    //
+    //! \name Plugins
+    //@{
+    //---------------------------
+#ifdef cimglist_plugin
+#include cimglist_plugin
+#endif
+#ifdef cimglist_plugin1
+#include cimglist_plugin1
+#endif
+#ifdef cimglist_plugin2
+#include cimglist_plugin2
+#endif
+#ifdef cimglist_plugin3
+#include cimglist_plugin3
+#endif
+#ifdef cimglist_plugin4
+#include cimglist_plugin4
+#endif
+#ifdef cimglist_plugin5
+#include cimglist_plugin5
+#endif
+#ifdef cimglist_plugin6
+#include cimglist_plugin6
+#endif
+#ifdef cimglist_plugin7
+#include cimglist_plugin7
+#endif
+#ifdef cimglist_plugin8
+#include cimglist_plugin8
+#endif
+
+    //@}
+    //--------------------------------------------------------
+    //
+    //! \name Constructors / Destructor / Instance Management
+    //@{
+    //--------------------------------------------------------
+
+    //! Destructor.
+    ~CImgList() {
+      if (_data) delete[] _data;
+    }
+
+    //! Default constructor.
+    CImgList():
+      _width(0),_allocated_width(0),_data(0) {}
+
+    //! Construct an image list containing n empty images.
+    explicit CImgList(const unsigned int n):_width(n) {
+      if (n) _data = new CImg<T>[_allocated_width = cimg::max(16UL,cimg::nearest_pow2(n))];
+      else { _allocated_width = 0; _data = 0; }
+    }
+
+    //! 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 spectrum=1):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum);
+    }
+
+    //! 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 spectrum, const T val):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum,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 spectrum, const int val0, const int val1, ...):
+      _width(0),_allocated_width(0),_data(0) {
+#define _CImgList_stdarg(t) { \
+        assign(n,width,height,depth,spectrum); \
+        const unsigned int siz = width*height*depth*spectrum, nsiz = siz*n; \
+        T *ptrd = _data->_data; \
+        va_list ap; \
+        va_start(ap,val1); \
+        for (unsigned int l = 0, s = 0, i = 0; i<nsiz; ++i) { \
+          *(ptrd++) = (T)(i==0?val0:(i==1?val1:va_arg(ap,t))); \
+          if ((++s)==siz) { ptrd = _data[++l]._data; s = 0; } \
+        } \
+        va_end(ap); \
+      }
+      _CImgList_stdarg(int);
+    }
+
+    //! 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 spectrum, const double val0, const double val1, ...):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(0),_data(0) {
+      assign(n);
+      cimglist_apply(*this,assign)(img,shared);
+    }
+
+    //! Construct an image list from one image.
+    template<typename t>
+    explicit CImgList(const CImg<t>& img, const bool shared=false):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(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, const bool shared=false):
+      _width(0),_allocated_width(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);
+    }
+
+    //! Default copy constructor.
+    template<typename t>
+    CImgList(const CImgList<t>& list):_width(0),_allocated_width(0),_data(0) {
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],false);
+    }
+
+    CImgList(const CImgList<T>& list):_width(0),_allocated_width(0),_data(0) {
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared);
+    }
+
+    //! Advanced copy constructor.
+    template<typename t>
+    CImgList(const CImgList<t>& list, const bool shared):_width(0),_allocated_width(0),_data(0) {
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],shared);
+    }
+
+    //! Construct an image list from a filename.
+    explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) {
+      assign(filename);
+    }
+
+    //! Construct an image list from a display.
+    explicit CImgList(const CImgDisplay &disp):_width(0),_allocated_width(0),_data(0) {
+      assign(disp);
+    }
+
+    //! Return a shared instance of the list.
+    CImgList<T> get_shared() {
+      CImgList<T> res(_width);
+      cimglist_for(*this,l) res[l].assign(_data[l],true);
+      return res;
+    }
+
+    //! Return a shared instance of the list.
+    const CImgList<T> get_shared() const {
+      CImgList<T> res(_width);
+      cimglist_for(*this,l) res[l].assign(_data[l],true);
+      return res;
+    }
+
+    //! 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.
+    **/
+    CImgList<T>& clear() {
+      return assign();
+    }
+
+    //! In-place version of the default constructor and default destructor.
+    CImgList<T>& assign() {
+      if (_data) delete[] _data;
+      _width = _allocated_width = 0;
+      _data = 0;
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImgList<T>& assign(const unsigned int n) {
+      if (n) {
+        if (_allocated_width<n || _allocated_width>(n<<2)) {
+          if (_data) delete[] _data;
+          _data = new CImg<T>[_allocated_width=cimg::max(16UL,cimg::nearest_pow2(n))];
+        }
+        _width = n;
+      } else assign();
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height=1,
+                        const unsigned int depth=1, const unsigned int spectrum=1) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+                        const unsigned int depth, const unsigned int spectrum, const T val) {
+      assign(n);
+      cimglist_apply(*this,assign)(width,height,depth,spectrum,val);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+                        const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) {
+      _CImgList_stdarg(int);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+                        const unsigned int depth, const unsigned int spectrum, 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, const bool shared=false) {
+      assign(list._width);
+      cimglist_for(*this,l) _data[l].assign(list[l],shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t>
+    CImgList<T>& assign(const unsigned int n, const CImg<t>& img, const bool shared=false) {
+      assign(n);
+      cimglist_apply(*this,assign)(img,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t>
+    CImgList<T>& assign(const CImg<t>& img, const bool shared=false) {
+      assign(1);
+      _data[0].assign(img,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t1, typename t2>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared=false) {
+      assign(2);
+      _data[0].assign(img1,shared); _data[1].assign(img2,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t1, typename t2, typename t3>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared=false) {
+      assign(3);
+      _data[0].assign(img1,shared); _data[1].assign(img2,shared); _data[2].assign(img3,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t1, typename t2, typename t3, typename t4>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+                        const bool shared=false) {
+      assign(4);
+      _data[0].assign(img1,shared); _data[1].assign(img2,shared); _data[2].assign(img3,shared); _data[3].assign(img4,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t1, typename t2, typename t3, typename t4, typename t5>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+                        const CImg<t5>& img5, const bool shared=false) {
+      assign(5);
+      _data[0].assign(img1,shared); _data[1].assign(img2,shared); _data[2].assign(img3,shared); _data[3].assign(img4,shared);
+      _data[4].assign(img5,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+                        const CImg<t5>& img5, const CImg<t6>& img6, const bool shared=false) {
+      assign(6);
+      _data[0].assign(img1,shared); _data[1].assign(img2,shared); _data[2].assign(img3,shared); _data[3].assign(img4,shared);
+      _data[4].assign(img5,shared); _data[5].assign(img6,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+                        const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared=false) {
+      assign(7);
+      _data[0].assign(img1,shared); _data[1].assign(img2,shared); _data[2].assign(img3,shared); _data[3].assign(img4,shared);
+      _data[4].assign(img5,shared); _data[5].assign(img6,shared); _data[6].assign(img7,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
+    CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+                        const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8,
+                        const bool shared=false) {
+      assign(8);
+      _data[0].assign(img1,shared); _data[1].assign(img2,shared); _data[2].assign(img3,shared); _data[3].assign(img4,shared);
+      _data[4].assign(img5,shared); _data[5].assign(img6,shared); _data[6].assign(img7,shared); _data[7].assign(img8,shared);
+      return *this;
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImgList<T>& assign(const char *const filename) {
+      return load(filename);
+    }
+
+    //! In-place version of the corresponding constructor.
+    CImgList<T>& assign(const CImgDisplay &disp) {
+      return assign(CImg<T>(disp));
+    }
+
+    //! Move the content of the instance image list into another one.
+    template<typename t>
+    CImgList<t>& move_to(CImgList<t>& list) {
+      list.assign(_width);
+      bool is_one_shared_element = false;
+      cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared;
+      if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]);
+      else cimglist_for(*this,l) _data[l].move_to(list[l]);
+      assign();
+      return list;
+    }
+
+    template<typename t>
+    CImgList<t>& move_to(CImgList<t>& list, const unsigned int pos) {
+      if (is_empty()) return list;
+      const unsigned int npos = pos>list._width?list._width:pos;
+      list.insert(_width,npos);
+      bool is_one_shared_element = false;
+      cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared;
+      if (is_one_shared_element) cimglist_for(*this,l) list[npos+l].assign(_data[l]);
+      else cimglist_for(*this,l) _data[l].move_to(list[npos+l]);
+      assign();
+      return list;
+    }
+
+    //! Swap all fields of two CImgList instances (use with care !)
+    CImgList<T>& swap(CImgList<T>& list) {
+      cimg::swap(_width,list._width);
+      cimg::swap(_allocated_width,list._allocated_width);
+      cimg::swap(_data,list._data);
+      return list;
+    }
+
+    //! Return a reference to an empty list.
+    static CImgList<T>& empty() {
+      static CImgList<T> _empty;
+      return _empty.assign();
+    }
+
+    //@}
+    //------------------------------------------
+    //
+    //! \name Overloaded Operators
+    //@{
+    //------------------------------------------
+
+    //! Return a reference to the i-th element of the image list.
+    CImg<T>& operator()(const unsigned int pos) {
+#if cimg_verbosity>=3
+      if (pos>=_width) {
+        cimg::warn(_cimglist_instance
+                   "operator() : Invalid image request, at position [%u].",
+                   cimglist_instance,
+                   pos);
+        return *_data;
+      }
+#endif
+      return _data[pos];
+    }
+
+    const CImg<T>& operator()(const unsigned int pos) const {
+      return const_cast<CImgList<T>*>(this)->operator()(pos);
+    }
+
+    //! Return a reference to (x,y,z,c) 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 c=0) {
+      return (*this)[pos](x,y,z,c);
+    }
+    const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
+                        const unsigned int z=0, const unsigned int c=0) const {
+      return (*this)[pos](x,y,z,c);
+    }
+
+    //! Return address of the image vector.
+    operator const CImg<T>*() const {
+      return _data;
+    }
+
+    operator CImg<T>*() {
+      return _data;
+    }
+
+    //! Operator=().
+    template<typename t>
+    CImgList<T>& operator=(const CImg<t>& img) {
+      return assign(img);
+    }
+
+    //! Operator=().
+    CImgList<T>& operator=(const CImgDisplay& disp) {
+      return assign(disp);
+    }
+
+    //! Operator=().
+    template<typename t>
+    CImgList<T>& operator=(const CImgList<t>& list) {
+      return assign(list);
+    }
+
+    CImgList<T>& operator=(const CImgList<T>& list) {
+      return assign(list);
+    }
+
+    //! Operator=().
+    CImgList<T>& operator=(const char *const filename) {
+      return assign(filename);
+    }
+
+    //! Operator+() (unary).
+    /**
+       Writting '+list' is a convenient shortcut to 'CImgList<T>(list,false)'
+       (forces a copy with non-shared elements).
+     **/
+    CImgList<T> operator+() const {
+      return CImgList<T>(*this,false);
+    }
+
+    //! Operator,().
+    template<typename t>
+    CImgList<T>& operator,(const CImg<t>& img) {
+      return insert(img);
+    }
+
+    //! Operator,().
+    template<typename t>
+    CImgList<T>& operator,(const CImgList<t>& list) {
+      return insert(list);
+    }
+
+    //! Operator>().
+    CImg<T> operator>(const char axis) const {
+      return get_append(axis,'p');
+    }
+
+    //! Operator<().
+    CImgList<T> operator<(const char axis) const {
+      return get_split(axis);
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Characteristics
+    //@{
+    //-------------------------------------
+
+    //! 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 the size of the list.
+    int width() const {
+      return (int)_width;
+    }
+
+    //! Return the size of the list.
+    unsigned int size() const {
+      return _width;
+    }
+
+    //! Return a pointer to the image buffer.
+    CImg<T> *data() {
+      return _data;
+    }
+
+    const CImg<T> *data() const {
+      return _data;
+    }
+
+    //! Return a pointer to the image buffer.
+#if cimg_verbosity>=3
+    CImg<T> *data(const unsigned int l) {
+      if (l>=size()) {
+        cimg::warn(_cimglist_instance
+                   "data() : Invalid pointer request, at position [%u].",
+                   cimglist_instance,
+                   l);
+        return _data;
+      }
+      return _data + l;
+    }
+
+    const CImg<T> *data(const unsigned int l) const {
+      return const_cast<CImgList<T>*>(this)->data(l);
+    }
+#else
+    CImg<T> *data(const unsigned int l) {
+      return _data + l;
+    }
+
+    const CImg<T> *data(const unsigned int l) const {
+      return _data + l;
+    }
+#endif
+
+    //! Returns an iterator to the beginning of the vector (STL-compliant name).
+    iterator begin() {
+      return _data;
+    }
+
+    const_iterator begin() const {
+      return _data;
+    }
+
+    //! Returns an iterator just past the last element (STL-compliant name).
+    iterator end() {
+      return _data + _width;
+    }
+
+    const_iterator end() const {
+      return _data + _width;
+    }
+
+    //! Returns a reference to the first element (STL-compliant name).
+    CImg<T>& front() {
+      return *_data;
+    }
+
+    const CImg<T>& front() const {
+      return *_data;
+    }
+
+    //! Return a reference to the last image (STL-compliant name).
+    const CImg<T>& back() const {
+      return *(_data + _width - 1);
+    }
+
+    CImg<T>& back() {
+      return *(_data + _width - 1);
+    }
+
+    //! Read an image in specified position.
+    CImg<T>& at(const int pos) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "at() : Empty instance.",
+                                    cimglist_instance);
+
+      return _data[pos<0?0:pos>=(int)_width?(int)_width-1:pos];
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions.
+    T& atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T out_val) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_val)=out_val):_data[pos].atXYZC(x,y,z,c,out_val);
+    }
+
+    T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T out_val) const {
+      return (pos<0 || pos>=(int)_width)?out_val:_data[pos].atXYZC(x,y,z,c,out_val);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions.
+    T& atNXYZC(const int pos, const int x, const int y, const int z, const int c) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZC() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZC(pos,x,y,z,c);
+    }
+
+    T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZC() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZC(pos,x,y,z,c);
+    }
+
+    T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZC(x,y,z,c);
+    }
+
+    T _atNXYZC(const int pos, const int x, const int y, const int z, const int c) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZC(x,y,z,c);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
+    T& atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T out_val) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_val)=out_val):_data[pos].atXYZ(x,y,z,c,out_val);
+    }
+
+    T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T out_val) const {
+      return (pos<0 || pos>=(int)_width)?out_val:_data[pos].atXYZ(x,y,z,c,out_val);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
+    T& atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZ() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZ(pos,x,y,z,c);
+    }
+
+    T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXYZ() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXYZ(pos,x,y,z,c);
+    }
+
+    T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZ(x,y,z,c);
+    }
+
+    T _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXYZ(x,y,z,c);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y).
+    T& atNXY(const int pos, const int x, const int y, const int z, const int c, const T out_val) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_val)=out_val):_data[pos].atXY(x,y,z,c,out_val);
+    }
+
+    T atNXY(const int pos, const int x, const int y, const int z, const int c, const T out_val) const {
+      return (pos<0 || pos>=(int)_width)?out_val:_data[pos].atXY(x,y,z,c,out_val);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c pos, \c x,\c y).
+    T& atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXY() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXY(pos,x,y,z,c);
+    }
+
+    T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNXY() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNXY(pos,x,y,z,c);
+    }
+
+    T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXY(x,y,z,c);
+    }
+
+    T _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atXY(x,y,z,c);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c pos,\c x).
+    T& atNX(const int pos, const int x, const int y, const int z, const int c, const T out_val) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_val)=out_val):_data[pos].atX(x,y,z,c,out_val);
+    }
+
+    T atNX(const int pos, const int x, const int y, const int z, const int c, const T out_val) const {
+      return (pos<0 || pos>=(int)_width)?out_val:_data[pos].atX(x,y,z,c,out_val);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c pos, \c x).
+    T& atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNX() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNX(pos,x,y,z,c);
+    }
+
+    T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atNX() : Empty instance.",
+                                    cimglist_instance);
+
+      return _atNX(pos,x,y,z,c);
+    }
+
+    T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atX(x,y,z,c);
+    }
+
+    T _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)].atX(x,y,z,c);
+    }
+
+    //! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c pos).
+    T& atN(const int pos, const int x, const int y, const int z, const int c, const T out_val) {
+      return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_val)=out_val):(*this)(pos,x,y,z,c);
+    }
+
+    T atN(const int pos, const int x, const int y, const int z, const int c, const T out_val) const {
+      return (pos<0 || pos>=(int)_width)?out_val:(*this)(pos,x,y,z,c);
+    }
+
+    //! Read a pixel value with Neumann boundary conditions for the first coordinates (\c pos).
+    T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atN() : Empty instance.",
+                                    cimglist_instance);
+      return _atN(pos,x,y,z,c);
+    }
+
+    T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "atN() : Empty instance.",
+                                    cimglist_instance);
+      return _atN(pos,x,y,z,c);
+    }
+
+    T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)](x,y,z,c);
+    }
+
+    T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const {
+      return _data[pos<0?0:(pos>=(int)_width?(int)_width-1:pos)](x,y,z,c);
+    }
+
+    //! Return a C-string containing the values of all images in the instance list.
+    CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
+      if (is_empty()) return CImg<ucharT>(1,1,1,1,0);
+      CImgList<charT> items;
+      for (unsigned int l = 0; l<_width-1; ++l) {
+        CImg<charT> item = _data[l].value_string(separator,0);
+        item.back() = separator;
+        item.move_to(items);
+      }
+      _data[_width-1].value_string(separator,0).move_to(items);
+      CImg<charT> res; (items>'x').move_to(res);
+      if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
+      return res;
+    }
+
+    //@}
+    //-------------------------------------
+    //
+    //! \name Instance Checking
+    //@{
+    //-------------------------------------
+
+    //! Return \p true if list is empty.
+    bool is_empty() const {
+      return (!_data || !_width);
+    }
+
+    //! Return \p true if list if of specified size.
+    bool is_sameN(const unsigned int n) const {
+      return (_width==n);
+    }
+
+    //! Return \p true if list if of specified size.
+    template<typename t>
+    bool is_sameN(const CImgList<t>& list) const {
+      return (_width==list._width);
+    }
+
+    // Define useful dimension check functions.
+    // (not documented because they are macro-generated).
+#define _cimglist_def_is_same1(axis) \
+    bool is_same##axis(const unsigned int val) const { \
+      bool res = true; for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(val); return res; \
+    } \
+    bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \
+      return is_sameN(n) && is_same##axis(val); \
+    } \
+
+#define _cimglist_def_is_same2(axis1,axis2) \
+    bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \
+      bool res = true; for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2(val1,val2); return res; \
+    } \
+    bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \
+      return is_sameN(n) && is_same##axis1##axis2(val1,val2); \
+    } \
+
+#define _cimglist_def_is_same3(axis1,axis2,axis3) \
+    bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
+      bool res = true; for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2##axis3(val1,val2,val3); return res; \
+    } \
+    bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
+      return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \
+    } \
+
+#define _cimglist_def_is_same(axis) \
+    template<typename t> bool is_same##axis(const CImg<t>& img) const { \
+      bool res = true; for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(img); return res; \
+    } \
+    template<typename t> bool is_same##axis(const CImgList<t>& list) const { \
+      const unsigned int lmin = cimg::min(_width,list._width); \
+      bool res = true; for (unsigned int l = 0; l<lmin && res; ++l) res = _data[l].is_same##axis(list[l]); return res; \
+    } \
+    template<typename t> bool is_sameN##axis(const unsigned int n, const CImg<t>& img) const { \
+      return (is_sameN(n) && is_same##axis(img)); \
+    } \
+    template<typename t> bool is_sameN##axis(const CImgList<t>& list) const { \
+      return (is_sameN(list) && is_same##axis(list)); \
+    }
+
+    _cimglist_def_is_same(XY)
+    _cimglist_def_is_same(XZ)
+    _cimglist_def_is_same(XC)
+    _cimglist_def_is_same(YZ)
+    _cimglist_def_is_same(YC)
+    _cimglist_def_is_same(XYZ)
+    _cimglist_def_is_same(XYC)
+    _cimglist_def_is_same(YZC)
+    _cimglist_def_is_same(XYZC)
+    _cimglist_def_is_same1(X)
+    _cimglist_def_is_same1(Y)
+    _cimglist_def_is_same1(Z)
+    _cimglist_def_is_same1(C)
+    _cimglist_def_is_same2(X,Y)
+    _cimglist_def_is_same2(X,Z)
+    _cimglist_def_is_same2(X,C)
+    _cimglist_def_is_same2(Y,Z)
+    _cimglist_def_is_same2(Y,C)
+    _cimglist_def_is_same2(Z,C)
+    _cimglist_def_is_same3(X,Y,Z)
+    _cimglist_def_is_same3(X,Y,C)
+    _cimglist_def_is_same3(X,Z,C)
+    _cimglist_def_is_same3(Y,Z,C)
+
+    bool is_sameXYZC(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc) const {
+      bool res = true;
+      for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_sameXYZC(dx,dy,dz,dc);
+      return res;
+    }
+
+    bool is_sameNXYZC(const unsigned int n, const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dc) const {
+      return is_sameN(n) && is_sameXYZC(dx,dy,dz,dc);
+    }
+
+    //! Return \c true if the list contains the pixel (n,x,y,z,c).
+    bool containsNXYZC(const int n, const int x=0, const int y=0, const int z=0, const int c=0) const {
+      if (is_empty()) return false;
+      return n>=0 && n<(int)_width && x>=0 && x<_data[n].width() && y>=0 && y<_data[n].height() &&
+        z>=0 && z<_data[n].depth() && c>=0 && c<_data[n].spectrum();
+    }
+
+    //! Return \c true if the list contains the image (n).
+    bool containsN(const int n) const {
+      if (is_empty()) return false;
+      return n>=0 && n<(int)_width;
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z,c).
+    template<typename t>
+    bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const {
+      if (is_empty()) return false;
+      cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; }
+      return false;
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z).
+    template<typename t>
+    bool contains(const T& pixel, t& n, t& x, t&y, t& z) const {
+      t c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y).
+    template<typename t>
+    bool contains(const T& pixel, t& n, t& x, t&y) const {
+      t z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x).
+    template<typename t>
+    bool contains(const T& pixel, t& n, t& x) const {
+      t y, z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n).
+    template<typename t>
+    bool contains(const T& pixel, t& n) const {
+      t x, y, z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if one of the image list contains the specified referenced value.
+    bool contains(const T& pixel) const {
+      unsigned int n, x, y, z, c;
+      return contains(pixel,n,x,y,z,c);
+    }
+
+    //! Return \c true if the list contains the image 'img'. 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 Mathematical Functions
+    //@{
+    //-------------------------------------
+
+    //! Return a reference to the minimum pixel value of the instance list.
+    T& min() {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_min = _data->_data;
+      T min_value = *ptr_min;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      }
+      return *ptr_min;
+    }
+
+    const T& min() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_min = _data->_data;
+      T min_value = *ptr_min;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+      }
+      return *ptr_min;
+    }
+
+    //! Return a reference to the maximum pixel value of the instance list.
+    T& max() {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_max = _data->_data;
+      T max_value = *ptr_max;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      }
+      return *ptr_max;
+    }
+
+    const T& max() const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_max = _data->_data;
+      T max_value = *ptr_max;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+      }
+      return *ptr_max;
+    }
+
+    //! Return a reference to the minimum pixel value of the instance list.
+    template<typename t>
+    T& min_max(t& max_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min_max() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_min = _data->_data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val<min_value) { min_value = val; ptr_min = ptrs; }
+          if (val>max_value) max_value = val;
+        }
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    template<typename t>
+    const T& min_max(t& max_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "min_max() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_min = _data->_data;
+      T min_value = *ptr_min, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val<min_value) { min_value = val; ptr_min = ptrs; }
+          if (val>max_value) max_value = val;
+        }
+      }
+      max_val = (t)max_value;
+      return *ptr_min;
+    }
+
+    //! Return a reference to the minimum pixel value of the instance list.
+    template<typename t>
+    T& max_min(t& min_val) {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max_min() : Empty instance.",
+                                    cimglist_instance);
+      T *ptr_max = _data->_data;
+      T min_value = *ptr_max, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val>max_value) { max_value = val; ptr_max = ptrs; }
+          if (val<min_value) min_value = val;
+        }
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    template<typename t>
+    const T& max_min(t& min_val) const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "max_min() : Empty instance.",
+                                    cimglist_instance);
+      const T *ptr_max = _data->_data;
+      T min_value = *ptr_max, max_value = min_value;
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        cimg_for(img,ptrs,T) {
+          const T val = *ptrs;
+          if (val>max_value) { max_value = val; ptr_max = ptrs; }
+          if (val<min_value) min_value = val;
+        }
+      }
+      min_val = (t)min_value;
+      return *ptr_max;
+    }
+
+    //@}
+    //---------------------------
+    //
+    //! \name List Manipulation
+    //@{
+    //---------------------------
+
+    //! Insert a copy of the image \p img into the current image list, at position \p pos.
+    template<typename t>
+    CImgList<T>& insert(const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) {
+      const unsigned int npos = pos==~0U?_width:pos;
+      if (npos>_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "insert() : Invalid insertion request of specified image (%u,%u,%u,%u,%p) at position %u.",
+                                    cimglist_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data,npos);
+      if (shared)
+        throw CImgArgumentException(_cimglist_instance
+                                    "insert() : Invalid insertion request of specified shared image CImg<%s>(%u,%u,%u,%u,%p) at position %u "
+                                    "(pixel types are different).",
+                                    cimglist_instance,
+                                    img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos);
+
+      CImg<T> *const new_data = (++_width>_allocated_width)?new CImg<T>[_allocated_width?(_allocated_width<<=1):(_allocated_width=16)]:0;
+      if (!_width || !_data) {
+        _data = new_data;
+        *_data = img;
+      } else {
+        if (new_data) {
+          if (npos) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos);
+          if (npos!=_width-1) std::memcpy(new_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos));
+          std::memset(_data,0,sizeof(CImg<T>)*(_width-1));
+          delete[] _data;
+          _data = new_data;
+        }
+        else if (npos!=_width-1) std::memmove(_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos));
+        _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; _data[npos]._data = 0;
+        _data[npos] = img;
+      }
+      return *this;
+    }
+
+    CImgList<T>& insert(const CImg<T>& img, const unsigned int pos=~0U, const bool shared=false) {
+      const unsigned int npos = pos==~0U?_width:pos;
+      if (npos>_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "insert() : Invalid insertion request of specified image (%u,%u,%u,%u,%p) at position %u.",
+                                    cimglist_instance,
+                                    img._width,img._height,img._depth,img._spectrum,img._data,npos);
+      CImg<T> *const new_data = (++_width>_allocated_width)?new CImg<T>[_allocated_width?(_allocated_width<<=1):(_allocated_width=16)]:0;
+      if (!_width || !_data) {
+        _data = new_data;
+        if (shared && img) {
+          _data->_width = img._width; _data->_height = img._height; _data->_depth = img._depth; _data->_spectrum = img._spectrum;
+          _data->_is_shared = true; _data->_data = img._data;
+        } else *_data = img;
+      }
+      else {
+        if (new_data) {
+          if (npos) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos);
+          if (npos!=_width-1) std::memcpy(new_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos));
+          if (shared && img) {
+            new_data[npos]._width = img._width; new_data[npos]._height = img._height; new_data[npos]._depth = img._depth;
+            new_data[npos]._spectrum = img._spectrum; new_data[npos]._is_shared = true; new_data[npos]._data = img._data;
+          } else {
+            new_data[npos]._width = new_data[npos]._height = new_data[npos]._depth = new_data[npos]._spectrum = 0; new_data[npos]._data = 0;
+            new_data[npos] = img;
+          }
+          std::memset(_data,0,sizeof(CImg<T>)*(_width-1));
+          delete[] _data;
+          _data = new_data;
+        } else {
+          if (npos!=_width-1) std::memmove(_data+npos+1,_data+npos,sizeof(CImg<T>)*(_width-1-npos));
+          if (shared && img) {
+            _data[npos]._width = img._width; _data[npos]._height = img._height; _data[npos]._depth = img._depth; _data[npos]._spectrum = img._spectrum;
+            _data[npos]._is_shared = true; _data[npos]._data = img._data;
+          } else {
+            _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; _data[npos]._data = 0;
+            _data[npos] = img;
+          }
+        }
+      }
+      return *this;
+    }
+
+    template<typename t>
+    CImgList<T> get_insert(const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
+      return (+*this).insert(img,pos,shared);
+    }
+
+    //! Insert n empty images img into the current image list, at position \p pos.
+    CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
+      CImg<T> foo;
+      if (!n) return *this;
+      const unsigned int npos = pos==~0U?_width:pos;
+      for (unsigned int i = 0; i<n; ++i) insert(foo,npos+i);
+      return *this;
+    }
+
+    CImgList<T> get_insert(const unsigned int n, const unsigned int pos=~0U) const {
+      return (+*this).insert(n,pos);
+    }
+
+    //! Insert n copies of the image \p img into the current image list, at position \p pos.
+    template<typename t>
+    CImgList<T>& insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) {
+      if (!n) return *this;
+      const unsigned int npos = pos==~0U?_width:pos;
+      insert(img,npos,shared);
+      for (unsigned int i = 1; i<n; ++i) insert(_data[npos],npos+i,shared);
+      return *this;
+    }
+
+    template<typename t>
+    CImgList<T> get_insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
+      return (+*this).insert(n,img,pos,shared);
+    }
+
+    //! Insert a copy of the image list \p list into the current image list, starting from position \p pos.
+    template<typename t>
+    CImgList<T>& insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
+      const unsigned int npos = pos==~0U?_width:pos;
+      if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos+l,shared);
+      else insert(CImgList<T>(list),npos,shared);
+      return *this;
+    }
+
+    template<typename t>
+    CImgList<T> get_insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
+      return (+*this).insert(list,pos,shared);
+    }
+
+    //! Insert n copies of the list \p list at position \p pos of the current list.
+    template<typename t>
+    CImgList<T>& insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
+      if (!n) return *this;
+      const unsigned int npos = pos==~0U?_width:pos;
+      for (unsigned int i = 0; i<n; ++i) insert(list,npos,shared);
+      return *this;
+    }
+
+    template<typename t>
+    CImgList<T> get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
+      return (+*this).insert(n,list,pos,shared);
+    }
+
+    //! Remove the images from positions \p pos1 to \p pos2.
+    CImgList<T>& remove(const unsigned int pos1, const unsigned int pos2) {
+      const unsigned int
+        npos1 = pos1<pos2?pos1:pos2,
+        tpos2 = pos1<pos2?pos2:pos1,
+        npos2 = tpos2<_width?tpos2:_width-1;
+      if (npos1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "remove() : Invalid remove request at positions %u->%u.",
+                                    cimglist_instance,
+                                    npos1,tpos2);
+      else {
+        if (tpos2>=_width)
+          throw CImgArgumentException(_cimglist_instance
+                                      "remove() : Invalid remove request at positions %u->%u.",
+                                      cimglist_instance,
+                                      npos1,tpos2);
+
+        for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign();
+        const unsigned int nb = 1 + npos2 - npos1;
+        if (!(_width-=nb)) return assign();
+        if (_width>(_allocated_width>>2) || _allocated_width<=8) { // Removing items without reallocation.
+          if (npos1!=_width) std::memmove(_data+npos1,_data+npos2+1,sizeof(CImg<T>)*(_width - npos1));
+          std::memset(_data + _width,0,sizeof(CImg<T>)*nb);
+        } else { // Removing items with reallocation.
+          _allocated_width>>=2;
+          while (_allocated_width>8 && _width<(_allocated_width>>1)) _allocated_width>>=1;
+          CImg<T> *const new_data = new CImg<T>[_allocated_width];
+          if (npos1) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos1);
+          if (npos1!=_width) std::memcpy(new_data+npos1,_data+npos2+1,sizeof(CImg<T>)*(_width-npos1));
+          if (_width!=_allocated_width) std::memset(new_data+_width,0,sizeof(_allocated_width - _width));
+          std::memset(_data,0,sizeof(CImg<T>)*(_width+nb));
+          delete[] _data;
+          _data = new_data;
+        }
+      }
+      return *this;
+    }
+
+    CImgList<T> get_remove(const unsigned int pos1, const unsigned int pos2) const {
+      return (+*this).remove(pos1,pos2);
+    }
+
+    //! Remove the image at position \p pos from the image list.
+    CImgList<T>& remove(const unsigned int pos) {
+      return remove(pos,pos);
+    }
+
+    CImgList<T> get_remove(const unsigned int pos) const {
+      return (+*this).remove(pos);
+    }
+
+    //! Remove the last image from the image list.
+    CImgList<T>& remove() {
+      return remove(_width-1);
+    }
+
+    CImgList<T> get_remove() const {
+      return (+*this).remove();
+    }
+
+    //! Reverse list order.
+    CImgList<T>& reverse() {
+      for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width-1-l]);
+      return *this;
+    }
+
+    CImgList<T> get_reverse() const {
+      return (+*this).reverse();
+    }
+
+    //! Get a sub-list.
+    CImgList<T>& images(const unsigned int i0, const unsigned int i1) {
+      return get_images(i0,i1).move_to(*this);
+    }
+
+    CImgList<T> get_images(const unsigned int i0, const unsigned int i1) const {
+      if (i0>i1 || i1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "images() : Specified sub-list indices (%u->%u) are out of bounds.",
+                                    cimglist_instance,
+                                    i0,i1);
+      CImgList<T> res(i1-i0+1);
+      cimglist_for(res,l) res[l].assign(_data[i0+l]);
+      return res;
+    }
+
+    //! Get a shared sub-list.
+    CImgList<T> get_shared_images(const unsigned int i0, const unsigned int i1) {
+      if (i0>i1 || i1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "get_shared_images() : Specified sub-list indices (%u->%u) are out of bounds.",
+                                    cimglist_instance,
+                                    i0,i1);
+      CImgList<T> res(i1-i0+1);
+      cimglist_for(res,l) res[l].assign(_data[i0+l],true);
+      return res;
+    }
+
+    const CImgList<T> get_shared_images(const unsigned int i0, const unsigned int i1) const {
+      if (i0>i1 || i1>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "get_shared_images() : Specified sub-list indices (%u->%u) are out of bounds.",
+                                    cimglist_instance,
+                                    i0,i1);
+      CImgList<T> res(i1-i0+1);
+      cimglist_for(res,l) res[l].assign(_data[i0+l],true);
+      return res;
+    }
+
+    //! Return a single image which is the concatenation of all images of the current CImgList instance.
+    /**
+       \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'c'.
+       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
+       \return A CImg<T> image corresponding to the concatenation is returned.
+    **/
+    CImg<T> get_append(const char axis, const char align='p') const {
+      if (align!='p' && align!='c' && align!='n')
+        throw CImgArgumentException(_cimglist_instance
+                                    "get_append() : Invalid alignment parameter '%c' "
+                                    "(should be { p | c | n }).",
+                                    cimglist_instance,
+                                    align);
+
+      if (is_empty()) return CImg<T>();
+      if (_width==1) return +((*this)[0]);
+      unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0;
+      CImg<T> res;
+      switch (cimg::uncase(axis)) {
+      case 'x' : { // Along the X-axis.
+        cimglist_for(*this,l) {
+          const CImg<T>& img = (*this)[l];
+          dx+=img._width; dy = cimg::max(dy,img._height); dz = cimg::max(dz,img._depth); dc = cimg::max(dc,img._spectrum);
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) switch (cimg::uncase(align)) {
+        case 'p' : {
+          cimglist_for(*this,l) {
+            res.draw_image(pos,(*this)[l]);
+            pos+=(*this)[l]._width;
+          }
+        } break;
+        case 'c' : {
+          cimglist_for(*this,l) {
+            res.draw_image(pos,(dy-(*this)[l]._height)/2,(dz-(*this)[l]._depth)/2,(dc-(*this)[l]._spectrum)/2,(*this)[l]);
+            pos+=(*this)[l]._width;
+          }
+        } break;
+        default : {
+          cimglist_for(*this,l) {
+            res.draw_image(pos,dy-(*this)[l]._height,dz-(*this)[l]._depth,dc-(*this)[l]._spectrum,(*this)[l]);
+            pos+=(*this)[l]._width;
+          }
+        }
+        }
+      } break;
+      case 'y' : { // Along the Y-axis.
+        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); dc = cimg::max(dc,img._spectrum);
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) switch (cimg::uncase(align)) {
+        case 'p' : {
+          cimglist_for(*this,l) { res.draw_image(0,pos,(*this)[l]); pos+=(*this)[l]._height; }
+        } break;
+        case 'c' : {
+          cimglist_for(*this,l) {
+            res.draw_image((dx-(*this)[l]._width)/2,pos,(dz-(*this)[l]._depth)/2,(dc-(*this)[l]._spectrum)/2,(*this)[l]);
+            pos+=(*this)[l]._height;
+          }
+        } break;
+        default : {
+          cimglist_for(*this,l) {
+            res.draw_image(dx-(*this)[l]._width,pos,dz-(*this)[l]._depth,dc-(*this)[l]._spectrum,(*this)[l]);
+            pos+=(*this)[l]._height;
+          }
+        }
+        }
+      } break;
+      case 'z' : { // Along the Z-axis.
+        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; dc = cimg::max(dc,img._spectrum);
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) switch (cimg::uncase(align)) {
+        case 'p' : {
+          cimglist_for(*this,l) { res.draw_image(0,0,pos,(*this)[l]); pos+=(*this)[l]._depth; }
+        } break;
+        case 'c' : {
+          cimglist_for(*this,l) {
+            res.draw_image((dx-(*this)[l]._width)/2,(dy-(*this)[l]._height)/2,pos,(dc-(*this)[l]._spectrum)/2,(*this)[l]);
+            pos+=(*this)[l]._depth;
+          }
+        } break;
+        default : {
+          cimglist_for(*this,l) {
+            res.draw_image(dx-(*this)[l]._width,dy-(*this)[l]._height,pos,dc-(*this)[l]._spectrum,(*this)[l]);
+            pos+=(*this)[l]._depth;
+          }
+        }
+        }
+      } break;
+      default : { // Along the C-axis.
+        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); dc+=img._spectrum;
+        }
+        res.assign(dx,dy,dz,dc,0);
+        if (res) switch (cimg::uncase(align)) {
+        case 'p' : {
+          cimglist_for(*this,l) { res.draw_image(0,0,0,pos,(*this)[l]); pos+=(*this)[l]._spectrum; }
+        } break;
+        case 'c' : {
+          cimglist_for(*this,l) {
+            res.draw_image((dx-(*this)[l]._width)/2,(dy-(*this)[l]._height)/2,(dz-(*this)[l]._depth)/2,pos,(*this)[l]);
+            pos+=(*this)[l]._spectrum;
+          }
+        } break;
+        default : {
+          cimglist_for(*this,l) {
+            res.draw_image(dx-(*this)[l]._width,dy-(*this)[l]._height,dz-(*this)[l]._depth,pos,(*this)[l]);
+            pos+=(*this)[l]._spectrum;
+          }
+        }
+        }
+      }
+      }
+      return res;
+    }
+
+    //! Return a list where each image has been split along the specified axis.
+    CImgList<T>& split(const char axis, const int nb=0) {
+      return get_split(axis,nb).move_to(*this);
+    }
+
+    CImgList<T> get_split(const char axis, const int nb=0) const {
+      CImgList<T> res;
+      cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U);
+      return res;
+    }
+
+    //! Insert image \p img at the end of the list (STL-compliant name).
+    template<typename t>
+    CImgList<T>& push_back(const CImg<t>& img) {
+      return insert(img);
+    }
+
+    //! Insert image \p img at the front of the list (STL-compliant name).
+    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 (STL-compliant name).
+    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 (STL-compliant name).
+    template<typename t>
+    CImgList<T>& push_front(const CImgList<t>& list) {
+      return insert(list,0);
+    }
+
+    //! Remove last element of the list (STL-compliant name).
+    CImgList<T>& pop_back() {
+      return remove(_width-1);
+    }
+
+    //! Remove first element of the list (STL-compliant name).
+    CImgList<T>& pop_front() {
+      return remove(0);
+    }
+
+    //! Remove the element pointed by iterator \p iter (STL-compliant name).
+    CImgList<T>& erase(const iterator iter) {
+      return remove(iter-_data);
+    }
+
+    //@}
+    //----------------------------------
+    //
+    //! \name Data Input
+    //@{
+    //----------------------------------
+
+    //! Load an image list from a file.
+    CImgList<T>& load(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      const char *const ext = cimg::split_filename(filename);
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      try {
+#ifdef cimglist_load_plugin
+        cimglist_load_plugin(filename);
+#endif
+#ifdef cimglist_load_plugin1
+        cimglist_load_plugin1(filename);
+#endif
+#ifdef cimglist_load_plugin2
+        cimglist_load_plugin2(filename);
+#endif
+#ifdef cimglist_load_plugin3
+        cimglist_load_plugin3(filename);
+#endif
+#ifdef cimglist_load_plugin4
+        cimglist_load_plugin4(filename);
+#endif
+#ifdef cimglist_load_plugin5
+        cimglist_load_plugin5(filename);
+#endif
+#ifdef cimglist_load_plugin6
+        cimglist_load_plugin6(filename);
+#endif
+#ifdef cimglist_load_plugin7
+        cimglist_load_plugin7(filename);
+#endif
+#ifdef cimglist_load_plugin8
+        cimglist_load_plugin8(filename);
+#endif
+        if (!cimg::strcasecmp(ext,"tif") ||
+            !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+        else if (!cimg::strcasecmp(ext,"cimg") ||
+                 !cimg::strcasecmp(ext,"cimgz") ||
+                 !*ext) load_cimg(filename);
+        else if (!cimg::strcasecmp(ext,"rec") ||
+                 !cimg::strcasecmp(ext,"par")) load_parrec(filename);
+        else if (!cimg::strcasecmp(ext,"avi") ||
+                 !cimg::strcasecmp(ext,"mov") ||
+                 !cimg::strcasecmp(ext,"asf") ||
+                 !cimg::strcasecmp(ext,"divx") ||
+                 !cimg::strcasecmp(ext,"flv") ||
+                 !cimg::strcasecmp(ext,"mpg") ||
+                 !cimg::strcasecmp(ext,"m1v") ||
+                 !cimg::strcasecmp(ext,"m2v") ||
+                 !cimg::strcasecmp(ext,"m4v") ||
+                 !cimg::strcasecmp(ext,"mjp") ||
+                 !cimg::strcasecmp(ext,"mkv") ||
+                 !cimg::strcasecmp(ext,"mpe") ||
+                 !cimg::strcasecmp(ext,"movie") ||
+                 !cimg::strcasecmp(ext,"ogm") ||
+                 !cimg::strcasecmp(ext,"ogg") ||
+                 !cimg::strcasecmp(ext,"qt") ||
+                 !cimg::strcasecmp(ext,"rm") ||
+                 !cimg::strcasecmp(ext,"vob") ||
+                 !cimg::strcasecmp(ext,"wmv") ||
+                 !cimg::strcasecmp(ext,"xvid") ||
+                 !cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
+        else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+        else throw CImgIOException("CImgList<%s>::load()",
+                                   pixel_type());
+      } catch (CImgIOException& e) {
+        if (!cimg::strncasecmp(e.what(),"cimg::fopen()",13)) {
+          cimg::exception_mode() = omode;
+          throw CImgIOException(_cimglist_instance
+                                "load() : Failed to open file '%s'.",
+                                cimglist_instance,
+                                filename);
+        } else try {
+          assign(1);
+          _data->load(filename);
+        } catch (CImgException&) {
+          throw CImgIOException(_cimglist_instance
+                                "load() : Failed to recognize format of file '%s'.",
+                                cimglist_instance,
+                                filename);
+        }
+      }
+      cimg::exception_mode() = omode;
+      return *this;
+    }
+
+    static CImgList<T> get_load(const char *const filename) {
+      return CImgList<T>().load(filename);
+    }
+
+    //! Load an image list from a .cimg file.
+    CImgList<T>& load_cimg(const char *const filename) {
+      return _load_cimg(0,filename);
+    }
+
+    static CImgList<T> get_load_cimg(const char *const filename) {
+      return CImgList<T>().load_cimg(filename);
+    }
+
+    //! Load an image list from a .cimg file.
+    CImgList<T>& load_cimg(std::FILE *const file) {
+      return _load_cimg(file,0);
+    }
+
+    static CImgList<T> get_load_cimg(std::FILE *const file) {
+      return CImgList<T>().load_cimg(file);
+    }
+
+    CImgList<T>& _load_cimg(std::FILE *const file, const char *const filename) {
+#ifdef cimg_use_zlib
+#define _cimgz_load_cimg_case(Tss) { \
+   Bytef *const cbuf = new Bytef[csiz]; \
+   cimg::fread(cbuf,csiz,nfile); \
+   raw.assign(W,H,D,C); \
+   unsigned long destlen = (unsigned long)raw.size()*sizeof(T); \
+   uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \
+   delete[] cbuf; \
+   const Tss *ptrs = raw._data; \
+   for (unsigned int off = raw.size(); off; --off) *(ptrd++) = (T)*(ptrs++); \
+}
+#else
+#define _cimgz_load_cimg_case(Tss) \
+   throw CImgIOException(_cimglist_instance \
+                         "load_cimg() : Unable to load compressed data from file '%s' unless zlib is enabled.", \
+                         cimglist_instance, \
+                         filename?filename:"(FILE*)");
+#endif
+
+#define _cimg_load_cimg_case(Ts,Tss) \
+      if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+        for (unsigned int l = 0; l<N; ++l) { \
+          j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \
+          W = H = D = C = 0; csiz = 0; \
+          if ((err = std::sscanf(tmp,"%u %u %u %u #%u",&W,&H,&D,&C,&csiz))<4) \
+            throw CImgIOException(_cimglist_instance \
+                                  "load_cimg() : Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \
+                                  cimglist_instance, \
+                                  W,H,D,C,l,filename?filename:("(FILE*)")); \
+          if (W*H*D*C>0) { \
+            CImg<Tss> raw; \
+            CImg<T> &img = _data[l]; \
+            img.assign(W,H,D,C); \
+            T *ptrd = img._data; \
+            if (err==5) _cimgz_load_cimg_case(Tss) \
+            else for (int toread = (int)img.size(); toread>0; ) { \
+              raw.assign(cimg::min(toread,cimg_iobuffer)); \
+              cimg::fread(raw._data,raw._width,nfile); \
+              if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); \
+              toread-=raw._width; \
+              const Tss *ptrs = raw._data; \
+              for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \
+            } \
+          } \
+        } \
+        loaded = true; \
+      }
+
+      if (!filename && !file)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      const int cimg_iobuffer = 12*1024*1024;
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      bool loaded = false, endian = cimg::endianness();
+      char tmp[256] = { 0 }, str_pixeltype[256] = { 0 }, str_endian[256] = { 0 };
+      unsigned int j, err, N = 0, W, H, D, C, csiz;
+      int i;
+      do {
+        j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+      } while (*tmp=='#' && i!=EOF);
+      err = std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
+      if (err<2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimglist_instance
+                              "load_cimg() : CImg header not found in file '%s'.",
+                              cimglist_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
+      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
+      assign(N);
+      _cimg_load_cimg_case("bool",bool);
+      _cimg_load_cimg_case("unsigned_char",unsigned char);
+      _cimg_load_cimg_case("uchar",unsigned char);
+      _cimg_load_cimg_case("char",char);
+      _cimg_load_cimg_case("unsigned_short",unsigned short);
+      _cimg_load_cimg_case("ushort",unsigned short);
+      _cimg_load_cimg_case("short",short);
+      _cimg_load_cimg_case("unsigned_int",unsigned int);
+      _cimg_load_cimg_case("uint",unsigned int);
+      _cimg_load_cimg_case("int",int);
+      _cimg_load_cimg_case("unsigned_long",unsigned long);
+      _cimg_load_cimg_case("ulong",unsigned long);
+      _cimg_load_cimg_case("long",long);
+      _cimg_load_cimg_case("float",float);
+      _cimg_load_cimg_case("double",double);
+      if (!loaded) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimglist_instance
+                              "load_cimg() : Unsupported pixel type '%s' for file '%s'.",
+                              cimglist_instance,
+                              str_pixeltype,filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load a sub-image list from a non compressed .cimg file.
+    CImgList<T>& load_cimg(const char *const filename,
+                           const unsigned int n0, const unsigned int n1,
+                           const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    static CImgList<T> get_load_cimg(const char *const filename,
+                                     const unsigned int n0, const unsigned int n1,
+                                     const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return CImgList<T>().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    //! Load a sub-image list from a non compressed .cimg file.
+    CImgList<T>& load_cimg(std::FILE *const file,
+                           const unsigned int n0, const unsigned int n1,
+                           const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                           const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    static CImgList<T> get_load_cimg(std::FILE *const file,
+                                     const unsigned int n0, const unsigned int n1,
+                                     const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                                     const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+      return CImgList<T>().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+    }
+
+    CImgList<T>& _load_cimg(std::FILE *const file, const char *const filename,
+                            const unsigned int n0, const unsigned int n1,
+                            const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int c0,
+                            const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int c1) {
+#define _cimg_load_cimg_case2(Ts,Tss) \
+      if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+        for (unsigned int l = 0; l<=nn1; ++l) { \
+          j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \
+          W = H = D = C = 0; \
+          if (std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \
+            throw CImgIOException(_cimglist_instance \
+                                  "load_cimg() : Invalid specified size (%u,%u,%u,%u) of image %u in file '%s'", \
+                                  cimglist_instance, \
+                                  W,H,D,C,l,filename?filename:"(FILE*)"); \
+          if (W*H*D*C>0) { \
+            if (l<n0 || x0>=W || y0>=H || z0>=D || c0>=D) std::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \
+            else { \
+              const unsigned int \
+                nx1 = x1>=W?W-1:x1, \
+                ny1 = y1>=H?H-1:y1, \
+                nz1 = z1>=D?D-1:z1, \
+                nc1 = c1>=C?C-1:c1; \
+              CImg<Tss> raw(1 + nx1 - x0); \
+              CImg<T> &img = _data[l - n0]; \
+              img.assign(1 + nx1 - x0,1 + ny1 - y0,1 + nz1 - z0,1 + nc1 - c0); \
+              T *ptrd = img._data; \
+              const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \
+              if (skipvb) std::fseek(nfile,skipvb,SEEK_CUR); \
+              for (unsigned int v = 1 + nc1 - c0; v; --v) { \
+                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
+                if (skipzb) std::fseek(nfile,skipzb,SEEK_CUR); \
+                for (unsigned int z = 1 + nz1 - z0; z; --z) { \
+                  const unsigned int skipyb = y0*W*sizeof(Tss); \
+                  if (skipyb) std::fseek(nfile,skipyb,SEEK_CUR); \
+                  for (unsigned int y = 1 + ny1 - y0; y; --y) { \
+                    const unsigned int skipxb = x0*sizeof(Tss); \
+                    if (skipxb) std::fseek(nfile,skipxb,SEEK_CUR); \
+                    cimg::fread(raw._data,raw._width,nfile); \
+                    if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw._width); \
+                    const Tss *ptrs = raw._data; \
+                    for (unsigned int off = raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \
+                    const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \
+                    if (skipxe) std::fseek(nfile,skipxe,SEEK_CUR); \
+                  } \
+                  const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
+                  if (skipye) std::fseek(nfile,skipye,SEEK_CUR); \
+                } \
+                const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
+                if (skipze) std::fseek(nfile,skipze,SEEK_CUR); \
+              } \
+              const unsigned int skipve = (C-1-nc1)*W*H*D*sizeof(Tss); \
+              if (skipve) std::fseek(nfile,skipve,SEEK_CUR); \
+            } \
+          } \
+        } \
+        loaded = true; \
+      }
+
+      if (!filename && !file)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      if (n1<n0 || x1<x0 || y1<y0 || z1<z0 || c1<c0)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_cimg() : Invalid specified sub-region coordinates [%u->%u] (%u,%u,%u,%u)->(%u,%u,%u,%u) for file '%s'.",
+                                    cimglist_instance,
+                                    n0,n1,x0,y0,z0,c0,x1,y1,z1,filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+      bool loaded = false, endian = cimg::endianness();
+      char tmp[256] = { 0 }, str_pixeltype[256] = { 0 }, str_endian[256] = { 0 };
+      unsigned int j, err, N, W, H, D, C;
+      int i;
+      j = 0; while((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+      err = std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
+      if (err<2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimglist_instance
+                              "load_cimg() : CImg header not found in file '%s'.",
+                              cimglist_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
+      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
+      const unsigned int nn1 = n1>=N?N-1:n1;
+      assign(1+nn1-n0);
+      _cimg_load_cimg_case2("bool",bool);
+      _cimg_load_cimg_case2("unsigned_char",unsigned char);
+      _cimg_load_cimg_case2("uchar",unsigned char);
+      _cimg_load_cimg_case2("char",char);
+      _cimg_load_cimg_case2("unsigned_short",unsigned short);
+      _cimg_load_cimg_case2("ushort",unsigned short);
+      _cimg_load_cimg_case2("short",short);
+      _cimg_load_cimg_case2("unsigned_int",unsigned int);
+      _cimg_load_cimg_case2("uint",unsigned int);
+      _cimg_load_cimg_case2("int",int);
+      _cimg_load_cimg_case2("unsigned_long",unsigned long);
+      _cimg_load_cimg_case2("ulong",unsigned long);
+      _cimg_load_cimg_case2("long",long);
+      _cimg_load_cimg_case2("float",float);
+      _cimg_load_cimg_case2("double",double);
+      if (!loaded) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimglist_instance
+                              "load_cimg() : Unsupported pixel type '%s' for file '%s'.",
+                              cimglist_instance,
+                              str_pixeltype,filename?filename:"(FILE*)");
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image list from a PAR/REC (Philips) file.
+    CImgList<T>& load_parrec(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_parrec() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      char body[1024] = { 0 }, filenamepar[1024] = { 0 }, filenamerec[1024] = { 0 };
+      const char *const ext = cimg::split_filename(filename,body);
+      if (!std::strcmp(ext,"par")) { std::strncpy(filenamepar,filename,sizeof(filenamepar)-1); cimg_snprintf(filenamerec,sizeof(filenamerec),"%s.rec",body); }
+      if (!std::strcmp(ext,"PAR")) { std::strncpy(filenamepar,filename,sizeof(filenamepar)-1); cimg_snprintf(filenamerec,sizeof(filenamerec),"%s.REC",body); }
+      if (!std::strcmp(ext,"rec")) { std::strncpy(filenamerec,filename,sizeof(filenamerec)-1); cimg_snprintf(filenamepar,sizeof(filenamepar),"%s.par",body); }
+      if (!std::strcmp(ext,"REC")) { std::strncpy(filenamerec,filename,sizeof(filenamerec)-1); cimg_snprintf(filenamepar,sizeof(filenamepar),"%s.PAR",body); }
+      std::FILE *file = cimg::fopen(filenamepar,"r");
+
+      // Parse header file
+      CImgList<floatT> st_slices;
+      CImgList<uintT> st_global;
+      int err;
+      char line[256] = { 0 };
+      do { err=std::fscanf(file,"%255[^\n]%*c",line); } while (err!=EOF && (*line=='#' || *line=='.'));
+      do {
+        unsigned int sn,sizex,sizey,pixsize;
+        float rs,ri,ss;
+        err = std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&sizex,&sizey,&ri,&rs,&ss);
+        if (err==7) {
+          CImg<floatT>::vector((float)sn,(float)pixsize,(float)sizex,(float)sizey,ri,rs,ss,0).move_to(st_slices);
+          unsigned int i; for (i = 0; i<st_global._width && sn<=st_global[i][2]; ++i) {}
+          if (i==st_global._width) CImg<uintT>::vector(sizex,sizey,sn).move_to(st_global);
+          else {
+            CImg<uintT> &vec = st_global[i];
+            if (sizex>vec[0]) vec[0] = sizex;
+            if (sizey>vec[1]) vec[1] = sizey;
+            vec[2] = sn;
+          }
+          st_slices[st_slices._width-1][7] = (float)i;
+        }
+      } while (err==7);
+
+      // Read data
+      std::FILE *file2 = cimg::fopen(filenamerec,"rb");
+      cimglist_for(st_global,l) {
+        const CImg<uintT>& vec = st_global[l];
+        CImg<T>(vec[0],vec[1],vec[2]).move_to(*this);
+      }
+
+      cimglist_for(st_slices,l) {
+        const CImg<floatT>& vec = st_slices[l];
+        const unsigned int
+          sn = (unsigned int)vec[0] - 1,
+          pixsize = (unsigned int)vec[1],
+          sizex = (unsigned int)vec[2],
+          sizey = (unsigned int)vec[3],
+          imn = (unsigned int)vec[7];
+        const float ri = vec[4], rs = vec[5], ss = vec[6];
+        switch (pixsize) {
+        case 8 : {
+          CImg<ucharT> buf(sizex,sizey);
+          cimg::fread(buf._data,sizex*sizey,file2);
+          if (cimg::endianness()) cimg::invert_endianness(buf._data,sizex*sizey);
+          CImg<T>& img = (*this)[imn];
+          cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
+        } break;
+        case 16 : {
+          CImg<ushortT> buf(sizex,sizey);
+          cimg::fread(buf._data,sizex*sizey,file2);
+          if (cimg::endianness()) cimg::invert_endianness(buf._data,sizex*sizey);
+          CImg<T>& img = (*this)[imn];
+          cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
+        } break;
+        case 32 : {
+          CImg<uintT> buf(sizex,sizey);
+          cimg::fread(buf._data,sizex*sizey,file2);
+          if (cimg::endianness()) cimg::invert_endianness(buf._data,sizex*sizey);
+          CImg<T>& img = (*this)[imn];
+          cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
+        } break;
+        default :
+          cimg::fclose(file);
+          cimg::fclose(file2);
+          throw CImgIOException(_cimglist_instance
+                                "load_parrec() : Unsupported %d-bits pixel type for file '%s'.",
+                                cimglist_instance,
+                                pixsize,filename);
+        }
+      }
+      cimg::fclose(file);
+      cimg::fclose(file2);
+      if (!_width)
+        throw CImgIOException(_cimglist_instance
+                              "load_parrec() : Failed to recognize valid PAR-REC data in file '%s'.",
+                              cimglist_instance,
+                              filename);
+      return *this;
+    }
+
+    static CImgList<T> get_load_parrec(const char *const filename) {
+      return CImgList<T>().load_parrec(filename);
+    }
+
+    //! Load an image sequence from a YUV file.
+    CImgList<T>& load_yuv(const char *const filename,
+                          const unsigned int sizex, const unsigned int sizey,
+                          const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                          const unsigned int step_frame=1, const bool yuv2rgb=true) {
+      return _load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    static CImgList<T> get_load_yuv(const char *const filename,
+                                    const unsigned int sizex, const unsigned int sizey=1,
+                                    const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                    const unsigned int step_frame=1, const bool yuv2rgb=true) {
+      return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    //! Load an image sequence from a YUV file.
+    CImgList<T>& load_yuv(std::FILE *const file,
+                          const unsigned int sizex, const unsigned int sizey,
+                          const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                          const unsigned int step_frame=1, const bool yuv2rgb=true) {
+      return _load_yuv(file,0,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    static CImgList<T> get_load_yuv(std::FILE *const file,
+                                    const unsigned int sizex, const unsigned int sizey=1,
+                                    const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                    const unsigned int step_frame=1, const bool yuv2rgb=true) {
+      return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
+    }
+
+    CImgList<T>& _load_yuv(std::FILE *const file, const char *const filename,
+                           const unsigned int sizex, const unsigned int sizey,
+                           const unsigned int first_frame, const unsigned int last_frame,
+                           const unsigned int step_frame, const bool yuv2rgb) {
+      if (!filename && !file)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_yuv() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (sizex%2 || sizey%2)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_yuv() : Invalid odd XY dimensions %ux%u in file '%s'.",
+                                    cimglist_instance,
+                                    sizex,sizey,filename?filename:"(FILE*)");
+      if (!sizex || !sizey)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_yuv() : Invalid sequence size (%u,%u) in file '%s'.",
+                                    cimglist_instance,
+                                    sizex,sizey,filename?filename:"(FILE*)");
+
+      const unsigned int
+        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+        nlast_frame = first_frame<last_frame?last_frame:first_frame,
+        nstep_frame = step_frame?step_frame:1;
+
+      CImg<ucharT> tmp(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_instance
+                                "load_yuv() : File '%s' doesn't contain frame number %u.",
+                                cimglist_instance,
+                                filename?filename:"(FILE*)",nfirst_frame);
+        }
+      }
+      unsigned int frame;
+      for (frame = nfirst_frame; !stopflag && frame<=nlast_frame; frame+=nstep_frame) {
+        tmp.fill(0);
+        // *TRY* to read the luminance part, do not replace by cimg::fread !
+        err = (int)std::fread((void*)(tmp._data),1,(size_t)(tmp._width*tmp._height),nfile);
+        if (err!=(int)(tmp._width*tmp._height)) {
+          stopflag = true;
+          if (err>0)
+            cimg::warn(_cimglist_instance
+                       "load_yuv() : File '%s' contains incomplete data or given image dimensions (%u,%u) are incorrect.",
+                       cimglist_instance,
+                       filename?filename:"(FILE*)",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_instance
+                         "load_yuv() : File '%s' contains incomplete data or given image dimensions (%u,%u) are incorrect.",
+                         cimglist_instance,
+                         filename?filename:"(FILE*)",sizex,sizey);
+          } else {
+            cimg_forXY(UV,x,y) {
+              const int x2 = x*2, y2 = y*2;
+              tmp(x2,y2,1) = tmp(x2+1,y2,1) = tmp(x2,y2+1,1) = tmp(x2+1,y2+1,1) = UV(x,y,0);
+              tmp(x2,y2,2) = tmp(x2+1,y2,2) = tmp(x2,y2+1,2) = tmp(x2+1,y2+1,2) = UV(x,y,1);
+            }
+            if (yuv2rgb) tmp.YCbCrtoRGB();
+            insert(tmp);
+            if (nstep_frame>1) std::fseek(nfile,(nstep_frame-1)*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
+          }
+        }
+      }
+      if (stopflag && nlast_frame!=~0U && frame!=nlast_frame)
+        cimg::warn(_cimglist_instance
+                   "load_yuv() : Frame %d not reached since only %u frames were found in file '%s'.",
+                   cimglist_instance,
+                   nlast_frame,frame-1,filename?filename:"(FILE*)");
+
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Load an image from a video file, using ffmpeg libraries.
+    // This piece of code has been firstly created by David Starweather (starkdg(at)users(dot)sourceforge(dot)net)
+    // I modified it afterwards for direct inclusion in the library core.
+    CImgList<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                             const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false) {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_ffmpeg() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      const unsigned int
+        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+        nlast_frame = first_frame<last_frame?last_frame:first_frame,
+        nstep_frame = step_frame?step_frame:1;
+      assign();
+
+#ifndef cimg_use_ffmpeg
+      if ((nfirst_frame || nlast_frame!=~0U || nstep_frame>1) || (resume && (pixel_format || !pixel_format)))
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_ffmpeg() : Unable to load sub-frames from file '%s' unless libffmpeg is enabled.",
+                                    cimglist_instance,
+                                    filename);
+
+      return load_ffmpeg_external(filename);
+#else
+      const PixelFormat ffmpeg_pixfmt = pixel_format?PIX_FMT_RGB24:PIX_FMT_GRAY8;
+      avcodec_register_all();
+      av_register_all();
+      static AVFormatContext *format_ctx = 0;
+      static AVCodecContext *codec_ctx = 0;
+      static AVCodec *codec = 0;
+      static AVFrame *avframe = avcodec_alloc_frame(), *converted_frame = avcodec_alloc_frame();
+      static int vstream = 0;
+
+      if (resume) {
+        if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame)
+          throw CImgArgumentException(_cimglist_instance
+                                      "load_ffmpeg() : Failed to resume loading of file '%s', due to unallocated FFMPEG structures.",
+                                      cimglist_instance,
+                                      filename);
+      } else {
+        // Open video file, find main video stream and codec.
+        if (format_ctx) av_close_input_file(format_ctx);
+        if (av_open_input_file(&format_ctx,filename,0,0,0)!=0)
+          throw CImgIOException(_cimglist_instance
+                                "load_ffmpeg() : Failed to open file '%s'.",
+                                cimglist_instance,
+                                filename);
+
+        if (!avframe || !converted_frame || av_find_stream_info(format_ctx)<0) {
+          av_close_input_file(format_ctx); format_ctx = 0;
+          return load_ffmpeg_external(filename);
+        }
+#if cimg_verbosity>=3
+        dump_format(format_ctx,0,0,0);
+#endif
+
+        // Special command : Return informations on main video stream.
+        // as a vector 1x4 containing : (nb_frames,width,height,fps).
+        if (!first_frame && !last_frame && !step_frame) {
+          for (vstream = 0; vstream<(int)(format_ctx->nb_streams); ++vstream)
+            if (format_ctx->streams[vstream]->codec->codec_type==CODEC_TYPE_VIDEO) break;
+          if (vstream==(int)format_ctx->nb_streams) assign();
+          else {
+            CImgList<doubleT> timestamps;
+            int nb_frames;
+            AVPacket packet;
+            // Count frames and store timestamps.
+            for (nb_frames = 0; av_read_frame(format_ctx,&packet)>=0; av_free_packet(&packet))
+              if (packet.stream_index==vstream) {
+                CImg<doubleT>::vector((double)packet.pts).move_to(timestamps);
+                ++nb_frames;
+              }
+            // Get frame with, height and fps.
+            const int
+              framew = format_ctx->streams[vstream]->codec->width,
+              frameh = format_ctx->streams[vstream]->codec->height;
+            const float
+              num = (float)(format_ctx->streams[vstream]->r_frame_rate).num,
+              den = (float)(format_ctx->streams[vstream]->r_frame_rate).den,
+              fps = num/den;
+            // Return infos as a list.
+            assign(2);
+            (*this)[0].assign(1,4).fill((T)nb_frames,(T)framew,(T)frameh,(T)fps);
+            (*this)[1] = (timestamps>'y');
+          }
+          av_close_input_file(format_ctx); format_ctx = 0;
+          return *this;
+        }
+
+        for (vstream = 0; vstream<(int)(format_ctx->nb_streams) &&
+               format_ctx->streams[vstream]->codec->codec_type!=CODEC_TYPE_VIDEO; ) ++vstream;
+        if (vstream==(int)format_ctx->nb_streams) {
+          av_close_input_file(format_ctx); format_ctx = 0;
+          return load_ffmpeg_external(filename);
+        }
+        codec_ctx = format_ctx->streams[vstream]->codec;
+        codec = avcodec_find_decoder(codec_ctx->codec_id);
+        if (!codec) {
+          return load_ffmpeg_external(filename);
+        }
+        if (avcodec_open(codec_ctx,codec)<0) { // Open codec
+          return load_ffmpeg_external(filename);
+        }
+      }
+
+      // Read video frames
+      const unsigned int numBytes = avpicture_get_size(ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
+      uint8_t *const buffer = new uint8_t[numBytes];
+      avpicture_fill((AVPicture *)converted_frame,buffer,ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
+      const T foo = (T)0;
+      AVPacket packet;
+      for (unsigned int frame = 0, next_frame = nfirst_frame; frame<=nlast_frame && av_read_frame(format_ctx,&packet)>=0; ) {
+        if (packet.stream_index==(int)vstream) {
+          int decoded = 0;
+#if LIBAVCODEC_VERSION_MAJOR<53
+          avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data, packet.size);
+#else
+          avcodec_decode_video2(codec_ctx,avframe,&decoded,&packet);
+#endif
+          if (decoded) {
+            if (frame==next_frame) {
+              SwsContext *c = sws_getContext(codec_ctx->width,codec_ctx->height,codec_ctx->pix_fmt,codec_ctx->width,
+                                             codec_ctx->height,ffmpeg_pixfmt,1,0,0,0);
+              sws_scale(c,avframe->data,avframe->linesize,0,codec_ctx->height,converted_frame->data,converted_frame->linesize);
+              if (ffmpeg_pixfmt==PIX_FMT_RGB24) {
+                CImg<ucharT> next_image(*converted_frame->data,3,codec_ctx->width,codec_ctx->height,1,true);
+                next_image._get_permute_axes("yzcx",foo).move_to(*this);
+              } else {
+                CImg<ucharT> next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true);
+                next_image._get_permute_axes("yzcx",foo).move_to(*this);
+              }
+              next_frame+=nstep_frame;
+            }
+            ++frame;
+          }
+          av_free_packet(&packet);
+          if (next_frame>nlast_frame) break;
+        }
+      }
+      delete[] buffer;
+#endif
+      return *this;
+    }
+
+    static CImgList<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                       const unsigned int step_frame=1, const bool pixel_format=true) {
+      return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format);
+    }
+
+    //! Load an image from a video file (MPEG,AVI) using the external tool 'ffmpeg'.
+    CImgList<T>& load_ffmpeg_external(const char *const filename) {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_ffmpeg_external() : Specified filename is (null).",
+                                    cimglist_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, filetmp2[512] = { 0 };
+      std::FILE *file = 0;
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_000001.ppm",filetmp);
+        if ((file=std::fopen(filetmp2,"rb"))!=0) std::fclose(file);
+      } while (file);
+      cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%%6d.ppm",filetmp);
+#if cimg_OS!=2
+      cimg_snprintf(command,sizeof(command),"%s -i \"%s\" %s >/dev/null 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
+#else
+      cimg_snprintf(command,sizeof(command),"\"%s -i \"%s\" %s\" >NUL 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
+#endif
+      cimg::system(command,0);
+      const unsigned int omode = cimg::exception_mode();
+      cimg::exception_mode() = 0;
+      assign();
+      unsigned int i = 1;
+      for (bool stopflag = false; !stopflag; ++i) {
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%.6u.ppm",filetmp,i);
+        CImg<T> img;
+        try { img.load_pnm(filetmp2); }
+        catch (CImgException&) { stopflag = true; }
+        if (img) { img.move_to(*this); std::remove(filetmp2); }
+      }
+      cimg::exception_mode() = omode;
+      if (is_empty())
+        throw CImgIOException(_cimglist_instance
+                              "load_ffmpeg_external() : Failed to open file '%s' with external command 'ffmpeg'.",
+                              cimglist_instance,
+                              filename);
+      return *this;
+    }
+
+    static CImgList<T> get_load_ffmpeg_external(const char *const filename) {
+      return CImgList<T>().load_ffmpeg_external(filename);
+    }
+
+    //! Load a gzipped list, using external tool 'gunzip'.
+    CImgList<T>& load_gzip_external(const char *const filename) {
+      if (!filename)
+        throw CImgIOException(_cimglist_instance
+                              "load_gzip_external() : Specified filename is (null).",
+                              cimglist_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *ext = cimg::split_filename(filename,body),
+        *ext2 = cimg::split_filename(body,0);
+      std::FILE *file = 0;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+      cimg_snprintf(command,sizeof(command),"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
+      cimg::system(command);
+      if (!(file = std::fopen(filetmp,"rb"))) {
+        cimg::fclose(cimg::fopen(filename,"r"));
+        throw CImgIOException(_cimglist_instance
+                              "load_gzip_external() : Failed to open file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      } else cimg::fclose(file);
+      load(filetmp);
+      std::remove(filetmp);
+      return *this;
+    }
+
+    static CImgList<T> get_load_gzip_external(const char *const filename) {
+      return CImgList<T>().load_gzip_external(filename);
+    }
+
+    //! Load a 3d object from a .OFF file.
+    template<typename tf, typename tc>
+    CImgList<T>& load_off(const char *const filename,
+                          CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return get_load_off(filename,primitives,colors).move_to(*this);
+    }
+
+    template<typename tf, typename tc>
+      static CImgList<T> get_load_off(const char *const filename,
+                                      CImgList<tf>& primitives, CImgList<tc>& colors) {
+      return CImg<T>().load_off(filename,primitives,colors)<'x';
+    }
+
+    //! Load a TIFF file.
+    CImgList<T>& load_tiff(const char *const filename,
+                           const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                           const unsigned int step_frame=1) {
+      const unsigned int
+        nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+        nstep_frame = step_frame?step_frame:1;
+      unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
+#ifndef cimg_use_tiff
+      if (nfirst_frame || nlast_frame!=~0U || nstep_frame!=1)
+        throw CImgArgumentException(_cimglist_instance
+                                    "load_tiff() : Unable to load sub-images from file '%s' unless libtiff is enabled.",
+                                    cimglist_instance,
+                                    filename);
+
+      return assign(CImg<T>::get_load_tiff(filename));
+#else
+      TIFF *tif = TIFFOpen(filename,"r");
+      if (tif) {
+        unsigned int nb_images = 0;
+        do ++nb_images; while (TIFFReadDirectory(tif));
+        if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
+          cimg::warn(_cimglist_instance
+                     "load_tiff() : Invalid specified frame range is [%u,%u] (step %u) since file '%s' contains %u image(s).",
+                     cimglist_instance,
+                     nfirst_frame,nlast_frame,nstep_frame,filename,nb_images);
+
+        if (nfirst_frame>=nb_images) return assign();
+        if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
+        assign(1+(nlast_frame-nfirst_frame)/nstep_frame);
+        TIFFSetDirectory(tif,0);
+#if cimg_verbosity>=3
+        TIFFSetWarningHandler(0);
+        TIFFSetErrorHandler(0);
+#endif
+        cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame);
+        TIFFClose(tif);
+      } else throw CImgException(_cimglist_instance
+                                 "load_tiff() : Failed to open file '%s'.",
+                                 cimglist_instance,
+                                 filename);
+      return *this;
+#endif
+    }
+
+    static CImgList<T> get_load_tiff(const char *const filename,
+                                     const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                     const unsigned int step_frame=1) {
+      return CImgList<T>().load_tiff(filename,first_frame,last_frame,step_frame);
+    }
+
+    //@}
+    //----------------------------------
+    //
+    //! \name Data Output
+    //@{
+    //----------------------------------
+
+    //! Print informations about the list on the standard output.
+    const CImgList<T>& print(const char *const title=0, const bool display_stats=true) const {
+      unsigned int msiz = 0;
+      cimglist_for(*this,l) msiz+=_data[l].size();
+      msiz*=sizeof(T);
+      const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2);
+      char ntitle[64] = { 0 };
+      if (!title) cimg_snprintf(ntitle,sizeof(ntitle),"CImgList<%s>",pixel_type());
+      std::fprintf(cimg::output(),"%s: this = %p, size = %u [%u %s], data = (CImg<%s>*)%p",
+                   title?title:ntitle,(void*)this,_width,
+                   mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
+                   mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
+                   pixel_type(),(void*)begin());
+      if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end()-1));
+      else std::fprintf(cimg::output(),".\n");
+
+      char tmp[16] = { 0 };
+      cimglist_for(*this,ll) {
+        cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll);
+        std::fprintf(cimg::output(),"  ");
+        _data[ll].print(tmp,display_stats);
+        if (ll==3 && _width>8) { ll = _width-5; std::fprintf(cimg::output(),"  ...\n"); }
+      }
+      std::fflush(cimg::output());
+      return *this;
+    }
+
+    //! Display the current CImgList instance in an existing CImgDisplay window (by reference).
+    /**
+       This function displays the list images of the current CImgList instance into an existing CImgDisplay window.
+       Images of the list are concatenated in a single temporarly image for visualization purposes.
+       The function returns immediately.
+       \param disp : reference to an existing CImgDisplay instance, where the current image list will be displayed.
+       \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'c'.
+       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
+       \return A reference to the current CImgList instance is returned.
+    **/
+    const CImgList<T>& display(CImgDisplay& disp, const char axis='x', const char align='p') const {
+      get_append(axis,align).display(disp);
+      return *this;
+    }
+
+    //! Display the current CImgList instance in a new display window.
+    /**
+       This function opens a new window with a specific title and displays the list images of the current CImgList instance into it.
+       Images of the list are concatenated in a single temporarly image for visualization purposes.
+       The function returns when a key is pressed or the display window is closed by the user.
+       \param title : specify the title of the opening display window.
+       \param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'c'.
+       \param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
+       \return A reference to the current CImgList instance is returned.
+    **/
+    const CImgList<T>& display(CImgDisplay &disp, const bool display_info,
+                               const char axis='x', const char align='p') const {
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "display() : Empty instance.",
+                                    cimglist_instance);
+
+      const CImg<T> visu = get_append(axis,align);
+      if (display_info) print(disp.title());
+      visu.display(disp,false);
+      return *this;
+    }
+
+    //! Display the current CImgList instance in a new display window.
+    const CImgList<T>& display(const char *const title=0, const bool display_info=true,
+                               const char axis='x', const char align='p') const {
+      const CImg<T> visu = get_append(axis,align);
+      char ntitle[64] = { 0 };
+      if (!title) cimg_snprintf(ntitle,sizeof(ntitle),"CImgList<%s>",pixel_type());
+      if (display_info) print(title?title:ntitle);
+      visu.display(title?title:ntitle,false);
+      return *this;
+    }
+
+    //! 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 (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save() : Specified filename is (null).",
+                                    cimglist_instance);
+      // Do not test for empty instances, since .cimg format is able to manage empty instances.
+      const char *const ext = cimg::split_filename(filename);
+      char nfilename[1024] = { 0 };
+      const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
+#ifdef cimglist_save_plugin
+      cimglist_save_plugin(fn);
+#endif
+#ifdef cimglist_save_plugin1
+      cimglist_save_plugin1(fn);
+#endif
+#ifdef cimglist_save_plugin2
+      cimglist_save_plugin2(fn);
+#endif
+#ifdef cimglist_save_plugin3
+      cimglist_save_plugin3(fn);
+#endif
+#ifdef cimglist_save_plugin4
+      cimglist_save_plugin4(fn);
+#endif
+#ifdef cimglist_save_plugin5
+      cimglist_save_plugin5(fn);
+#endif
+#ifdef cimglist_save_plugin6
+      cimglist_save_plugin6(fn);
+#endif
+#ifdef cimglist_save_plugin7
+      cimglist_save_plugin7(fn);
+#endif
+#ifdef cimglist_save_plugin8
+      cimglist_save_plugin8(fn);
+#endif
+      if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
+      else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false);
+      else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
+      else if (!cimg::strcasecmp(ext,"avi") ||
+               !cimg::strcasecmp(ext,"mov") ||
+               !cimg::strcasecmp(ext,"asf") ||
+               !cimg::strcasecmp(ext,"divx") ||
+               !cimg::strcasecmp(ext,"flv") ||
+               !cimg::strcasecmp(ext,"mpg") ||
+               !cimg::strcasecmp(ext,"m1v") ||
+               !cimg::strcasecmp(ext,"m2v") ||
+               !cimg::strcasecmp(ext,"m4v") ||
+               !cimg::strcasecmp(ext,"mjp") ||
+               !cimg::strcasecmp(ext,"mkv") ||
+               !cimg::strcasecmp(ext,"mpe") ||
+               !cimg::strcasecmp(ext,"movie") ||
+               !cimg::strcasecmp(ext,"ogm") ||
+               !cimg::strcasecmp(ext,"ogg") ||
+               !cimg::strcasecmp(ext,"qt") ||
+               !cimg::strcasecmp(ext,"rm") ||
+               !cimg::strcasecmp(ext,"vob") ||
+               !cimg::strcasecmp(ext,"wmv") ||
+               !cimg::strcasecmp(ext,"xvid") ||
+               !cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
+#ifdef cimg_use_tiff
+      else if (!cimg::strcasecmp(ext,"tif") ||
+          !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
+#endif
+      else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
+      else { if (_width==1) _data[0].save(fn,-1); else cimglist_for(*this,l) _data[l].save(fn,l); }
+      return *this;
+    }
+
+    // Tell if a CImgList can be saved as one single file.
+    static bool is_saveable(const char *const filename) {
+      const char *const ext = cimg::split_filename(filename);
+      if (!cimg::strcasecmp(ext,"cimgz") ||
+#ifdef cimg_use_tiff
+          !cimg::strcasecmp(ext,"tif") ||
+          !cimg::strcasecmp(ext,"tiff") ||
+#endif
+          !cimg::strcasecmp(ext,"yuv") ||
+          !cimg::strcasecmp(ext,"avi") ||
+          !cimg::strcasecmp(ext,"mov") ||
+          !cimg::strcasecmp(ext,"asf") ||
+          !cimg::strcasecmp(ext,"divx") ||
+          !cimg::strcasecmp(ext,"flv") ||
+          !cimg::strcasecmp(ext,"mpg") ||
+          !cimg::strcasecmp(ext,"m1v") ||
+          !cimg::strcasecmp(ext,"m2v") ||
+          !cimg::strcasecmp(ext,"m4v") ||
+          !cimg::strcasecmp(ext,"mjp") ||
+          !cimg::strcasecmp(ext,"mkv") ||
+          !cimg::strcasecmp(ext,"mpe") ||
+          !cimg::strcasecmp(ext,"movie") ||
+          !cimg::strcasecmp(ext,"ogm") ||
+          !cimg::strcasecmp(ext,"ogg") ||
+          !cimg::strcasecmp(ext,"qt") ||
+          !cimg::strcasecmp(ext,"rm") ||
+          !cimg::strcasecmp(ext,"vob") ||
+          !cimg::strcasecmp(ext,"wmv") ||
+          !cimg::strcasecmp(ext,"xvid") ||
+          !cimg::strcasecmp(ext,"mpeg")) return true;
+      return false;
+    }
+
+    //! Save an image sequence, using FFMPEG library.
+    // This piece of code has been originally written by David. G. Starkweather.
+    const CImgList<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                   const unsigned int fps=25, const unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+      if (!fps)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Invalid specified framerate 0, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+      const unsigned int nlast_frame = last_frame==~0U?_width-1:last_frame;
+      if (first_frame>=_width || nlast_frame>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Out of range specified frames [%u,%u], for file '%s'.",
+                                    cimglist_instance,
+                                    first_frame,last_frame,filename);
+
+      for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!_data[ll].is_sameXYZ(_data[0]))
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg() : Invalid instance dimensions, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+#ifndef cimg_use_ffmpeg
+      return save_ffmpeg_external(filename,first_frame,last_frame,"mpeg2video",fps,bitrate);
+#else
+      avcodec_register_all();
+      av_register_all();
+      const int
+        frame_dimx = _data[first_frame].width(),
+        frame_dimy = _data[first_frame].height(),
+        frame_dimv = _data[first_frame].spectrum();
+      if (frame_dimv!=1 && frame_dimv!=3)
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg() : Image[0] (%u,%u,%u,%u,%p) has not 1 or 3 channels, for file '%s'.",
+                                    cimglist_instance,
+                                    _data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum,_data,filename);
+
+      PixelFormat dest_pxl_fmt = PIX_FMT_YUV420P;
+      PixelFormat src_pxl_fmt  = (frame_dimv == 3)?PIX_FMT_RGB24:PIX_FMT_GRAY8;
+
+      int sws_flags = SWS_FAST_BILINEAR; // Interpolation method (keeping same size images for now).
+      AVOutputFormat *fmt = 0;
+      fmt = guess_format(0,filename,0);
+      if (!fmt) fmt = guess_format("mpeg",0,0); // Default format "mpeg".
+      if (!fmt)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg() : Unable to determine codec for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+      AVFormatContext *oc = 0;
+#ifdef LIBAVFORMAT_VERSION_MAJOR
+      oc = avformat_alloc_context();
+#else
+      oc = av_alloc_format_context();
+#endif
+      if (!oc) // Failed to allocate format context.
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to allocate FFMPEG structure for format context, for file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      AVCodec *codec = 0;
+      AVFrame *picture = 0;
+      AVFrame *tmp_pict = 0;
+      oc->oformat = fmt;
+      std::sprintf(oc->filename,"%s",filename);
+
+      // Add video stream.
+      int stream_index = 0;
+      AVStream *video_str = 0;
+      if (fmt->video_codec!=CODEC_ID_NONE) {
+        video_str = av_new_stream(oc,stream_index);
+        if (!video_str) { // Failed to allocate stream.
+          av_free(oc);
+          throw CImgIOException(_cimglist_instance
+                                "save_ffmpeg() : Failed to allocate FFMPEG structure for video stream, for file '%s'.",
+                                cimglist_instance,
+                                filename);
+        }
+      } else { // No codec identified.
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to identify proper codec, for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+
+      AVCodecContext *c = video_str->codec;
+      c->codec_id = fmt->video_codec;
+      c->codec_type = CODEC_TYPE_VIDEO;
+      c->bit_rate = 1024*bitrate;
+      c->width = frame_dimx;
+      c->height = frame_dimy;
+      c->time_base.num = 1;
+      c->time_base.den = fps;
+      c->gop_size = 12;
+      c->pix_fmt = dest_pxl_fmt;
+      if (c->codec_id == CODEC_ID_MPEG2VIDEO) c->max_b_frames = 2;
+      if (c->codec_id == CODEC_ID_MPEG1VIDEO) c->mb_decision = 2;
+
+      if (av_set_parameters(oc,0)<0) { // Parameters not properly set.
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Invalid parameters set for avcodec, for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+
+      // Open codecs and alloc buffers.
+      codec = avcodec_find_encoder(c->codec_id);
+      if (!codec) { // Failed to find codec.
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : No valid codec found for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+      if (avcodec_open(c,codec)<0) // Failed to open codec.
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to open codec for file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      tmp_pict = avcodec_alloc_frame();
+      if (!tmp_pict) { // Failed to allocate memory for tmp_pict frame.
+        avcodec_close(video_str->codec);
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+      tmp_pict->linesize[0] = (src_pxl_fmt==PIX_FMT_RGB24)?3*frame_dimx:frame_dimx;
+      tmp_pict->type = FF_BUFFER_TYPE_USER;
+      int tmp_size = avpicture_get_size(src_pxl_fmt,frame_dimx,frame_dimy);
+      uint8_t *tmp_buffer = (uint8_t*)av_malloc(tmp_size);
+      if (!tmp_buffer) { // Failed to allocate memory for tmp buffer.
+        av_free(tmp_pict);
+        avcodec_close(video_str->codec);
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+
+      // Associate buffer with tmp_pict.
+      avpicture_fill((AVPicture*)tmp_pict,tmp_buffer,src_pxl_fmt,frame_dimx,frame_dimy);
+      picture = avcodec_alloc_frame();
+      if (!picture) { // Failed to allocate picture frame.
+        av_free(tmp_pict->data[0]);
+        av_free(tmp_pict);
+        avcodec_close(video_str->codec);
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+
+      int size = avpicture_get_size(c->pix_fmt,frame_dimx,frame_dimy);
+      uint8_t *buffer = (uint8_t*)av_malloc(size);
+      if (!buffer) { // Failed to allocate picture frame buffer.
+        av_free(picture);
+        av_free(tmp_pict->data[0]);
+        av_free(tmp_pict);
+        avcodec_close(video_str->codec);
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to allocate memory for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+
+      // Associate the buffer with picture.
+      avpicture_fill((AVPicture*)picture,buffer,c->pix_fmt,frame_dimx,frame_dimy);
+
+      // Open file.
+      if (!(fmt->flags&AVFMT_NOFILE)) {
+        if (url_fopen(&oc->pb,filename,URL_WRONLY)<0)
+          throw CImgIOException(_cimglist_instance
+                                "save_ffmpeg() : Failed to open file '%s'.",
+                                cimglist_instance,
+                                filename);
+      }
+
+      if (av_write_header(oc)<0)
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to write header in file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      double video_pts;
+      SwsContext *img_convert_context = 0;
+      img_convert_context = sws_getContext(frame_dimx,frame_dimy,src_pxl_fmt,
+                                           c->width,c->height,c->pix_fmt,sws_flags,0,0,0);
+      if (!img_convert_context) { // Failed to get swscale context.
+        // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
+        av_free(picture->data);
+        av_free(picture);
+        av_free(tmp_pict->data[0]);
+        av_free(tmp_pict);
+        avcodec_close(video_str->codec);
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to get conversion context for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+      int ret = 0, out_size;
+      uint8_t *video_outbuf = 0;
+      int video_outbuf_size = 1000000;
+      video_outbuf = (uint8_t*)av_malloc(video_outbuf_size);
+      if (!video_outbuf) {
+        // if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
+        av_free(picture->data);
+        av_free(picture);
+        av_free(tmp_pict->data[0]);
+        av_free(tmp_pict);
+        avcodec_close(video_str->codec);
+        av_free(oc);
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to allocate memory, for file '%s'.",
+                              cimglist_instance,
+                              filename);
+      }
+
+      // Loop through each desired image in list.
+      for (unsigned int i = first_frame; i<=nlast_frame; ++i) {
+        CImg<uint8_t> currentIm = _data[i], red, green, blue, gray;
+        if (src_pxl_fmt == PIX_FMT_RGB24) {
+          red = currentIm.get_shared_channel(0);
+          green = currentIm.get_shared_channel(1);
+          blue = currentIm.get_shared_channel(2);
+          cimg_forXY(currentIm,X,Y) { // Assign pizel values to data buffer in interlaced RGBRGB ... format.
+            tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X] = red(X,Y);
+            tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 1] = green(X,Y);
+            tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 2] = blue(X,Y);
+          }
+        } else {
+          gray = currentIm.get_shared_channel(0);
+          cimg_forXY(currentIm,X,Y) tmp_pict->data[0][Y*tmp_pict->linesize[0] + X] = gray(X,Y);
+        }
+
+        if (video_str) video_pts = (video_str->pts.val * video_str->time_base.num)/(video_str->time_base.den);
+        else video_pts = 0.0;
+        if (!video_str) break;
+        if (sws_scale(img_convert_context,tmp_pict->data,tmp_pict->linesize,0,c->height,picture->data,picture->linesize)<0) break;
+        out_size = avcodec_encode_video(c,video_outbuf,video_outbuf_size,picture);
+        if (out_size>0) {
+          AVPacket pkt;
+          av_init_packet(&pkt);
+          pkt.pts = av_rescale_q(c->coded_frame->pts,c->time_base,video_str->time_base);
+          if (c->coded_frame->key_frame) pkt.flags|=PKT_FLAG_KEY;
+          pkt.stream_index = video_str->index;
+          pkt.data = video_outbuf;
+          pkt.size = out_size;
+          ret = av_write_frame(oc,&pkt);
+        } else if (out_size<0) break;
+        if (ret) break; // Error occured in writing frame.
+      }
+
+      // Close codec.
+      if (video_str) {
+        avcodec_close(video_str->codec);
+        av_free(picture->data[0]);
+        av_free(picture);
+        av_free(tmp_pict->data[0]);
+        av_free(tmp_pict);
+      }
+      if (av_write_trailer(oc)<0)
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg() : Failed to write trailer for file '%s'.",
+                              cimglist_instance,
+                              filename);
+
+      av_freep(&oc->streams[stream_index]->codec);
+      av_freep(&oc->streams[stream_index]);
+      if (!(fmt->flags&AVFMT_NOFILE)) {
+        /*if (url_fclose(oc->pb)<0)
+          throw CImgIOException(_cimglist_instance
+                                "save_ffmpeg() : File '%s', failed to close file.",
+                                cimglist_instance,
+                                filename);
+        */
+      }
+      av_free(oc);
+      av_free(video_outbuf);
+#endif
+      return *this;
+    }
+
+    // Save an image sequence into a YUV file (internal).
+    const CImgList<T>& _save_yuv(std::FILE *const file, const char *const filename, const bool rgb2yuv) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_yuv() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_yuv() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename?filename:"(FILE*)");
+
+      if ((*this)[0].width()%2 || (*this)[0].height()%2)
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_yuv() : Invalid odd instance dimensions (%u,%u) for file '%s'.",
+                                    cimglist_instance,
+                                    (*this)[0].width(),(*this)[0].height(),
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      cimglist_for(*this,l) {
+        CImg<ucharT> YCbCr((*this)[l]);
+        if (rgb2yuv) YCbCr.RGBtoYCbCr();
+        cimg::fwrite(YCbCr._data,YCbCr._width*YCbCr._height,nfile);
+        cimg::fwrite(YCbCr.get_resize(YCbCr._width/2, YCbCr._height/2,1,3,3).data(0,0,0,1),
+                     YCbCr._width*YCbCr._height/2,nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save an image sequence into a YUV file.
+    const CImgList<T>& save_yuv(const char *const filename=0, const bool rgb2yuv=true) const {
+      return _save_yuv(0,filename,rgb2yuv);
+    }
+
+    //! Save an image sequence into a YUV file.
+    const CImgList<T>& save_yuv(std::FILE *const file, const bool rgb2yuv=true) const {
+      return _save_yuv(file,0,rgb2yuv);
+    }
+
+    //! Save an image list into a .cimg file.
+    /**
+       A CImg RAW file is a simple uncompressed binary file that may be used to save list of CImg<T> images.
+       \param filename : name of the output file.
+       \return A reference to the current CImgList instance is returned.
+    **/
+    const CImgList<T>& _save_cimg(std::FILE *const file, const char *const filename, const bool compression) const {
+      if (!file && !filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+#ifndef cimg_use_zlib
+      if (compression)
+        cimg::warn(_cimglist_instance
+                   "save_cimg() : Unable to save compressed data in file '%s' unless zlib is enabled, saving them uncompressed.",
+                   cimglist_instance,
+                   filename?filename:"(FILE*)");
+#endif
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little";
+      if (std::strstr(ptype,"unsigned")==ptype) std::fprintf(nfile,"%u unsigned_%s %s_endian\n",_width,ptype+9,etype);
+      else std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype);
+      cimglist_for(*this,l) {
+        const CImg<T>& img = _data[l];
+        std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum);
+        if (img._data) {
+          CImg<T> tmp;
+          if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp._data,tmp.size()); }
+          const CImg<T>& ref = cimg::endianness()?tmp:img;
+          bool compressed = false;
+          if (compression) {
+#ifdef cimg_use_zlib
+            const unsigned long siz = sizeof(T)*ref.size();
+            unsigned long csiz = siz + siz/100 + 16;
+            Bytef *const cbuf = new Bytef[csiz];
+            if (compress(cbuf,&csiz,(Bytef*)ref._data,siz)) {
+              cimg::warn(_cimglist_instance
+                         "save_cimg() : Failed to save compressed data for file '%s', saving them uncompressed.",
+                         cimglist_instance,
+                         filename?filename:"(FILE*)");
+
+              compressed = false;
+            } else {
+              std::fprintf(nfile," #%lu\n",csiz);
+              cimg::fwrite(cbuf,csiz,nfile);
+              delete[] cbuf;
+              compressed = true;
+            }
+#else
+            compressed = false;
+#endif
+          }
+          if (!compressed) {
+            std::fputc('\n',nfile);
+            cimg::fwrite(ref._data,ref.size(),nfile);
+          }
+        } else std::fputc('\n',nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Save an image list into a CImg file (RAW binary file + simple header)
+    const CImgList<T>& save_cimg(std::FILE *file, const bool compress=false) const {
+      return _save_cimg(file,0,compress);
+    }
+
+    //! Save an image list into a CImg file (RAW binary file + simple header)
+    const CImgList<T>& save_cimg(const char *const filename, const bool compress=false) const {
+      return _save_cimg(0,filename,compress);
+    }
+
+    // Insert the instance image into 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 c0) const {
+#define _cimg_save_cimg_case(Ts,Tss) \
+      if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+        for (unsigned int l = 0; l<lmax; ++l) { \
+          j = 0; while((i=std::fgetc(nfile))!='\n') tmp[j++]=(char)i; tmp[j] = 0; \
+          W = H = D = C = 0; \
+          if (std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&C)!=4) \
+            throw CImgIOException(_cimglist_instance \
+                                  "save_cimg() : Invalid size (%u,%u,%u,%u) of image[%u], for file '%s'.", \
+                                  cimglist_instance, \
+                                  W,H,D,C,l,filename?filename:"(FILE*)"); \
+          if (W*H*D*C>0) { \
+            if (l<n0 || x0>=W || y0>=H || z0>=D || c0>=D) std::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \
+            else { \
+              const CImg<T>& img = (*this)[l - n0]; \
+              const T *ptrs = img._data; \
+              const unsigned int \
+                x1 = x0 + img._width - 1, \
+                y1 = y0 + img._height - 1, \
+                z1 = z0 + img._depth - 1, \
+                c1 = c0 + img._spectrum - 1, \
+                nx1 = x1>=W?W-1:x1, \
+                ny1 = y1>=H?H-1:y1, \
+                nz1 = z1>=D?D-1:z1, \
+                nc1 = c1>=C?C-1:c1; \
+              CImg<Tss> raw(1+nx1-x0); \
+              const unsigned int skipvb = c0*W*H*D*sizeof(Tss); \
+              if (skipvb) std::fseek(nfile,skipvb,SEEK_CUR); \
+              for (unsigned int v = 1 + nc1 - c0; v; --v) { \
+                const unsigned int skipzb = z0*W*H*sizeof(Tss); \
+                if (skipzb) std::fseek(nfile,skipzb,SEEK_CUR); \
+                for (unsigned int z = 1 + nz1 - z0; z; --z) { \
+                  const unsigned int skipyb = y0*W*sizeof(Tss); \
+                  if (skipyb) std::fseek(nfile,skipyb,SEEK_CUR); \
+                  for (unsigned int y = 1 + ny1 - y0; y; --y) { \
+                    const unsigned int skipxb = x0*sizeof(Tss); \
+                    if (skipxb) std::fseek(nfile,skipxb,SEEK_CUR); \
+                    raw.assign(ptrs, raw._width); \
+                    ptrs+=img._width; \
+                    if (endian) cimg::invert_endianness(raw._data,raw._width); \
+                    cimg::fwrite(raw._data,raw._width,nfile); \
+                    const unsigned int skipxe = (W - 1 - nx1)*sizeof(Tss); \
+                    if (skipxe) std::fseek(nfile,skipxe,SEEK_CUR); \
+                  } \
+                  const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \
+                  if (skipye) std::fseek(nfile,skipye,SEEK_CUR); \
+                } \
+                const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \
+                if (skipze) std::fseek(nfile,skipze,SEEK_CUR); \
+              } \
+              const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \
+              if (skipve) std::fseek(nfile,skipve,SEEK_CUR); \
+            } \
+          } \
+        } \
+        saved = true; \
+      }
+
+      if (!file && !filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_cimg() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_cimg() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename?filename:"(FILE*)");
+
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+");
+      bool saved = false, endian = cimg::endianness();
+      char tmp[256] = { 0 }, str_pixeltype[256] = { 0 }, str_endian[256] = { 0 };
+      unsigned int j, err, N, W, H, D, C;
+      int i;
+      j = 0; while((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+      err = std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
+      if (err<2) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimglist_instance
+                              "save_cimg() : CImg header not found in file '%s'.",
+                              cimglist_instance,
+                              filename?filename:"(FILE*)");
+      }
+      if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
+      else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
+      const unsigned int lmax = cimg::min(N,n0+_width);
+      _cimg_save_cimg_case("bool",bool);
+      _cimg_save_cimg_case("unsigned_char",unsigned char);
+      _cimg_save_cimg_case("uchar",unsigned char);
+      _cimg_save_cimg_case("char",char);
+      _cimg_save_cimg_case("unsigned_short",unsigned short);
+      _cimg_save_cimg_case("ushort",unsigned short);
+      _cimg_save_cimg_case("short",short);
+      _cimg_save_cimg_case("unsigned_int",unsigned int);
+      _cimg_save_cimg_case("uint",unsigned int);
+      _cimg_save_cimg_case("int",int);
+      _cimg_save_cimg_case("unsigned_long",unsigned long);
+      _cimg_save_cimg_case("ulong",unsigned long);
+      _cimg_save_cimg_case("long",long);
+      _cimg_save_cimg_case("float",float);
+      _cimg_save_cimg_case("double",double);
+      if (!saved) {
+        if (!file) cimg::fclose(nfile);
+        throw CImgIOException(_cimglist_instance
+                              "save_cimg() : Unsupported data type '%s' for file '%s'.",
+                              cimglist_instance,
+                              filename?filename:"(FILE*)",str_pixeltype);
+      }
+      if (!file) cimg::fclose(nfile);
+      return *this;
+    }
+
+    //! Insert the 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 c0) const {
+      return _save_cimg(0,filename,n0,x0,y0,z0,c0);
+    }
+
+    //! 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 c0) const {
+      return _save_cimg(file,0,n0,x0,y0,z0,c0);
+    }
+
+    // Create an empty .cimg file with specified dimensions (internal)
+    static void _save_empty_cimg(std::FILE *const file, const char *const filename,
+                                const unsigned int nb,
+                                const unsigned int dx, const unsigned int dy,
+                                const unsigned int dz, const unsigned int dc) {
+      std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+      const unsigned int siz = dx*dy*dz*dc*sizeof(T);
+      std::fprintf(nfile,"%u %s\n",nb,pixel_type());
+      for (unsigned int i=nb; i; --i) {
+        std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dc);
+        for (unsigned int off=siz; off; --off) std::fputc(0,nfile);
+      }
+      if (!file) cimg::fclose(nfile);
+    }
+
+    //! Create an empty .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 dc=1) {
+      return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc);
+    }
+
+    //! 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 dc=1) {
+      return _save_empty_cimg(file,0,nb,dx,dy,dz,dc);
+    }
+
+    //! Save a file in TIFF format.
+#ifdef cimg_use_tiff
+    const CImgList<T>& save_tiff(const char *const filename) const {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_tiff() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_tiff() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+      TIFF *tif = TIFFOpen(filename,"w");
+      if (tif) {
+        for (unsigned int dir = 0, l = 0; l<_width; ++l) {
+          const CImg<T>& img = (*this)[l];
+          if (img) {
+            if (img._depth==1) img._save_tiff(tif,dir++);
+            else cimg_forZ(img,z) img.get_slice(z)._save_tiff(tif,dir++);
+          }
+        }
+        TIFFClose(tif);
+      } else
+        throw CImgException(_cimglist_instance
+                            "save_tiff() : Failed to open stream for file '%s'.",
+                            cimglist_instance,
+                            filename);
+      return *this;
+    }
+#endif
+
+    //! Save an image list as a gzipped file, using external tool 'gzip'.
+    const CImgList<T>& save_gzip_external(const char *const filename) const {
+      if (!filename)
+        throw CImgIOException(_cimglist_instance
+                              "save_gzip_external() : Specified filename is (null).",
+                              cimglist_instance);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, body[512] = { 0 };
+      const char
+        *ext = cimg::split_filename(filename,body),
+        *ext2 = cimg::split_filename(body,0);
+      std::FILE *file;
+      do {
+        if (!cimg::strcasecmp(ext,"gz")) {
+          if (*ext2) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        } else {
+          if (*ext) cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+          else cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s.cimg",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        }
+        if ((file=std::fopen(filetmp,"rb"))!=0) std::fclose(file);
+      } while (file);
+
+      if (is_saveable(body)) {
+        save(filetmp);
+        cimg_snprintf(command,sizeof(command),"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
+        cimg::system(command);
+        file = std::fopen(filename,"rb");
+        if (!file)
+          throw CImgIOException(_cimglist_instance
+                                "save_gzip_external() : Failed to save file '%s' with external command 'gzip'.",
+                                cimglist_instance,
+                                filename);
+        else cimg::fclose(file);
+        std::remove(filetmp);
+      } else {
+        char nfilename[1024] = { 0 };
+        cimglist_for(*this,l) {
+          cimg::number_filename(body,l,6,nfilename);
+          if (*ext) std::sprintf(nfilename + std::strlen(nfilename),".%s",ext);
+          _data[l].save_gzip_external(nfilename);
+        }
+      }
+      return *this;
+    }
+
+    //! Save an image sequence using the external tool 'ffmpeg'.
+    const CImgList<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+                                            const char *const codec="mpeg2video", const unsigned int fps=25, const unsigned int bitrate=2048) const {
+      if (!filename)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg_external() : Specified filename is (null).",
+                                    cimglist_instance);
+      if (is_empty())
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg_external() : Empty instance, for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+
+      char command[1024] = { 0 }, filetmp[512] = { 0 }, filetmp2[512] = { 0 };
+      std::FILE *file = 0;
+      const unsigned int nlast_frame = last_frame==~0U?_width-1:last_frame;
+      if (first_frame>=_width || nlast_frame>=_width)
+        throw CImgArgumentException(_cimglist_instance
+                                    "save_ffmpeg_external() : Out of range specified frames [%u,%u] for file '%s'.",
+                                    cimglist_instance,
+                                    filename,first_frame,last_frame);
+
+      for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!_data[ll].is_sameXYZ(_data[0]))
+        throw CImgInstanceException(_cimglist_instance
+                                    "save_ffmpeg_external() : Invalid instance dimensions for file '%s'.",
+                                    cimglist_instance,
+                                    filename);
+      do {
+        cimg_snprintf(filetmp,sizeof(filetmp),"%s%c%s",cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_000001.ppm",filetmp);
+        if ((file=std::fopen(filetmp2,"rb"))!=0) std::fclose(file);
+      } while (file);
+      for (unsigned int l = first_frame; l<=nlast_frame; ++l) {
+        cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%.6u.ppm",filetmp,l+1);
+        if (_data[l]._depth>1 || _data[l]._spectrum!=3) _data[l].get_resize(-100,-100,1,3).save_pnm(filetmp2);
+        else _data[l].save_pnm(filetmp2);
+      }
+#if cimg_OS!=2
+      cimg_snprintf(command,sizeof(command),"ffmpeg -i %s_%%6d.ppm -vcodec %s -b %uk -r %u -y \"%s\" >/dev/null 2>&1",filetmp,codec,bitrate,fps,filename);
+#else
+      cimg_snprintf(command,sizeof(command),"\"ffmpeg -i %s_%%6d.ppm -vcodec %s -b %uk -r %u -y \"%s\"\" >NUL 2>&1",filetmp,codec,bitrate,fps,filename);
+#endif
+      cimg::system(command);
+      file = std::fopen(filename,"rb");
+      if (!file)
+        throw CImgIOException(_cimglist_instance
+                              "save_ffmpeg_external() : Failed to save file '%s' with external command 'ffmpeg'.",
+                              cimglist_instance,
+                              filename);
+      else cimg::fclose(file);
+      cimglist_for(*this,lll) { cimg_snprintf(filetmp2,sizeof(filetmp2),"%s_%.6u.ppm",filetmp,lll+1); std::remove(filetmp2); }
+      return *this;
+    }
+
+    //@}
+    //----------------------------------
+    //
+    //! \name Others
+    //@{
+    //----------------------------------
+
+    //! Create an auto-cropped font (along the X axis) from a input font \p font.
+    CImgList<T>& crop_font() {
+      return get_crop_font().move_to(*this);
+    }
+
+    CImgList<T> get_crop_font() const {
+      CImgList<T> res;
+      cimglist_for(*this,l) {
+        const CImg<T>& letter = (*this)[l];
+        int xmin = letter._width, xmax = 0;
+        cimg_forXY(letter,x,y) if (letter(x,y)) { if (x<xmin) xmin = x; if (x>xmax) xmax = x; }
+        if (xmin>xmax) CImg<T>(letter._width,letter._height,1,letter._spectrum,0).move_to(res);
+        else letter.get_crop(xmin,0,xmax,letter._height-1).move_to(res);
+      }
+      res[' '].resize(res['f']._width,-100,-100,-100,0);
+      if (' '+256<res.size()) res[' '+256].resize(res['f']._width,-100,-100,-100,0);
+      return res;
+    }
+
+
+    //! Return a CImg pre-defined font with desired size.
+    /**
+       \param font_height = height of the desired font (exact match for 11,13,17,19,24,32,38,57)
+       \param fixed_size = tell if the font has a fixed or variable width.
+    **/
+    static const CImgList<T>& font(const unsigned int font_height, const bool variable_size=true) {
+
+#define _cimg_font(sx,sy) \
+      if (!variable_size && (!font || font[0]._height!=sy)) font = _font(cimg::font##sx##x##sy,sx,sy,false); \
+      if (variable_size  && (!vfont || vfont[0]._height!=sy)) vfont = _font(cimg::font##sx##x##sy,sx,sy,true); \
+      if (font_height==sy) return variable_size?vfont:font; \
+      if (variable_size) { \
+        if (cvfont && font_height==cvfont[0]._height) return cvfont; \
+        cvfont = vfont; \
+        cimglist_for(cvfont,l) \
+          cvfont[l].resize(cimg::max(1U,cvfont[l]._width*font_height/cvfont[l]._height),font_height,-100,-100, \
+                           cvfont[0]._height>font_height?2:5); \
+        return cvfont; \
+      } else { \
+        if (cfont && font_height==cfont[0]._height) return cfont; \
+        cfont = font; \
+        cimglist_for(cfont,l) \
+          cfont[l].resize(cimg::max(1U,cfont[l]._width*font_height/cfont[l]._height),font_height,-100,-100, \
+                          cfont[0]._height>font_height?2:5); \
+        return cfont; \
+      } \
+
+      static CImgList<T> font, vfont, cfont, cvfont;
+      if (!font_height) return CImgList<T>::empty();
+      if (font_height<=13) { _cimg_font(10,13); } // [1,13] -> ref 13
+      if (font_height<=28) { _cimg_font(12,24); } // [14,28] -> ref 24
+      if (font_height<=32) { _cimg_font(16,32); } // [29,32] -> ref 32
+      _cimg_font(29,57);                          // [33,+inf] -> ref 57
+    }
+
+    static CImgList<T> _font(const unsigned int *const font, const unsigned int w, const unsigned int h, const bool variable_size) {
+      CImgList<T> res(256,w,h,1,1);
+      const unsigned int *ptr = font;
+      unsigned int m = 0, val = 0;
+      for (unsigned int y = 0; y<h; ++y)
+        for (unsigned int x = 0; x<256*w; ++x) {
+          m>>=1; if (!m) { m = 0x80000000; val = *(ptr++); }
+          CImg<T>& img = res[x/w];
+          unsigned int xm = x%w;
+          img(xm,y) = (T)((val&m)?1:0);
+        }
+      if (variable_size) res.crop_font();
+      return  res.insert(res);
+    }
+
+    //! Compute a 1-D Fast Fourier Transform, along specified axis.
+    CImgList<T>& FFT(const char axis, const bool invert=false) {
+      if (is_empty()) return *this;
+      if (_width==1) insert(1);
+      if (_width>2)
+        cimg::warn(_cimglist_instance
+                   "FFT() : Instance has more than 2 images",
+                   cimglist_instance);
+
+      CImg<T>::FFT(_data[0],_data[1],axis,invert);
+      return *this;
+    }
+
+    CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
+      return CImgList<Tfloat>(*this,false).FFT(axis,invert);
+    }
+
+    //! Compute a n-d Fast Fourier Transform.
+    CImgList<T>& FFT(const bool invert=false) {
+      if (is_empty()) return *this;
+      if (_width==1) insert(1);
+      if (_width>2)
+        cimg::warn(_cimglist_instance
+                   "FFT() : Instance has more than 2 images",
+                   cimglist_instance);
+
+      CImg<T>::FFT(_data[0],_data[1],invert);
+      return *this;
+    }
+
+    CImgList<Tfloat> get_FFT(const bool invert=false) const {
+      return CImgList<Tfloat>(*this,false).FFT(invert);
+    }
+
+    //! Invert primitives orientation of a 3d object.
+    CImgList<T>& reverse_object3d() {
+      cimglist_for(*this,l) {
+        CImg<T>& p = _data[l];
+        const unsigned int siz = p.size();
+        if (siz==2 || siz==3 || siz==6 || siz==9) cimg::swap(p[0],p[1]);
+        else if (siz==4 || siz==12) cimg::swap(p[0],p[3],p[1],p[2]);
+      }
+      return *this;
+    }
+
+    CImgList<T> get_reverse_object3d() const {
+      return (+*this).reverse_object3d();
+    }
+
+    //@}
+  };
+
+  /*
+  #---------------------------------------------
+  #
+   # Completion of previously declared functions
+   #
+   #----------------------------------------------
+  */
+
+namespace cimg {
+
+  //! Display a dialog box, where a user can click standard buttons.
+  /**
+     Up to 6 buttons can be defined in the dialog window.
+     This function returns when a user clicked one of the button or closed the dialog window.
+     \param title = Title of the dialog window.
+     \param msg = Main message displayed inside the dialog window.
+     \param button1_label = Label of the 1st button.
+     \param button2_label = Label of the 2nd button.
+     \param button3_label = Label of the 3rd button.
+     \param button4_label = Label of the 4th button.
+     \param button5_label = Label of the 5th button.
+     \param button6_label = 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 *const title, const char *const msg,
+                    const char *const button1_label, const char *const button2_label,
+                    const char *const button3_label, const char *const button4_label,
+                    const char *const button5_label, const char *const button6_label,
+                    const CImg<t>& logo, const bool centering = false) {
+#if cimg_display==0
+    cimg::unused(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label,logo._data,centering);
+    throw CImgDisplayException("cimg::dialog() : No display available.");
+#else
+    const unsigned char
+      black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 200,200,200 }, gray2[] = { 150,150,150 };
+
+    // Create buttons and canvas graphics
+    CImgList<unsigned char> buttons, cbuttons, sbuttons;
+    if (button1_label) { CImg<unsigned char>().draw_text(0,0,button1_label,black,gray,1,13).move_to(buttons);
+      if (button2_label) { CImg<unsigned char>().draw_text(0,0,button2_label,black,gray,1,13).move_to(buttons);
+        if (button3_label) { CImg<unsigned char>().draw_text(0,0,button3_label,black,gray,1,13).move_to(buttons);
+          if (button4_label) { CImg<unsigned char>().draw_text(0,0,button4_label,black,gray,1,13).move_to(buttons);
+            if (button5_label) { CImg<unsigned char>().draw_text(0,0,button5_label,black,gray,1,13).move_to(buttons);
+              if (button6_label) { CImg<unsigned char>().draw_text(0,0,button6_label,black,gray,1,13).move_to(buttons);
+              }}}}}}
+    if (!buttons._width)
+      throw CImgArgumentException("cimg::dialog() : No buttons have been defined.");
+    cimglist_for(buttons,l) buttons[l].resize(-100,-100,1,3);
+
+    unsigned int bw = 0, bh = 0;
+    cimglist_for(buttons,l) { bw = cimg::max(bw,buttons[l]._width); bh = cimg::max(bh,buttons[l]._height); }
+    bw+=8; bh+=8;
+    if (bw<64) bw=64;
+    if (bw>128) bw=128;
+    if (bh<24) bh=24;
+    if (bh>48) bh=48;
+
+    CImg<unsigned char> button(bw,bh,1,3);
+    button.draw_rectangle(0,0,bw-1,bh-1,gray);
+    button.draw_line(0,0,bw-1,0,white).draw_line(0,bh-1,0,0,white);
+    button.draw_line(bw-1,0,bw-1,bh-1,black).draw_line(bw-1,bh-1,0,bh-1,black);
+    button.draw_line(1,bh-2,bw-2,bh-2,gray2).draw_line(bw-2,bh-2,bw-2,1,gray2);
+    CImg<unsigned char> sbutton(bw,bh,1,3);
+    sbutton.draw_rectangle(0,0,bw-1,bh-1,gray);
+    sbutton.draw_line(0,0,bw-1,0,black).draw_line(bw-1,0,bw-1,bh-1,black);
+    sbutton.draw_line(bw-1,bh-1,0,bh-1,black).draw_line(0,bh-1,0,0,black);
+    sbutton.draw_line(1,1,bw-2,1,white).draw_line(1,bh-2,1,1,white);
+    sbutton.draw_line(bw-2,1,bw-2,bh-2,black).draw_line(bw-2,bh-2,1,bh-2,black);
+    sbutton.draw_line(2,bh-3,bw-3,bh-3,gray2).draw_line(bw-3,bh-3,bw-3,2,gray2);
+    sbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
+    sbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);
+    CImg<unsigned char> cbutton(bw,bh,1,3);
+    cbutton.draw_rectangle(0,0,bw-1,bh-1,black).draw_rectangle(1,1,bw-2,bh-2,gray2).draw_rectangle(2,2,bw-3,bh-3,gray);
+    cbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
+    cbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);
+
+    cimglist_for(buttons,ll) {
+      CImg<unsigned char>(cbutton).draw_image(1+(bw-buttons[ll].width())/2,1+(bh-buttons[ll].height())/2,buttons[ll]).
+        move_to(cbuttons);
+      CImg<unsigned char>(sbutton).draw_image((bw-buttons[ll].width())/2,(bh-buttons[ll].height())/2,buttons[ll]).
+        move_to(sbuttons);
+      CImg<unsigned char>(button).draw_image((bw-buttons[ll].width())/2,(bh-buttons[ll].height())/2,buttons[ll]).
+        move_to(buttons[ll]);
+    }
+
+    CImg<unsigned char> canvas;
+    if (msg) CImg<unsigned char>().draw_text(0,0,"%s",black,gray,1,13,msg).resize(-100,-100,1,3).move_to(canvas);
+    const unsigned int
+      bwall = (buttons._width-1)*(12+bw) + bw,
+      w = cimg::max(196U,36+logo._width+canvas._width,24+bwall),
+      h = cimg::max(96U,36+canvas._height+bh,36+logo._height+bh),
+      lx = 12 + (canvas._data?0:((w-24-logo._width)/2)),
+      ly = (h-12-bh-logo._height)/2,
+      tx = lx+logo._width+12,
+      ty = (h-12-bh-canvas._height)/2,
+      bx = (w-bwall)/2,
+      by = h-12-bh;
+
+    if (canvas._data)
+      canvas = CImg<unsigned char>(w,h,1,3).
+        draw_rectangle(0,0,w-1,h-1,gray).
+        draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
+        draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black).
+        draw_image(tx,ty,canvas);
+    else
+      canvas = CImg<unsigned char>(w,h,1,3).
+        draw_rectangle(0,0,w-1,h-1,gray).
+        draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
+        draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black);
+    if (logo._data) canvas.draw_image(lx,ly,logo);
+
+    unsigned int xbuttons[6] = { 0 };
+    cimglist_for(buttons,lll) { xbuttons[lll] = bx+(bw+12)*lll; canvas.draw_image(xbuttons[lll],by,buttons[lll]); }
+
+    // Open window and enter events loop
+    CImgDisplay disp(canvas,title?title:" ",0,false,centering?true:false);
+    if (centering) disp.move((CImgDisplay::screen_width() - disp.width())/2,
+                             (CImgDisplay::screen_height() - disp.height())/2);
+    bool stopflag = false, refresh = false;
+    int oselected = -1, oclicked = -1, selected = -1, clicked = -1;
+    while (!disp.is_closed() && !stopflag) {
+      if (refresh) {
+        if (clicked>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp);
+        else {
+          if (selected>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[selected],by,sbuttons[selected]).display(disp);
+          else canvas.display(disp);
+        }
+        refresh = false;
+      }
+      disp.wait(15);
+      if (disp.is_resized()) disp.resize(disp);
+
+      if (disp.button()&1)  {
+        oclicked = clicked;
+        clicked = -1;
+        cimglist_for(buttons,l)
+          if (disp.mouse_y()>=(int)by && disp.mouse_y()<(int)(by+bh) &&
+              disp.mouse_x()>=(int)xbuttons[l] && disp.mouse_x()<(int)(xbuttons[l]+bw)) {
+            clicked = selected = l;
+            refresh = true;
+          }
+        if (clicked!=oclicked) refresh = true;
+      } else if (clicked>=0) stopflag = true;
+
+      if (disp.key()) {
+        oselected = selected;
+        switch (disp.key()) {
+        case cimg::keyESC : selected=-1; stopflag=true; break;
+        case cimg::keyENTER : if (selected<0) selected = 0; stopflag = true; break;
+        case cimg::keyTAB :
+        case cimg::keyARROWRIGHT :
+        case cimg::keyARROWDOWN : selected = (selected+1)%buttons._width; break;
+        case cimg::keyARROWLEFT :
+        case cimg::keyARROWUP : selected = (selected+buttons._width-1)%buttons._width; break;
+        }
+        disp.set_key();
+        if (selected!=oselected) refresh = true;
+      }
+    }
+    if (!disp) selected = -1;
+    return selected;
+#endif
+  }
+
+  inline int dialog(const char *const title, const char *const msg,
+                    const char *const button1_label, const char *const button2_label, const char *const button3_label,
+                    const char *const button4_label, const char *const button5_label, const char *const button6_label,
+                    const bool centering) {
+    return dialog(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label,
+                  CImg<unsigned char>::logo40x38(),centering);
+  }
+
+  //! Evaluate math expression.
+  inline double eval(const char *const expression, const double x, const double y, const double z, const double c) {
+    static const CImg<float> empty;
+    return empty.eval(expression,x,y,z,c);
+  }
+
+  // End of cimg:: namespace
+}
+
+  // End of cimg_library:: namespace
+}
+
+#ifdef _cimg_redefine_None
+#define None 0
+#endif
+#ifdef _cimg_redefine_min
+#define min(a,b) (((a)<(b))?(a):(b))
+#endif
+#ifdef _cimg_redefine_max
+#define max(a,b) (((a)>(b))?(a):(b))
+#endif
+#ifdef _cimg_redefine_PI
+#define PI 3.141592653589793238462643383
+#endif
+
+#endif
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..872b3ce
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,68 @@
+
+ # this command finds Qt4 libraries and sets all required variables
+  # note that it's Qt4, not QT4 or qt4
+FIND_PACKAGE( Qt4 REQUIRED )
+
+  
+  # add some useful macros and variables
+  # (QT_USE_FILE is a variable defined by FIND_PACKAGE( Qt4 ) that contains a path to CMake script)
+INCLUDE( ${QT_USE_FILE} )
+
+SET( BEADS_CPP
+	beads.cpp
+	ConfigFile/ConfigFile
+	parameters
+	encode/base64
+	images/imageIntensity.cpp
+	images/imageDirect.cpp
+	images/imageCode
+	images/imageCoule
+	images/imagePaths
+	images/imageConfluent
+	images/imageProb
+	images/parameterProb
+	parameterDetection
+	images/parameterConfluent
+	detection
+	images/imageDetection
+	images/imageNumber
+	images/parameterNumber
+	images/imageContours
+	spotPROTICdbDocument
+	spot
+	spotDocument
+	spot_document_gnumeric
+	spotSvgDocument
+	images/imageDeNovo
+	CImg
+)
+IF(WIN32)
+ELSE(WIN32)
+	FIND_PACKAGE(X11 REQUIRED)
+	
+	INCLUDE_DIRECTORIES(X11_INCLUDE_DIR)
+	# X11_FOUND is true if X11 is available.
+	#    * X11_INCLUDE_DIR contains the include directories to use X11.
+	#    * X11_LIBRARIES points to the libraries to link against to use X11.
+	# Make sure the compiler can find include files from our Hello library.
+	include_directories (${X11_INCLUDE_DIR})
+	
+	ADD_DEFINITIONS(${PTHREADS_DEFINITIONS})
+	# -m -pthread FindThreads
+	
+	# Make sure the linker can find the Hello library once it is built.
+	link_directories (${X11_INCLUDE_DIR})
+ENDIF(WIN32)
+
+
+add_executable (beads ${BEADS_CPP})
+
+IF(WIN32)
+	 # Link the executable to the Hello library.
+	target_link_libraries (beads ${EXTRA_CIMG_LIBRARY} ${QT_LIBRARIES} )
+ELSE(WIN32)
+	 # Link the executable to the Hello library.
+	target_link_libraries (beads ${X11_LIBRARIES} m ${PTHREADS_LIBRARY} ${EXTRA_CIMG_LIBRARY} ${QT_LIBRARIES} )
+ENDIF(WIN32)
+
+SUBDIRS (qtbeads)
\ No newline at end of file
diff --git a/src/ConfigFile/AntBlueMaize.jpg b/src/ConfigFile/AntBlueMaize.jpg
new file mode 100644
index 0000000..3dc2845
Binary files /dev/null and b/src/ConfigFile/AntBlueMaize.jpg differ
diff --git a/src/ConfigFile/ArrowHome.gif b/src/ConfigFile/ArrowHome.gif
new file mode 100644
index 0000000..b38dd5b
Binary files /dev/null and b/src/ConfigFile/ArrowHome.gif differ
diff --git a/src/ConfigFile/ConfigFile.cpp b/src/ConfigFile/ConfigFile.cpp
new file mode 100644
index 0000000..f041064
--- /dev/null
+++ b/src/ConfigFile/ConfigFile.cpp
@@ -0,0 +1,142 @@
+// ConfigFile.cpp
+
+#include "ConfigFile.h"
+
+using std::string;
+
+ConfigFile::ConfigFile( string filename, string delimiter,
+                        string comment, string sentry )
+	: myDelimiter(delimiter), myComment(comment), mySentry(sentry)
+{
+	// Construct a ConfigFile, getting keys and values from given file
+	
+	std::ifstream in( filename.c_str() );
+	
+	if( !in ) throw file_not_found( filename ); 
+	
+	in >> (*this);
+}
+
+
+ConfigFile::ConfigFile()
+	: myDelimiter( string(1,'=') ), myComment( string(1,'#') )
+{
+	// Construct a ConfigFile without a file; empty
+}
+
+
+void ConfigFile::remove( const string& key )
+{
+	// Remove key and its value
+	myContents.erase( myContents.find( key ) );
+	return;
+}
+
+
+bool ConfigFile::keyExists( const string& key ) const
+{
+	// Indicate whether key is found
+	mapci p = myContents.find( key );
+	return ( p != myContents.end() );
+}
+
+
+/* static */
+void ConfigFile::trim( string& s )
+{
+	// Remove leading and trailing whitespace
+	static const char whitespace[] = " \n\t\v\r\f";
+	s.erase( 0, s.find_first_not_of(whitespace) );
+	s.erase( s.find_last_not_of(whitespace) + 1U );
+}
+
+
+std::ostream& operator<<( std::ostream& os, const ConfigFile& cf )
+{
+	// Save a ConfigFile to os
+	for( ConfigFile::mapci p = cf.myContents.begin();
+	     p != cf.myContents.end();
+		 ++p )
+	{
+		os << p->first << " " << cf.myDelimiter << " ";
+		os << p->second << std::endl;
+	}
+	return os;
+}
+
+
+std::istream& operator>>( std::istream& is, ConfigFile& cf )
+{
+	// Load a ConfigFile from is
+	// Read in keys and values, keeping internal whitespace
+	typedef string::size_type pos;
+	const string& delim  = cf.myDelimiter;  // separator
+	const string& comm   = cf.myComment;    // comment
+	const string& sentry = cf.mySentry;     // end of file sentry
+	const pos skip = delim.length();        // length of separator
+	
+	string nextline = "";  // might need to read ahead to see where value ends
+	
+	while( is || nextline.length() > 0 )
+	{
+		// Read an entire line at a time
+		string line;
+		if( nextline.length() > 0 )
+		{
+			line = nextline;  // we read ahead; use it now
+			nextline = "";
+		}
+		else
+		{
+			std::getline( is, line );
+		}
+		
+		// Ignore comments
+		line = line.substr( 0, line.find(comm) );
+		
+		// Check for end of file sentry
+		if( sentry != "" && line.find(sentry) != string::npos ) return is;
+		
+		// Parse the line if it contains a delimiter
+		pos delimPos = line.find( delim );
+		if( delimPos < string::npos )
+		{
+			// Extract the key
+			string key = line.substr( 0, delimPos );
+			line.replace( 0, delimPos+skip, "" );
+			
+			// See if value continues on the next line
+			// Stop at blank line, next line with a key, end of stream,
+			// or end of file sentry
+			bool terminate = false;
+			while( !terminate && is )
+			{
+				std::getline( is, nextline );
+				terminate = true;
+				
+				string nlcopy = nextline;
+				ConfigFile::trim(nlcopy);
+				if( nlcopy == "" ) continue;
+				
+				nextline = nextline.substr( 0, nextline.find(comm) );
+				if( nextline.find(delim) != string::npos )
+					continue;
+				if( sentry != "" && nextline.find(sentry) != string::npos )
+					continue;
+				
+				nlcopy = nextline;
+				ConfigFile::trim(nlcopy);
+				if( nlcopy != "" ) line += "\n";
+				line += nextline;
+				terminate = false;
+			}
+			
+			// Store key and value
+			ConfigFile::trim(key);
+			ConfigFile::trim(line);
+			cf.myContents[key] = line;  // overwrites if key is repeated
+		}
+	}
+	
+	return is;
+}
diff --git a/src/ConfigFile/ConfigFile.h b/src/ConfigFile/ConfigFile.h
new file mode 100644
index 0000000..c2f0024
--- /dev/null
+++ b/src/ConfigFile/ConfigFile.h
@@ -0,0 +1,253 @@
+// ConfigFile.h
+// Class for reading named values from configuration files
+// Richard J. Wagner  v2.1  24 May 2004  wagnerr at umich.edu
+
+// Copyright (c) 2004 Richard J. Wagner
+// 
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// Typical usage
+// -------------
+// 
+// Given a configuration file "settings.inp":
+//   atoms  = 25
+//   length = 8.0  # nanometers
+//   name = Reece Surcher
+// 
+// Named values are read in various ways, with or without default values:
+//   ConfigFile config( "settings.inp" );
+//   int atoms = config.read<int>( "atoms" );
+//   double length = config.read( "length", 10.0 );
+//   string author, title;
+//   config.readInto( author, "name" );
+//   config.readInto( title, "title", string("Untitled") );
+// 
+// See file example.cpp for more examples.
+
+#ifndef CONFIGFILE_H
+#define CONFIGFILE_H
+
+#include <string>
+#include <map>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+using std::string;
+
+class ConfigFile {
+// Data
+protected:
+	string myDelimiter;  // separator between key and value
+	string myComment;    // separator between value and comments
+	string mySentry;     // optional string to signal end of file
+	std::map<string,string> myContents;  // extracted keys and values
+	
+	typedef std::map<string,string>::iterator mapi;
+	typedef std::map<string,string>::const_iterator mapci;
+
+// Methods
+public:
+	ConfigFile( string filename,
+	            string delimiter = "=",
+	            string comment = "#",
+				string sentry = "EndConfigFile" );
+	ConfigFile();
+	
+	// Search for key and read value or optional default value
+	template<class T> T read( const string& key ) const;  // call as read<T>
+	template<class T> T read( const string& key, const T& value ) const;
+	template<class T> bool readInto( T& var, const string& key ) const;
+	template<class T>
+	bool readInto( T& var, const string& key, const T& value ) const;
+	
+	// Modify keys and values
+	template<class T> void add( string key, const T& value );
+	void remove( const string& key );
+	
+	// Check whether key exists in configuration
+	bool keyExists( const string& key ) const;
+	
+	// Check or change configuration syntax
+	string getDelimiter() const { return myDelimiter; }
+	string getComment() const { return myComment; }
+	string getSentry() const { return mySentry; }
+	string setDelimiter( const string& s )
+		{ string old = myDelimiter;  myDelimiter = s;  return old; }  
+	string setComment( const string& s )
+		{ string old = myComment;  myComment = s;  return old; }
+	
+	// Write or read configuration
+	friend std::ostream& operator<<( std::ostream& os, const ConfigFile& cf );
+	friend std::istream& operator>>( std::istream& is, ConfigFile& cf );
+	
+protected:
+	template<class T> static string T_as_string( const T& t );
+	template<class T> static T string_as_T( const string& s );
+	static void trim( string& s );
+
+
+// Exception types
+public:
+	struct file_not_found {
+		string filename;
+		file_not_found( const string& filename_ = string() )
+			: filename(filename_) {} };
+	struct key_not_found {  // thrown only by T read(key) variant of read()
+		string key;
+		key_not_found( const string& key_ = string() )
+			: key(key_) {} };
+};
+
+
+/* static */
+template<class T>
+string ConfigFile::T_as_string( const T& t )
+{
+	// Convert from a T to a string
+	// Type T must support << operator
+	std::ostringstream ost;
+	ost << t;
+	return ost.str();
+}
+
+
+/* static */
+template<class T>
+T ConfigFile::string_as_T( const string& s )
+{
+	// Convert from a string to a T
+	// Type T must support >> operator
+	T t;
+	std::istringstream ist(s);
+	ist >> t;
+	return t;
+}
+
+
+/* static */
+template<>
+inline string ConfigFile::string_as_T<string>( const string& s )
+{
+	// Convert from a string to a string
+	// In other words, do nothing
+	return s;
+}
+
+
+/* static */
+template<>
+inline bool ConfigFile::string_as_T<bool>( const string& s )
+{
+	// Convert from a string to a bool
+	// Interpret "false", "F", "no", "n", "0" as false
+	// Interpret "true", "T", "yes", "y", "1", "-1", or anything else as true
+	bool b = true;
+	string sup = s;
+	for( string::iterator p = sup.begin(); p != sup.end(); ++p )
+		*p = toupper(*p);  // make string all caps
+	if( sup==string("FALSE") || sup==string("F") ||
+	    sup==string("NO") || sup==string("N") ||
+	    sup==string("0") || sup==string("NONE") )
+		b = false;
+	return b;
+}
+
+
+template<class T>
+T ConfigFile::read( const string& key ) const
+{
+	// Read the value corresponding to key
+	mapci p = myContents.find(key);
+	if( p == myContents.end() ) throw key_not_found(key);
+	return string_as_T<T>( p->second );
+}
+
+
+template<class T>
+T ConfigFile::read( const string& key, const T& value ) const
+{
+	// Return the value corresponding to key or given default value
+	// if key is not found
+	mapci p = myContents.find(key);
+	if( p == myContents.end() ) return value;
+	return string_as_T<T>( p->second );
+}
+
+
+template<class T>
+bool ConfigFile::readInto( T& var, const string& key ) const
+{
+	// Get the value corresponding to key and store in var
+	// Return true if key is found
+	// Otherwise leave var untouched
+	mapci p = myContents.find(key);
+	bool found = ( p != myContents.end() );
+	if( found ) var = string_as_T<T>( p->second );
+	return found;
+}
+
+
+template<class T>
+bool ConfigFile::readInto( T& var, const string& key, const T& value ) const
+{
+	// Get the value corresponding to key and store in var
+	// Return true if key is found
+	// Otherwise set var to given default
+	mapci p = myContents.find(key);
+	bool found = ( p != myContents.end() );
+	if( found )
+		var = string_as_T<T>( p->second );
+	else
+		var = value;
+	return found;
+}
+
+
+template<class T>
+void ConfigFile::add( string key, const T& value )
+{
+	// Add a key with given value
+	string v = T_as_string( value );
+	trim(key);
+	trim(v);
+	myContents[key] = v;
+	return;
+}
+
+#endif  // CONFIGFILE_H
+
+// Release notes:
+// v1.0  21 May 1999
+//   + First release
+//   + Template read() access only through non-member readConfigFile()
+//   + ConfigurationFileBool is only built-in helper class
+// 
+// v2.0  3 May 2002
+//   + Shortened name from ConfigurationFile to ConfigFile
+//   + Implemented template member functions
+//   + Changed default comment separator from % to #
+//   + Enabled reading of multiple-line values
+// 
+// v2.1  24 May 2004
+//   + Made template specializations inline to avoid compiler-dependent linkage
+//   + Allowed comments within multiple-line values
+//   + Enabled blank line termination for multiple-line values
+//   + Added optional sentry to detect end of configuration file
+//   + Rewrote messy trimWhitespace() function as elegant trim()
diff --git a/src/ConfigFile/ConfigFile.html b/src/ConfigFile/ConfigFile.html
new file mode 100644
index 0000000..2f29e41
--- /dev/null
+++ b/src/ConfigFile/ConfigFile.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+
+<head>
+	<meta name="Author" content="Richard Joseph Wagner">
+	<meta name="Description" content="C++ class for configuration file reader">
+	<meta name="Keywords" content="configuration,file,reader,input,text,C++,class,ConfigFile,data,type,datatype">
+	<link href="main.css" rel="stylesheet" type="text/css">
+	<style type="text/css">
+		body { background: url("AntBlueMaize.jpg"); }
+		tr { text-indent: 2em }
+	</style>
+	<title>Configuration File Reader for C++</title>
+</head>
+
+<body>
+
+<h1>Configuration File Reader for C++</h1>
+
+ConfigFile is a C++ class for reading configuration files.  Such files are handy, for example, in running computer simulations.  The simulation program can be set to read its input parameters and settings from a configuration file.  Then, changing the simulation conditions is as easy as editing text in the configuration file rather than editing and recompiling the source code.
+
+<p>With this purpose in mind, ConfigFile is designed to be convenient, portable, and free.  Take a look at the
+<a href="ConfigFile.h">class header</a>,
+<a href="ConfigFile.cpp">class definition</a>,
+<a href="example.cpp">example program<a>, and
+<a href="example.inp">sample input<a>.
+Or, download the complete package in
+<a href="ConfigFile-2.1.zip">zip</a>
+or
+<a href="ConfigFile-2.1.tar.gz">tarball</a>
+format.
+
+<p>Features:
+<ul>
+	<li>Human-readable configuration files:
+	<ul>
+		<li><code>atoms = 250</code>
+		<li><code>length = 8.0  # nanometers</code>
+		<li><code>name = Reece Surcher</code>
+	</ul>
+	<li>Simple file opening with <code>ConfigFile config( "config.inp" );</code>
+	<li>Convenient reading of any data type:
+	<ul>
+		<li><code>int atoms = config.read<int>( "atoms" );</code>
+		<li><code>double length = config.read( "length", 10.0 );</code>
+		<li><code>string author = config.read<string>( "name", "none" );</code>
+	</ul>
+	<li>Ability to modify and save configuration files
+	<li>Thorough example program
+	<li>Validation tests
+	<li>Open source code under MIT License
+</ul>
+
+<p>If you like this software, also try my version of the
+<a href="http://www-personal.engin.umich.edu/~wagnerr/MersenneTwister.html">Mersenne Twister</a>
+random number generator.
+
+<!-- counter included only in online version -->
+
+<p><table align=center><tr>
+	<td><a href="http://www-personal.engin.umich.edu/~wagnerr/index.html">
+		<img class="nav" src="ArrowHome.gif" alt="^ home" height=16 width=16 border=0>
+	</a></td>
+	<td><span class="center"><address>
+		Rick Wagner (
+		<a href="mailto:wagnerr at umich.edu">wagnerr at umich.edu</a>
+		) 26 May 04
+	</address></span></td>
+</table>
+
+</body>
+</html>
diff --git a/src/ConfigFile/README b/src/ConfigFile/README
new file mode 100644
index 0000000..27406cf
--- /dev/null
+++ b/src/ConfigFile/README
@@ -0,0 +1,57 @@
+README for ConfigFile distribution
+Richard J. Wagner  v2.1  24 May 2004
+
+Instructions
+------------
+
+The only necessary files for using this configuration file reader are
+"ConfigFile.h" and "ConfigFile.cpp".  The class name is ConfigFile.
+
+Usage examples are in "example.cpp".  Linux or Unix users can type "make" to
+compile and then type "make run" to run the example program.
+
+The test program in "tester.cpp" will check that the class properly reads
+a variety of simple and complex configuration file entries.  To run the test
+program type "make test".
+
+When you are done with the examples and the test program, type "make clean"
+to get rid of temporary files.
+
+For Windows or Mac users with a compiler such as Metrowerks CodeWarrior or
+Microsoft Visual C++, simply add "example.cpp" and "ConfigFile.cpp" to an
+empty C++ console application.  Compile and run to see the configuration
+file reader in action.  Do likewise with "tester.cpp" to check that the
+code works properly with your compiler.
+
+If you encounter any problems, please e-mail a copy of the output and a
+description of the test system to me at "wagnerr at umich.edu".  Any other
+feedback is welcome too.
+
+
+Installation
+------------
+
+Just copy the files "ConfigFile.h" and "ConfigFile.cpp" to your working
+directory or some other place where your compiler can find them.  Add
+"ConfigFile.cpp" to your project and put the following line at the top of
+your program to access the ConfigFile class:
+
+#include "ConfigFile.h"
+
+
+Contents
+--------
+
+README            - this file
+ConfigFile.h      - declaration of ConfigFile class
+ConfigFile.cpp    - definitions of ConfigFile class
+example.cpp       - examples of using ConfigFile
+tester.cpp        - tests ConfigFile class
+example.inp       - configuration file for example program
+test.inp          - configuration file for tester program
+Triplet.h         - sample user-defined data type
+Makefile          - instructions used by "make" command
+ConfigFile.html   - Web page about ConfigFile
+AntBlueMaize.jpg  - background for ConfigFile.html
+ArrowHome.gif     - home icon for ConfigFile.html
+main.css          - style sheet for ConfigFile.html
diff --git a/src/ConfigFile/example.inp b/src/ConfigFile/example.inp
new file mode 100644
index 0000000..503fb1b
--- /dev/null
+++ b/src/ConfigFile/example.inp
@@ -0,0 +1,14 @@
+# example.inp
+# Example configuration file for ConfigFile class
+
+apples = 7             # comment after apples
+pears  = 3             # comment after pears
+price  = 1.99          # comment after price
+sale   = true          # comment after sale
+title  = one fine day  # comment after title
+weight = 2.5 kg        # comment after weight
+zone   = 1 2 3  # comment after 1st point
+         4 5 6  # comment after 2nd point
+         7 8 9  # comment after 3rd point
+
+This is also a comment since it has no equals sign and follows a blank line.
diff --git a/src/ConfigFile/main.css b/src/ConfigFile/main.css
new file mode 100644
index 0000000..08bbd90
--- /dev/null
+++ b/src/ConfigFile/main.css
@@ -0,0 +1,37 @@
+body { text-align: justify;
+       margin: 0.5em 1.5em;
+       color: #002244;
+       background-color: white;
+       background: url("AntGreenWhite.jpg");
+       font-family: "new century schoolbook", serif }
+
+a:link    { color: #0055cc }
+a:visited { color: #303077 }
+a:active  { color: #aaaa00 }
+
+h1 { text-align: center; font-size: 200%; margin: 0em 0em 0.2em; color: #000066 }
+h2 { text-align: left;   font-size: 150%; margin: 0em 0em 0.4em; color: #000000 }
+h3 { text-align: left;   font-size: 120%; margin: 0em 0em 0.4em; color: #000000 }
+li { margin: 0em 0em 0.2em }
+
+.left    { text-align:  left }
+.center  { text-align:  center }
+.right   { text-align:  right }
+.justify { text-align:  justify }
+.top     { vertical-align: top }
+.middle  { vertical-align: middle }
+.bottom  { vertical-align: bottom }
+.bold    { font-weight: bold }
+
+.caption { text-align: center; font-size: 90%; font-style: italic }
+.preview { vertical-align: top; margin: 0.2em 3.0em 0.2em 2.0em }
+.clear   { clear: both }
+a.plain  { text-decoration: none; font-size: larger; font-weight: bold }
+
+.in0 { text-indent: 0.000em; line-height: 110% }
+.in1 { text-indent: 0.143em; line-height: 110% }
+.in2 { text-indent: 0.518em; line-height: 110% }
+.in3 { text-indent: 0.982em; line-height: 110% }
+.in4 { text-indent: 1.357em; line-height: 110% }
+.in5 { text-indent: 1.500em; line-height: 110% }
+
diff --git a/src/ConfigFile/test.inp b/src/ConfigFile/test.inp
new file mode 100644
index 0000000..1370918
--- /dev/null
+++ b/src/ConfigFile/test.inp
@@ -0,0 +1,83 @@
+# test.inp
+# Test configuration file for ConfigFile class
+
+################################
+# Run through the basic syntax #
+################################
+
+integer  = 7             # comment after apples
+double   = 1.99          # comment after double
+boolean  = true          # comment after bool
+string   = one fine day  # comment after string
+weight   = 2.5 kg        # comment after weight
+triplets = 1 2 3  # comment after 1st triplet
+           4 5 6  # comment after 2nd triplet
+           7 8 9  # comment after 3rd triplet
+
+This is also a comment since it has no equals sign and follows a blank line.
+
+
+##########################################
+# Run through some more difficult syntax #
+##########################################
+
+# Repeated keys should overwrite previous values
+repeated = 1
+repeated = 2
+
+# Key recognition should be case-sensitive
+oneStall = 1
+onesTall = 111
+
+# Keys with embedded spaces should be recognized
+space key = true
+
+# An all-space value should be legal
+noValue =
+
+# An all-space key, though weird, should be legal too
+= 5
+
+# On a line with two delimiters, the second should belong to the value
+equation = y = mx + b 
+
+# Blank lines should terminate multiple-line values
+multilinePause =
+	first
+	second
+	third
+
+	fourth
+
+# But comments should not terminate multiple-line values
+multilineComment =
+	first   # 1st
+	second  # 2nd
+	third   # 3rd
+	fourth  # 4th
+
+# Commented lines in multiple-line values should simply be skipped
+multilineSkip =
+	first
+	second
+#	third
+	fourth
+
+# Assignments within comments should be ignored
+# postComment = 9
+
+# Alternative delimiters should be recognized
+atDelimiter @ 7
+
+# Alternative comment separators should be recognized
+! alternateComment = 9
+
+# A space should work as an alternative delimiter
+spaceDelimiter 7
+
+# Assignments after an active end of file sentry should be ignored
+end = before commented sentry
+# EndConfigFile
+end = before uncommented sentry
+EndConfigFile
+end = before EOF
diff --git a/src/beads.cpp b/src/beads.cpp
new file mode 100644
index 0000000..fa5d88f
--- /dev/null
+++ b/src/beads.cpp
@@ -0,0 +1,615 @@
+#include "beads.h"
+#include <vector>
+#include <iostream>
+#include "config.h"
+
+#include "images/imageCoule.h"
+#include "images/imageProb.h"
+#include "images/imagePaths.h"
+#include "images/imageDetection.h"
+#include "images/imageConfluent.h"
+#include "images/imageContours.h"
+#include "images/imageDeNovo.h"
+#include "spot.h"
+#include "detection.h"
+#include "spotSvgDocument.h"
+#include "spotPROTICdbDocument.h"
+
+#include "parameters.h"
+
+#include <QFileInfo>
+
+using namespace std;
+
+// The undef below is necessary when using a non-standard compiler.
+#ifdef cimg_use_visualcpp6
+#define std
+#endif
+
+void create_default_parameter_file(const string filename) {
+	ofstream param_stream;
+	param_stream.open(filename.c_str());
+	param_stream << "#" << filename << endl;
+	param_stream << "# beads configuration file" << endl;
+	param_stream << "# default parameters, optimized for silver staining"
+			<< endl;
+	param_stream << endl;
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for DIRECTIONS    #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "burned_pixel_threshold = 63000" << endl;
+	param_stream << endl;
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for SELECT        #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "minflux = 100" << endl;
+	param_stream << "minpath = 2" << endl;
+	param_stream << "winconfl = 300" << endl;
+	param_stream << "minbeads = 200" << endl;
+	param_stream << "confluent_intmax = 60000" << endl;
+	param_stream << "confluent_minpct = 1" << endl;
+	param_stream << endl;
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for PROBABILITIES #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "prob_threshold = 10" << endl;
+	param_stream << "sx = 3" << endl;
+	param_stream << "sy = 3" << endl;
+	param_stream << "sx_bottom = 5" << endl;
+	param_stream << "sy_bottom = 5" << endl;
+	param_stream << endl;
+
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for detections #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "minproba  = 400" << endl;
+	param_stream << endl;
+
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for quantification #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "quantification_enlarge_fusion = 3" << endl;
+	param_stream << "npass = 10" << endl;
+	param_stream << endl;
+	param_stream.close();
+}
+
+bool exist(const string filename) {
+	ifstream in;
+	in.open(filename.c_str());
+	if (in.fail()) {
+		in.close();
+		return false;
+	}
+	in.close();
+	return true;
+}
+
+// Main procedure
+//---------------
+int main(int argc, char * * argv) {
+
+	cout << "beads " << BEADS_VERSION << endl;
+
+	const bool save_steps =
+			cimg_option("--steps",false,"save each step of the beads process");
+
+	//argument for parameter file :
+	string parameter_filename =
+			cimg_option("--param","","configuration file for beads");
+	if (parameter_filename == "") {
+		parameter_filename
+				= cimg_option("-p","beads.conf","configuration file for beads");
+	}
+	if (parameter_filename == "beads.conf") {
+		if (!exist(parameter_filename)) {
+			cerr << "ERROR : parameter file not found" << endl;
+			cerr
+					<< "creating an empty parameter file in the current directory named \""
+					<< parameter_filename << "\"" << endl;
+			create_default_parameter_file(parameter_filename);
+		}
+	}
+	qDebug() << " main(int argc, char * * argv) loading parameter_filename " << parameter_filename.c_str();
+	parameters beads_params(parameter_filename);
+
+	//argument to inverse or not source gel image :
+	const bool
+			inverse =
+					cimg_option("--inverse",true,"Inverse intensity (true if spot intensity lower than background)");
+
+	// Read and check command line parameters
+	const std::string sub_function(
+			cimg_option("-sub","nosub","function to apply"));
+
+	if (sub_function == "total") {
+#ifndef MINGW32 //deactivate the sub total for mingw32 version
+		cimg_usage("Spot detection and quantification");
+		const char * file_i1 =
+				cimg_option("-i1","ap-petit.TIFF","Input gel image");
+		const char * file_odir =
+				cimg_option("-odir","direct.TIFF","Output Direct Image");
+		const char * file_oc =
+				cimg_option("-oc","coule.TIFF","Output Bead Image");
+		const char * file_op =
+				cimg_option("-op","prob.TIFF","Output Prob Image");
+		const char * file_odet =
+				cimg_option("-odet","detect.TIFF","Output Detect Image");
+		const char * file_o1 =
+				cimg_option("-o1","numeros.TIFF","Image of spot numbers");
+		const char * file_o2 =
+				cimg_option("-o2","contours.TIFF","Image of contours");
+		const char * file_paths =
+				cimg_option("-opth","paths.TIFF","Bead paths");
+		const char * file_confl =
+				cimg_option("-ocfl","confluents.TIFF","Image of merging paths");
+		const bool visu = cimg_option("-visu",false,"Visualization mode");
+		const string meth =
+				cimg_option("-methode","confluent","surface or merging paths");
+		const bool svg_output = cimg_option("-svg",false,"SVG output");
+
+		//cout << "inverse" << inverse << endl;
+		QString source_filename(file_i1);
+		QFileInfo source_fileinfo(source_filename);
+		source_filename = source_fileinfo.baseName();
+
+		// Init images
+		// gestion image inversée: les programmes direct et quantif sont concus tels que
+		// les hautes intensités sont au centre des spots.
+		// si besoin on inverse avant analyse et apres analyse (ie dans contours)
+		printf("Loading image\n");
+		imageIntensity gel_image_loaded(file_i1);
+
+		imageIntensity * p_gel_image;
+		if (inverse) {
+			printf("Inversing image intensity (negative image)\n");
+			//gel_image.image_inverse();
+			p_gel_image = gel_image_loaded.new_image_inversed();
+			//delete (p_gel_image_loaded);
+		} else {
+			p_gel_image = &gel_image_loaded;
+		}
+
+		printf("Computing directions\n");
+		imageDirect dest;
+		const parameterDirect & parameter_direct =
+				beads_params.get_parameter_direction();
+		dest.compute_from_gel_image(*p_gel_image, parameter_direct);
+		printf("Writing direction image\n");
+		if (file_odir)
+			dest.save(file_odir);
+		// cimg_library::CImgList<unsigned short int> liste(img1,dest);
+		if (visu)
+			dest.display("Direction");
+		printf("Beads are rolling...\n");
+		imageCoule coule;
+		coule.compute_from_direct(dest);
+		//coule(dest, img1);
+		// Save and exit: on fait pas de visu pour le moment
+		printf("Writing image of beads\n");
+		if (file_oc)
+			coule.save(file_oc);
+
+		// definition des maintenant de l'image des proba
+		imageProb dest_image_prob;
+
+		if (meth == "confluent") {
+			printf("Computing bead paths\n");
+			imagePaths parcours;
+			parcours.compute_from_direct_and_coule(dest, coule);
+			printf("Writing paths \n");
+			if (file_paths)
+				parcours.save(file_paths);
+
+			cout << "Looking for merging paths" << endl;
+			const parameterConfluent & parameter_confl =
+					beads_params.get_parameter_confluent();
+
+			cout << " minflux : " << parameter_confl.get_minflux() << endl;
+			cout << " minpath : " << parameter_confl.get_minpath() << endl;
+			cout << " winconfl : " << parameter_confl.get_winconfl() << endl;
+			cout << " min number of beads : " << parameter_confl.get_minbeads()
+					<< endl;
+			cout << " max intensity for confluent detection : "
+					<< parameter_confl.get_intmax() << endl;
+			cout << " min percentage of spot for confluent detection : "
+					<< parameter_confl.get_minpct() << endl;
+
+			imageConfluent confluent;
+			//			confluent.compute_from_direct_and_paths (dest, parcours, flux);
+			confluent.compute_from_direct_and_paths(dest, parcours,
+					*p_gel_image, parameter_confl);
+
+			//confluent.dilate(5);
+
+			printf("Writing merging positions and final positions \n");
+			if (file_confl)
+				confluent.save(file_confl);
+
+			cout << "Probabilities of spot positions" << endl;
+			const parameterProb & parameter_prob =
+					beads_params.get_parameter_prob();
+			cout << " threshold : " << parameter_prob.get_threshold() << endl;
+			cout << " sx : " << parameter_prob.get_sx() << endl;
+			cout << " sy : " << parameter_prob.get_sy() << endl;
+
+			dest_image_prob.compute_from_image_intensity(confluent,
+					parameter_prob);
+
+		} else {
+			cout << "Probabilities of spot positions" << endl;
+			const parameterProb & parameter_prob =
+					beads_params.get_parameter_prob();
+			cout << " threshold : " << parameter_prob.get_threshold() << endl;
+			cout << " sx : " << parameter_prob.get_sx() << endl;
+			cout << " sy : " << parameter_prob.get_sy() << endl;
+
+			//			imageProb dest_image_prob;
+
+			dest_image_prob.compute_from_image_intensity(coule, parameter_prob);
+		}
+		// Save and exit
+		printf("Writing image of probabilities \n");
+		if (file_op)
+			dest_image_prob.save(file_op);
+
+		printf("Spot detection\n");
+		const parameterDetection & parameter_detection =
+				beads_params.get_parameter_detection();
+		cout << " minproba : " << parameter_detection.get_minproba() << endl;
+
+		printf("Creating spot list \n");
+		detection the_detection_from_prob;
+		the_detection_from_prob.detect_from_prob(dest_image_prob,
+				parameter_detection);
+
+		imageDetection img_detect(the_detection_from_prob);
+		//the_detection.write_detection_image(img1);
+
+		//detect (dest, img1, threshold);
+		// Save and exit
+		printf("Writing detection image \n");
+		if (file_odet)
+			img_detect.save(file_odet);
+
+		printf("Quantification\n");
+		const parameterNumber & parameter_number =
+				beads_params.get_parameter_number();
+
+		imageNumber numeros;
+		numeros.compute_from_direct(dest, the_detection_from_prob,
+				parameter_number);
+
+		int npass = parameter_number.get_npass();
+		numeros.expand_areas_down_the_slope(*p_gel_image, npass);
+
+		//   		printf("Computing spot volume and background\n");
+		the_detection_from_prob.quantify_spots_with_image(*p_gel_image, numeros);
+		cout << "Writing image of spot numbers" << endl;
+		if (file_o1)
+			numeros.save(file_o1);
+
+		cout << "Computing spot contours" << endl;
+		imageContours contours;
+		contours.compute_from_gel_image_and_numeros(*p_gel_image, numeros);
+
+		contours.draw_contours(the_detection_from_prob, inverse);
+		// Save
+		if (file_o2) {
+			printf("Writing original image with spot contours\n");
+			if (inverse) {
+				contours.save_inversed(file_o2);
+			} else {
+				contours.save(file_o2);
+			}
+		}
+
+		/*****************************************************
+		 //write results in several file formats:
+		 * ****************************************************/
+		// the PROTICdbML file contains all detected spots with quantitations in XML format
+		// the text tabulated file contains spots coordinates and quantitations
+		// the SVG output is a drawing of detected spots viewable with any SVG compatible software
+		//  (example : http://www.inkscape.org)
+		cout << "Writing PROTICdbML xml spot file" << endl;
+		spotPROTICdbDocument proticdbml_file;
+		proticdbml_file.set_gel_image_file_name(file_i1);
+		proticdbml_file.open(source_filename);
+		proticdbml_file.write_detection(the_detection_from_prob);
+		proticdbml_file.close();
+
+		// ecriture fichier de spots
+		cout << "Writing text tabulated spot file" << endl;
+
+		spotDocument fspot;
+		fspot.open(source_filename);
+		fspot.write_detection(the_detection_from_prob);
+		fspot.close();
+
+		if (svg_output) {
+			cout << "Computing SVG file" << endl;
+			the_detection_from_prob.store_spot_edges(numeros);
+			spotSvgDocument svg_file;
+			svg_file.set_gel_image_file_name(file_i1);
+			//On peut créer et modifier les styles utilisés pour dessiner les contours ou les spots détectés ici:
+			svg_file.set_css_class("detection",
+					"fill:blue;fill-opacity:1;stroke:none");
+			svg_file.set_css_class("contour",
+					"stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:none");
+
+			svg_file.open(source_filename);
+			svg_file.write_detection(the_detection_from_prob, "detection");
+			svg_file.draw_spot_numbers(the_detection_from_prob, "detection");
+			svg_file.write_detection(the_detection_from_prob, "quantif");
+			svg_file.draw_contours(the_detection_from_prob, "contour");
+			svg_file.close();
+		}
+
+		cout << "Done" << endl;
+#endif
+	}
+
+	else if (sub_function == "denovo") {
+		imageDeNovo image;
+		QString output_filename =
+				cimg_option("-o","denovo.tiff","output DeNovo image file");
+		const QString file_denovo_spots =
+				cimg_option("-denovospots","denovo.txt","Parameter file");
+
+		if (image.build_image_with_spot_file(file_denovo_spots)) {
+			cout
+					<< "create an artificial gel image with spots described in file : "
+					<< file_denovo_spots.toStdString() << endl;
+		} else {
+			cerr
+					<< "please use argument -denovospots to provide a de novo spot file."
+					<< endl;
+		}
+		//if (inverse) {
+		image.save_inversed(output_filename);
+		//}
+		//else {
+		//	image.save(output_filename.c_str());
+		//}
+	} else {
+		// no sub function : detect spots and write ouput
+		QString input_filename = cimg_option("-i","","input gel image file");
+		QString output_filename =
+				cimg_option("-o","output.xml","output data file");
+
+		if (input_filename == "") {
+			cerr << "please use argument -i to provide a gel image file."
+					<< endl;
+			return 1;
+		}
+
+		QString source_filename(input_filename);
+		QFileInfo source_fileinfo(source_filename);
+
+		QFileInfo output_fileinfo(output_filename);
+
+		source_filename = source_fileinfo.baseName();
+
+		QString output_extension = output_fileinfo.suffix();
+
+		qDebug() << "main output_extension : " << output_extension;
+
+		cout << "Loading input image file " << input_filename.toStdString()
+				<< endl;
+		imageIntensity gel_image_loaded(input_filename);
+
+		imageIntensity * p_gel_image;
+		if (inverse) {
+			printf("Inversing image intensity (negative image)\n");
+			//gel_image.image_inverse();
+			p_gel_image = gel_image_loaded.new_image_inversed();
+			//delete (p_gel_image_loaded);
+		} else {
+			p_gel_image = &gel_image_loaded;
+		}
+
+		QString tmp_filename;
+		if (save_steps) {
+			tmp_filename = source_filename + "_working_on.tif";
+			cout << "saving working gel image in "
+					<< tmp_filename.toStdString() << endl;
+			p_gel_image->save(tmp_filename);
+		}
+
+		cout << "Computing bead directions " << endl;
+		imageDirect directions;
+		const parameterDirect & parameter_direct =
+				beads_params.get_parameter_direction();
+		directions.compute_from_gel_image(*p_gel_image, parameter_direct);
+
+		if (save_steps) {
+			tmp_filename = source_filename + "_DIRECTIONS.png";
+			cout << "saving directions image in " << tmp_filename.toStdString()
+					<< endl;
+			directions.save(tmp_filename);
+		}
+
+		cout << "Beads are rolling" << endl;
+		imageCoule rolling;
+		rolling.compute_from_direct(directions);
+
+		if (save_steps) {
+			tmp_filename = source_filename + "_BEADS.png";
+			rolling.save(tmp_filename);
+		}
+
+		//using confluent method :
+		cout << "Computing bead paths" << endl;
+		imagePaths path;
+		path.compute_from_direct_and_coule(directions, rolling);
+
+		if (save_steps) {
+			tmp_filename = source_filename + "_PATHS.png";
+			cout << "saving path image in " << tmp_filename.toStdString()
+					<< endl;
+			path.save(tmp_filename);
+		}
+
+		cout << "Detecting merging paths and arrival positions" << endl;
+		const parameterConfluent & parameter_confl =
+				beads_params.get_parameter_confluent();
+		cout << " minflux : " << parameter_confl.get_minflux() << endl;
+		cout << " minpath : " << parameter_confl.get_minpath() << endl;
+		cout << " winconfl : " << parameter_confl.get_winconfl() << endl;
+		cout << " minbeads : " << parameter_confl.get_minbeads() << endl;
+
+		imageConfluent confluent;
+		confluent.compute_from_direct_and_paths(directions, path, *p_gel_image,
+				parameter_confl);
+
+		if (save_steps) {
+			tmp_filename = source_filename + "_SELECT.png";
+			cout << "saving confluent image in " << tmp_filename.toStdString()
+					<< endl;
+			confluent.save(tmp_filename);
+		}
+
+		cout << "Computing probabilites of spot positions" << endl;
+		const parameterProb & parameter_prob =
+				beads_params.get_parameter_prob();
+		cout << " threshold : " << parameter_prob.get_threshold() << endl;
+		cout << " sx : " << parameter_prob.get_sx() << endl;
+		cout << " sy : " << parameter_prob.get_sy() << endl;
+
+		imageProb dest_image_prob;
+
+		dest_image_prob.compute_from_image_intensity(confluent, parameter_prob);
+
+		if (save_steps) {
+			tmp_filename = source_filename + "_PROBABILITIES.png";
+			cout << "saving prob image in " << tmp_filename.toStdString()
+					<< endl;
+			dest_image_prob.save(tmp_filename);
+		}
+
+		cout << "Detecting spots" << endl;
+		const parameterDetection & parameter_detection =
+				beads_params.get_parameter_detection();
+		cout << " minproba : " << parameter_detection.get_minproba() << endl;
+
+		detection the_detection_from_prob;
+		the_detection_from_prob.detect_from_prob(dest_image_prob,
+				parameter_detection);
+
+		//imageDetection img_detect(the_detection_from_prob);
+
+		cout << "Quantification" << endl;
+		const parameterNumber & parameter_number =
+				beads_params.get_parameter_number();
+
+		imageNumber numbers;
+		//quantification des spots:
+		numbers.compute_from_direct(directions, the_detection_from_prob,
+				parameter_number);
+
+		int npass = parameter_number.get_npass();
+		numbers.expand_areas_down_the_slope(*p_gel_image, npass);
+
+		if (save_steps) {
+			tmp_filename = source_filename + "_NUMBERS.png";
+			cout << "saving numbers image in " << tmp_filename.toStdString()
+					<< endl;
+			numbers.save(tmp_filename);
+		}
+
+		/*****************************************************
+		 //write results
+		 * ****************************************************/
+		if (output_extension == "xml") {
+			the_detection_from_prob.quantify_spots_with_image(*p_gel_image,
+					numbers);
+			// the PROTICdbML file contains all detected spots with quantitations in XML format
+			// the text tabulated file contains spots coordinates and quantitations
+			// the SVG output is a drawing of detected spots viewable with any SVG compatible software
+			//  (example : http://www.inkscape.org)
+			cout << "Writing PROTICdbML xml spot file "
+					<< output_filename.toStdString() << endl;
+			spotPROTICdbDocument proticdbml_file;
+			proticdbml_file.set_gel_image_file_name(input_filename);
+			proticdbml_file.open(output_filename);
+			proticdbml_file.write_detection(the_detection_from_prob);
+			proticdbml_file.close();
+		}
+		if (output_extension == "txt") {
+			the_detection_from_prob.quantify_spots_with_image(*p_gel_image,
+					numbers);
+			cout << "Writing text tabulated spot file "
+					<< output_filename.toStdString() << endl;
+
+			spotDocument fspot;
+			fspot.open(output_filename);
+			fspot.write_detection(the_detection_from_prob);
+			fspot.close();
+		}
+		if (output_extension == "jpg") {
+			imageContours contours;
+			contours.compute_from_gel_image_and_numeros(*p_gel_image, numbers);
+			contours.draw_contours(the_detection_from_prob, inverse);
+			cout << "Writing jpeg file of spot contours"
+					<< output_filename.toStdString() << endl;
+			cout << "Writing jpeg file " << output_filename.toStdString()
+					<< endl;
+			contours.save_inversed(output_filename);
+		}
+		if (output_extension == "jpg2") {
+			//draw spot barycenters instead of detected spot centers
+			imageContours contours;
+			contours.compute_from_gel_image_and_numeros(*p_gel_image, numbers);
+			the_detection_from_prob.quantify_spots_with_image(*p_gel_image,
+					numbers);
+			contours.draw_contours(the_detection_from_prob, inverse);
+			cout << "Writing jpeg file of spot contours"
+					<< output_filename.toStdString() << endl;
+			cout << "Writing jpeg file " << output_filename.toStdString()
+					<< endl;
+			contours.save_inversed(output_filename);
+		}
+		if ((output_extension == "tiff") || (output_extension == "tif")) {
+			imageContours contours;
+			contours.compute_from_gel_image_and_numeros(*p_gel_image, numbers);
+			contours.draw_contours(the_detection_from_prob, inverse);
+			cout << "Write tiff file of spot contours"
+					<< output_filename.toStdString() << endl;
+			cout << "Writing tiff file " << output_filename.toStdString()
+					<< endl;
+			contours.save_inversed(output_filename);
+		}
+		if (output_extension == "svg") {
+			cout << "Writing SVG file of spot contours"
+					<< output_filename.toStdString() << endl;
+			the_detection_from_prob.store_spot_edges(numbers);
+			spotSvgDocument svg_file;
+			svg_file.set_gel_image_file_name(input_filename);
+			svg_file.set_css_class("detection",
+					"fill:blue;fill-opacity:1;stroke:none");
+			svg_file.set_css_class("contour",
+					"stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:none");
+
+			svg_file.embed_gel_image(gel_image_loaded);
+			//svg_file.embed_gel_image();
+
+			svg_file.open(output_filename);
+			svg_file.write_detection(the_detection_from_prob, "detection");
+			svg_file.draw_spot_numbers(the_detection_from_prob, "detection");
+
+			the_detection_from_prob.quantify_spots_with_image(*p_gel_image,
+					numbers);
+			svg_file.write_detection(the_detection_from_prob, "quantif");
+			svg_file.draw_contours(the_detection_from_prob, "contour");
+
+			svg_file.close();
+		}
+	}
+	return 0;
+}
+
diff --git a/src/beads.h b/src/beads.h
new file mode 100644
index 0000000..8ff3680
--- /dev/null
+++ b/src/beads.h
@@ -0,0 +1,43 @@
+/************************************************************************
+ *   Description : "beads" is a software to detect and quantify spots
+ * 				on a 2-DE electrophoresis gel image
+ *
+ *   Copyright   : Michel Zivy
+ *                 ( http://moulon.inra.fr/ )
+ *   License     : CeCILL
+ *   This software is governed by the CeCILL 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
+ *   license as circulated by CEA, CNRS and INRIA at the following URL
+ *   "http://www.cecill.info".
+ *   Please refer to "COPYING" file for the complete licence terms
+ **************************************************************************/
+
+#ifndef BEADS_H_
+#define BEADS_H_
+
+#include <QDebug>
+#include <QApplication>
+#include "config.h"
+
+#include <iostream>
+#include <string>
+
+/*
+ *
+ #include "spotDocument.h"
+ #include "spotSvgDocument.h"
+ #include "images/imageDetection.h"
+ #include "images/imageNumber.h"
+ #include "images/imageContours.h"
+ #include "detection.h"
+ */
+
+//void image_inverse (cimg_library::CImg<unsigned short int> & img1);
+
+//int amas (int &xx, int &yy, imageCode & mark, const imageIntensity & img1);
+
+//int voisin (int x, int y, imageNumber & image_numeros);
+
+#endif /*BEADS_H_*/
+
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..b18aac0
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,21 @@
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+#define BEADS_VERSION "1.1.13d"
+
+#define cimg_OS 1
+
+#if cimg_OS==2
+//Windows
+//#define cimg_use_magick
+#define cimg_display_type  0
+
+//#ifdef MINGW32
+// in Cimg : Unknown configuration : ask for minimal dependencies (no display).
+//#define cimg_OS            2
+//#define cimg_display_type  0
+//#endif
+
+#endif
+
+#endif /* _CONFIG_H */
diff --git a/src/config.h.cmake b/src/config.h.cmake
new file mode 100644
index 0000000..ebab906
--- /dev/null
+++ b/src/config.h.cmake
@@ -0,0 +1,21 @@
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+#define BEADS_VERSION "@BEADS_VERSION@"
+
+#define cimg_OS @CIMG_OS@
+
+#if cimg_OS==2
+//Windows
+//#define cimg_use_magick
+#define cimg_display_type  0
+
+//#ifdef MINGW32
+// in Cimg : Unknown configuration : ask for minimal dependencies (no display).
+//#define cimg_OS            2
+//#define cimg_display_type  0
+//#endif
+
+#endif
+
+#endif /* _CONFIG_H */
diff --git a/src/detection.cpp b/src/detection.cpp
new file mode 100644
index 0000000..2e01d35
--- /dev/null
+++ b/src/detection.cpp
@@ -0,0 +1,304 @@
+#include "detection.h"
+#include "images/imageNumber.h"
+#include "qtbeads/qtbeads_error.h"
+
+#include <iostream>
+using namespace std;
+
+detection::detection() :
+	spot_map() {
+	_image_file_name = "";
+	_gel_image_width = 0;
+	_gel_image_height = 0;
+}
+;
+
+detection::detection(const detection & src) :
+	spot_map(src) {
+	_image_file_name = src._image_file_name;
+	_gel_image_width = src._gel_image_width;
+	_gel_image_height = src._gel_image_height;
+}
+
+detection::~detection() {
+}
+
+spot & detection::get_spot(unsigned int spot_number) {
+
+	spot_map::iterator it = this->find(spot_number);
+
+	if (it == this->end()) {
+		throw qtbeadsError(
+				QObject::tr(
+						"trying to get the spot number '%1' which does not exists in this detection").arg(spot_number));
+	}
+	else {
+		return it->second;
+	}
+
+}
+
+/** \brief creates a collection of spot from an imageDetection object
+ * 
+ * on imageDetection, each pixel with intensity > 0 means we have a "spot"
+ * imageDetection is a way to store detected spots and this function gives a way to read this detection
+ */
+
+detection::detection(const imageDetection & image_detect) {
+	spot_map & _spots = *this;
+	unsigned int nspot;
+
+	nspot = 0;
+
+	cimg_for_insideXY(image_detect,x,y,1)
+		{
+			if (image_detect(x, y) > 0) {
+				//on a un spot:
+				_spots[++nspot].set_x(x);
+				_spots[nspot].set_number(nspot);
+				_spots[nspot].set_y(y);
+			}
+		}
+}
+;
+
+/** \brief detect spots from imageProb
+ * 
+ * imageProb gives potential spot positions
+ * given a threshold, this try to find spot positions
+ */
+void detection::detect_from_prob(const imageProb & image_prob,
+		const parameterDetection & parameter_detection) {
+
+	//cerr << "detection::detect_from_prob begin" << endl;
+	//image_prob.save("/tmp/prob.tiff");
+
+	float minproba = parameter_detection.get_minproba();
+	spot_map & _spots = *this;
+
+	_gel_image_width = image_prob.width();
+	_gel_image_height = image_prob.height();
+
+	unsigned int nspot;
+
+	nspot = 0;
+
+	imageCode mark;
+	//  cimg_library::CImg<unsigned short int> mark=img1;
+	//  cimg_library::CImg<unsigned short int> dest=img1;
+	mark.resize(image_prob.width(), image_prob.height(), 1, 1);
+	mark.fill(0);
+	// processing
+	//
+	// on recherche un voisin plus haut dans une fenêtre 3 par 3, et on y va
+	cimg_for_insideXY(image_prob,x,y,1)
+		{
+			int xx, yy;
+			int atteint = 0;
+			xx = x;
+			yy = y;
+			if (mark(xx, yy) == 0) {
+				if (image_prob(xx, yy) == imageProb::_max_value) {
+					//this pixel is burned :
+					//cerr << "detection::detect_from_prob burned" << endl;
+					// we have to find neighbors and group it into a spot
+					nspot++;
+
+					image_prob.mark_burned_pixels(mark, xx, yy);
+
+					_spots[nspot].set_x(xx);
+					_spots[nspot].set_number(nspot);
+					_spots[nspot].set_y(yy);
+					//cerr << "detection::detect_from_prob burned end" << endl;
+
+
+				} else {
+					if (image_prob(xx, yy) > minproba) {
+						//find the maximum of intensity and make a spot with it
+
+						//       	 printf("1 %d %d %d\n",xx,yy,atteint);
+						atteint = image_prob.amas(xx, yy, mark);
+						//       	 printf("2 %d %d %d\n",xx,yy,atteint);
+						while (atteint == 0) {
+							atteint = image_prob.amas(xx, yy, mark);
+							//       	       printf("3 %d %d %d\n",xx,yy,atteint);
+						}
+						if (atteint == 1) {
+							//on a un spot:
+							_spots[++nspot].set_x(xx);
+							_spots[nspot].set_number(nspot);
+							_spots[nspot].set_y(yy);
+
+							//				std::cout << "spot " << _spots[nspot].get_number() << " x "  << _spots[nspot].get_x() << " y "<< _spots[nspot].get_y() << std::endl;
+						}
+						//	 printf("4 %d %d %d\n",xx,yy,atteint);
+					}
+				}
+			}
+		}
+	printf("%d spots detected\n", nspot);
+}
+;
+
+/** \brief compute quantitations using original image and "contours"
+ * 
+ * on each spot, this will give :
+ * - the minimum intensity (on which the background is estimated)
+ * - the spot surface in pixels
+ * - the volume
+ * - the spot barycenter (tx and ty on each spot)
+ * - the background
+ * 
+ * if a spot has a surface of 0, this is deleted
+ */
+
+void detection::quantify_spots_with_image(
+		const cimg_library::CImg<int> & image_depart,
+		const imageNumber & image_numeros) {
+	detection & spot_array = *this;
+	cout << "Computing volume, surface and background" << endl;
+
+	qDebug() << "detection::quantify_spots_with_image coucou spot_array size "
+			<< spot_array.size();
+
+	// dans un premier temps on ne recherche que les min et max dans chaque spot (tmin et tmax)
+	cimg_forXY(image_numeros,x,y)
+		{
+			//qDebug() << "detection::quantify_spots_with_image x " << x << " y "
+			//		<< y;
+
+			unsigned int spot_number = image_numeros(x, y);
+
+			//qDebug() << "detection::quantify_spots_with_image image_numeros("
+			//		<< x << "," << y << ") = " << image_numeros(x, y);
+
+			if (spot_number > 0) {
+				spot & current_detected_spot = this->get_spot(spot_number);
+				//cout << image_numeros(x,y) << endl;
+				// vol[image_numeros(x,y)] += vol[image_numeros(x,y)]
+				//			spot_array[image_numeros(x,y)].set_vol(spot_array[image_numeros(x,y)].get_vol() + image_depart(x,y));
+				//			spot_arrayimage_numeros(x,y)].set_area(spot_array[image_numeros(x,y)].get_area() + 1);
+
+				if (image_depart(x, y) < (long int) current_detected_spot.get_tmin())
+					current_detected_spot.set_tmin(image_depart(x, y));
+				if (image_depart(x, y) > (long int) current_detected_spot.get_tmax())
+					current_detected_spot.set_tmax(image_depart(x, y));
+			}
+		}
+
+	qDebug() << "detection::quantify_spots_with_image coucou 1";
+	// correction du minimum au cas ou on tombe sur une valeur extreme
+	detection::iterator it;
+	for (it = spot_array.begin(); it != spot_array.end(); ++it) {
+		spot & the_spot = (*it).second;
+		//		cout << " tmin avant correction: " <<  the_spot.get_tmin() << endl;
+		//		the_spot.set_tmin((long unsigned int) (the_spot.get_tmin()+(((float) the_spot.get_tmax()-(float)the_spot.get_tmin())/(float)2)));
+		the_spot.set_tmin((long unsigned int) ((float) the_spot.get_tmin()
+				* 1.1));
+		//		cout << " tmin après correction: " <<  the_spot.get_tmin() << endl;
+	}
+
+	qDebug() << "detection::quantify_spots_with_image coucou 2";
+	// on peut mantenant calculer les volumes, surfaces, et barycentres, en ne prenant que
+	// les valeurs supérieures au tmin.
+	cimg_forXY(image_numeros,x,y)
+		{
+			if ((image_numeros(x, y) > 0) && (image_depart(x, y)
+					> (long int) spot_array[image_numeros(x, y)].get_tmin())) {
+				// vol[image_numeros(x,y)] += vol[image_numeros(x,y)]
+				spot_array[image_numeros(x, y)].set_vol(
+						spot_array[image_numeros(x, y)].get_vol()
+								+ image_depart(x, y));
+				spot_array[image_numeros(x, y)].set_area(
+						spot_array[image_numeros(x, y)].get_area() + 1);
+				spot_array[image_numeros(x, y)].set_tx(
+						spot_array[image_numeros(x, y)].get_tx()
+								+ ((image_depart(x, y)
+										- spot_array[image_numeros(x, y)].get_tmin())
+										* x));
+				spot_array[image_numeros(x, y)].set_ty(
+						spot_array[image_numeros(x, y)].get_ty()
+								+ ((image_depart(x, y)
+										- spot_array[image_numeros(x, y)].get_tmin())
+										* y));
+			}
+		}
+	qDebug() << "detection::quantify_spots_with_image coucou 3";
+
+	//compute background and delete spots that would have a null surface or a background greater than volume :
+	detection::iterator to_delete;
+	for (it = spot_array.begin(); it != spot_array.end();) {
+		spot & the_spot = (*it).second;
+		the_spot.compute_background();
+		if ((the_spot.get_area() == 0) || ((long int) the_spot.get_bckgnd()
+				> the_spot.get_vol())) {
+			//on efface les spots non quantifiés :
+			to_delete = it;
+			++it;
+			//cout << " spot deleted " << endl;
+			spot_array.erase(to_delete);
+		} else {
+			++it;
+		}
+	}
+	qDebug() << "detection::quantify_spots_with_image coucou 4";
+
+	//means (moyenne des x et y pondérée par les intensités) 
+	cout << "Computing barycenter coordinates" << endl;
+
+	for (it = spot_array.begin(); it != spot_array.end(); ++it) {
+		spot & the_spot = (*it).second;
+		the_spot.set_tx(the_spot.get_tx() / ((float) the_spot.get_vol()
+				- (float) the_spot.get_bckgnd()));
+		the_spot.set_ty(the_spot.get_ty() / ((float) the_spot.get_vol()
+				- (float) the_spot.get_bckgnd()));
+	}
+}
+
+/** \brief add edges coordinates on each spot of the collection
+ * 
+ * the imageNumeros represent the surface of each spot. With this image, we can store
+ * the shape of this spot as "spot edges"
+ * It is usefull to represent spots in SVG drawings
+ */
+
+void detection::store_spot_edges(const imageNumber & image_numeros) {
+	qDebug() << "begin detection::store_spot_edges begin";
+	//for each spot, scan imageNumber to find coordinates of edges
+	detection & spot_array = *this;
+	// début contour
+	//for each spot, scan imageNumber to find coordinates of edges
+	cimg_for_insideXY(image_numeros,x,y,1)
+		{
+			int spot_num = image_numeros(x, y);
+			if (spot_num > 0) {
+				spot & the_spot = spot_array[spot_num];
+				if (spot_num != image_numeros(x + 1, y))
+					the_spot.add_edge_point(x, y);
+				else if (spot_num != image_numeros(x, y + 1))
+					the_spot.add_edge_point(x, y);
+				else if (spot_num != image_numeros(x - 1, y))
+					the_spot.add_edge_point(x, y);
+				else if (spot_num != image_numeros(x, y - 1))
+					the_spot.add_edge_point(x, y);
+				else if ((x + 1) == (image_numeros.width() - 1)) {
+					the_spot.add_edge_point(x, y);
+				} else if ((y + 1) == (image_numeros.height() - 1)) {
+					the_spot.add_edge_point(x, y);
+				} else if ((x - 1) == 0) {
+					the_spot.add_edge_point(x, y);
+				} else if ((y - 1) == 0) {
+					the_spot.add_edge_point(x, y);
+				}
+			}
+		}
+	detection::iterator it;
+	for (it = begin(); it != end(); ++it) {
+		//int spot_num = (*it).first;
+		spot & the_spot = (*it).second;
+		the_spot.sort_edge_points();
+		the_spot.eliminate_aligned_edge_points();
+	}
+	qDebug() << "begin detection::store_spot_edges end";
+}
+
diff --git a/src/detection.h b/src/detection.h
new file mode 100644
index 0000000..71bb5b8
--- /dev/null
+++ b/src/detection.h
@@ -0,0 +1,67 @@
+#ifndef DETECTION_H_
+#define DETECTION_H_
+
+/*
+ * Olivier:
+ * l'idée de l'objet détection est de représenter une collection de spots détectés sur une image
+ *
+ * les fonctions membres permettront de détecter les spots (créer et numéroter les spots trouvés sur une image)
+ *
+ * */
+#include <map>
+#include <string>
+#include "spot.h"
+#include "images/imageProb.h"
+#include "images/imageDetection.h"
+#include "parameterDetection.h"
+
+class imageNumber;
+
+/** \brief detection is a structure to handle a collection of spot objects
+ * 
+ * detection is a C++ STL map derived object
+ * it is a collection of spots for an image, indexed by their spot number
+ * 
+ * the spot object is described in file spot.h
+ * 
+ */
+
+typedef std::map<unsigned int, spot> spot_map;
+
+class detection: public spot_map {
+public:
+	detection();
+	detection(const detection & src);
+	detection(const imageDetection &);
+	~detection();
+
+	void detect_from_prob(const imageProb & image_prob,
+			const parameterDetection & parameter_detection);
+	//void write_detection_image(cimg_library::CImg<unsigned short int> & detection_image);
+	void quantify_spots_with_image(
+			const cimg_library::CImg<int> & image_depart,
+			const imageNumber & image_numeros);
+
+	void store_spot_edges(const imageNumber & image_numeros);
+
+	const int get_gel_image_width() const {
+		return (_gel_image_width);
+	}
+
+	const int get_gel_image_height() const {
+		return (_gel_image_height);
+	}
+
+	spot & get_spot(unsigned int spot_number);
+
+private:
+
+	//the original image file name on which spots were detected
+	std::string _image_file_name;
+
+	//the size of the original gel image
+	int _gel_image_width;
+	int _gel_image_height;
+};
+
+#endif /*DETECTION_H_*/
diff --git a/src/encode/base64.cpp b/src/encode/base64.cpp
new file mode 100644
index 0000000..0dc8cdf
--- /dev/null
+++ b/src/encode/base64.cpp
@@ -0,0 +1,105 @@
+#include "base64.h"
+#include <iostream>
+
+static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	"abcdefghijklmnopqrstuvwxyz"
+	"0123456789+/";
+
+static inline bool is_base64(unsigned char c) {
+	return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+std::string base64_encode(unsigned char const* bytes_to_encode,
+		unsigned int in_len) {
+	std::string ret;
+	int i = 0;
+	int j = 0;
+	unsigned char char_array_3[3];
+	unsigned char char_array_4[4];
+
+	while (in_len--) {
+		char_array_3[i++] = *(bytes_to_encode++);
+		if (i == 3) {
+			char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+			char_array_4[1] = ((char_array_3[0] & 0x03) << 4)
+					+ ((char_array_3[1] & 0xf0) >> 4);
+			char_array_4[2] = ((char_array_3[1] & 0x0f) << 2)
+					+ ((char_array_3[2] & 0xc0) >> 6);
+			char_array_4[3] = char_array_3[2] & 0x3f;
+
+			for (i = 0; (i < 4); i++)
+				ret += base64_chars[char_array_4[i]];
+			i = 0;
+		}
+	}
+
+	if (i) {
+		for (j = i; j < 3; j++)
+			char_array_3[j] = '\0';
+
+		char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+		char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1]
+				& 0xf0) >> 4);
+		char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2]
+				& 0xc0) >> 6);
+		char_array_4[3] = char_array_3[2] & 0x3f;
+
+		for (j = 0; (j < i + 1); j++)
+			ret += base64_chars[char_array_4[j]];
+
+		while ((i++ < 3))
+			ret += '=';
+
+	}
+
+	return ret;
+
+}
+
+std::string base64_decode(std::string const& encoded_string) {
+	int in_len = encoded_string.size();
+	int i = 0;
+	int j = 0;
+	int in_ = 0;
+	unsigned char char_array_4[4], char_array_3[3];
+	std::string ret;
+
+	while (in_len-- && (encoded_string[in_] != '=') && is_base64(
+			encoded_string[in_])) {
+		char_array_4[i++] = encoded_string[in_];
+		in_++;
+		if (i == 4) {
+			for (i = 0; i < 4; i++)
+				char_array_4[i] = base64_chars.find(char_array_4[i]);
+
+			char_array_3[0] = (char_array_4[0] << 2)
+					+ ((char_array_4[1] & 0x30) >> 4);
+			char_array_3[1] = ((char_array_4[1] & 0xf) << 4)
+					+ ((char_array_4[2] & 0x3c) >> 2);
+			char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+			for (i = 0; (i < 3); i++)
+				ret += char_array_3[i];
+			i = 0;
+		}
+	}
+
+	if (i) {
+		for (j = i; j < 4; j++)
+			char_array_4[j] = 0;
+
+		for (j = 0; j < 4; j++)
+			char_array_4[j] = base64_chars.find(char_array_4[j]);
+
+		char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30)
+				>> 4);
+		char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2]
+				& 0x3c) >> 2);
+		char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+		for (j = 0; (j < i - 1); j++)
+			ret += char_array_3[j];
+	}
+
+	return ret;
+}
diff --git a/src/encode/base64.h b/src/encode/base64.h
new file mode 100644
index 0000000..d96a327
--- /dev/null
+++ b/src/encode/base64.h
@@ -0,0 +1,23 @@
+#include <string>
+
+std::string base64_encode(unsigned char const*, unsigned int len);
+std::string base64_decode(std::string const& s);
+
+/*
+ The test file
+
+ #include "base64.h"
+ #include <iostream>
+
+ int main() {
+ const std::string s = "ADP GmbH\nAnalyse Design & Programmierung\nGesellschaft mit beschränkter Haftung" ;
+
+ std::string encoded = base64_encode(reinterpret_cast<const unsigned char*>(s.c_str()), s.length());
+ std::string decoded = base64_decode(encoded);
+
+ std::cout << "encoded: " << encoded << std::endl;
+ std::cout << "decoded: " << decoded << std::endl;
+
+ return 0;
+ }
+ */
diff --git a/src/images/imageCode.h b/src/images/imageCode.h
new file mode 100644
index 0000000..84b690a
--- /dev/null
+++ b/src/images/imageCode.h
@@ -0,0 +1,34 @@
+#ifndef IMAGECODE_H_
+#define IMAGECODE_H_
+
+#include "../config.h"
+
+#include "../CImg.h"
+//#include <CImg.h>
+
+//Images pour lesquelles les valeurs des pixels sont des codes et non des valeurs quantitatives
+//Sont des codes: l'image des directions, l'image des numeros
+
+/** \brief class of image that contains codes and not quantitative values
+ * for example : the direction image or the number image
+ */
+class imageCode: public cimg_library::CImg<short int> {
+public:
+	imageCode(const char * & filename) :
+		cimg_library::CImg<short int>(filename) {
+	}
+
+	imageCode() {
+	}
+
+	~imageCode() {
+	}
+
+private:
+
+public:
+	static const unsigned short int _max_value = 32767;
+
+};
+
+#endif /*IMAGECODE_H_*/
diff --git a/src/images/imageConfluent.cpp b/src/images/imageConfluent.cpp
new file mode 100644
index 0000000..c37662a
--- /dev/null
+++ b/src/images/imageConfluent.cpp
@@ -0,0 +1,126 @@
+#include <iostream>
+
+// ---------------------------------------------------------------------------
+// confluent
+// ---------------------------------------------------------------------------
+#include "imageConfluent.h"
+
+using namespace std;
+
+void imageConfluent::compute_from_direct_and_paths(const imageDirect & direct,
+		const imagePaths & paths, const imageIntensity & gel_image,
+		const parameterConfluent & parameter_confl) {
+
+	imageConfluent & confluent = *this;
+
+	int minflux = parameter_confl.get_minflux();
+	int minpath = parameter_confl.get_minpath();
+	int winconfl = parameter_confl.get_winconfl();
+	int minbeads = parameter_confl.get_minbeads();
+	int intmax = parameter_confl.get_intmax();
+	int minpct = parameter_confl.get_minpct();
+
+	confluent.resize(direct.width(), direct.height(), 1, 1);
+	confluent.fill(0);
+	// processing
+	int confl, is_max;
+	//	cimg_forXY(direct,x,y) {
+	cimg_for_insideXY(direct,x,y,1)
+		{
+			is_max = 0;
+
+			/* on est plus indulgent dans le cas où plus de 2 chemins convergent.
+			 * A la limite il n'y a plus besoin d'indiquer le nb de confluents rechercé */
+			confl = direct.get_nb_confluent_for_pixel(x, y, paths, (int) (minflux
+					* 0.8));
+			if (confl == 2)
+				confl = direct.get_nb_confluent_for_pixel(x, y, paths, minflux);
+
+			/*
+			 if (direct(x+1,y-1)==7 && paths(x+1,y-1)>flux) confl++;
+			 if (direct(x+1,y)==0 && paths(x+1,y)>flux) confl++;
+			 if (direct(x+1,y+1)==2 && paths(x+1,y+1)>flux) confl++;
+			 if (direct(x,y+1)==1 && paths(x,y+1)>flux) confl++;
+			 if (direct(x-1,y+1)==3 && paths(x-1,y+1)>flux) confl++;
+			 if (direct(x-1,y)==4 && paths(x-1,y)>flux) confl++;
+			 if (direct(x-1,y-1)==6 && paths(x-1,y-1)>flux) confl++;
+			 if (direct(x,y-1)==5 && paths(x,y-1)>flux) confl++;
+			 */
+
+			// si on est sur un confluent, on va chercher le maximum local vers où il va en suivant la
+			// direction indiquée par direct. On peut alors éliminer le confluent dans le cas où le
+			// ne concernait qu'une petite surface par rapport au maximum local.
+
+			if (confl >= minpath) {
+				int xx = x;
+				int yy = y;
+				int i = 0;
+				//int curr;
+				unsigned short int oldpix = paths(x, y) - 1;
+				unsigned short int newpix = paths(x, y);
+				unsigned short int pixconfl = paths(x, y);
+				int distance = 0;
+				// on s'arrete des qu'on penetre dans une zone occupée par un carré de spot
+				// attention ca risque de faire aller jusqu'à 100 pour bcp de spots du bruit de
+				// fond
+				while (i < 100 && newpix > oldpix) {
+					i++;
+					if (direct.move_to_spot_center(xx, yy)) {
+						//move ok
+					}
+					/*
+					 curr=(int)direct(xx,yy);
+					 switch (curr) {
+					 case 0: xx--; break;
+					 case 1: yy--; break;
+					 case 2: {xx--; yy--;} break;
+					 case 3: {xx++; yy--;} break;
+					 case 4: xx++; break;
+					 case 5: yy++; break;
+					 case 6: {xx++; yy++;} break;
+					 case 7: {xx--; yy++;} break;
+					 }
+					 */
+					distance++;
+					oldpix = newpix;
+					newpix = paths(xx, yy);
+				}
+				// on ne fait rien en cas de plat (= case -1): xx et yy sont inchangés
+				// est-ce judicieusement fait ?
+
+				if (float(pixconfl) * 100 / newpix < minpct)
+					confl = 0;
+
+			}
+
+			/* dans le même temps je prends le maximum local */
+			if (paths(x, y) > minbeads) {
+				if ((paths(x + 1, y - 1) < paths(x, y)) && (paths(x + 1, y)
+						< paths(x, y)) && (paths(x + 1, y + 1) < paths(x, y))
+						&& (paths(x, y + 1) < paths(x, y)) && (paths(x - 1, y
+						+ 1) < paths(x, y)) && (paths(x - 1, y) < paths(x, y))
+						&& (paths(x - 1, y - 1) < paths(x, y)) && (paths(x, y
+						- 1) < paths(x, y)))
+					is_max = 1;
+			}
+
+			// on enleve les confluents sur les spots intenses:la plupart du temps c'est artefactuel.
+			if (confl >= minpath && gel_image(x, y) < intmax)
+				confluent(x, y) = winconfl;
+			//		if (is_max==1) confluent(x,y)=winconfl+paths(x,y);
+			if (is_max == 1)
+				confluent(x, y) = paths(x, y);
+		}
+
+	// transmission valeurs saturées
+	cimg_for_insideXY(direct,x,y,1)
+		{
+			if (direct.is_burned_pixel(x, y))
+				confluent(x, y) = _max_value;
+		}
+
+	//save("/tmp/confluent.tiff");
+	//cerr << "imageConfluent::compute_from_direct_and_paths end" << endl;
+
+}
+
diff --git a/src/images/imageConfluent.h b/src/images/imageConfluent.h
new file mode 100644
index 0000000..e83f38f
--- /dev/null
+++ b/src/images/imageConfluent.h
@@ -0,0 +1,26 @@
+#ifndef IMAGECONFLUENT_H_
+#define IMAGECONFLUENT_H_
+
+
+#include "imageIntensity.h"
+#include "imageDirect.h"
+#include "imagePaths.h"
+#include "parameterConfluent.h"
+
+
+//Identification des confluents
+class imageConfluent : public imageIntensity {
+public:
+	imageConfluent(const char * & filename) : imageIntensity(filename){
+	};
+	imageConfluent() {
+	};
+	~imageConfluent() {
+	};
+
+	void compute_from_direct_and_paths (const imageDirect & direct, const imagePaths & paths, const imageIntensity & gel_image, const parameterConfluent & parameter_confl);
+
+private:
+};
+
+#endif /*IMAGECONFLUENTS_H_*/
diff --git a/src/images/imageContours.cpp b/src/images/imageContours.cpp
new file mode 100644
index 0000000..12c6276
--- /dev/null
+++ b/src/images/imageContours.cpp
@@ -0,0 +1,113 @@
+#include <iostream>
+#include "imageContours.h"
+#include "imageNumber.h"
+
+using namespace std;
+
+void imageContours::compute_from_gel_image_and_numeros(
+		const imageIntensity & image_depart, const imageNumber & image_numeros) {
+
+	bool inverse;
+
+	inverse = image_depart.get_is_inversed();
+
+	imageContours & image_contours = *this;
+	//img1 => image_detect
+	//img2 => image_depart
+	image_contours.resize(image_depart.width(), image_depart.height(), 1, 1); //mark
+	//  cimg_library::CImg<unsigned short int> dest=img1;
+	//  cimg_library::CImg<unsigned short int> mark=img2;
+	//	cout << "Quantif- initialisation à zero de image_contours" << endl;
+
+	// pour contour on remplit avec 1 car sinon confusion qnad image inversée
+	image_contours.fill(1);
+	// processing
+
+	/*
+	 * déjà fait par imageNumber::compute_from_gel_image
+	 // un petit coup de median d'abord
+	 printf("Quantif- lissage de l'image\n");
+	 image_depart.blur_median(n=3);
+	 */
+
+	// on fait bouger les frontieres:
+	// pour tout pixel frontiere entre 2 spots, on calcule la direction de plus grande
+	// pente. Le pixel changera de numero si besoin.
+	// non car comme ca on va éliminer tous les spots non maximum locaux (épaulements)
+
+
+	// début contour
+	//unsigned short int spot_color = 65535-inverse*65535; 
+	unsigned short int spot_color = this->_max_value; //white
+
+	//	printf("Quantif- contours\n");
+	cimg_for_insideXY(image_numeros,x,y,1)
+		{
+			if (image_numeros(x, y) == 0) {
+			}
+
+			else if (image_numeros(x, y) > image_numeros(x + 1, y))
+				image_contours(x, y) = spot_color;
+			else if (image_numeros(x, y) > image_numeros(x, y + 1))
+				image_contours(x, y) = spot_color;
+			else if (image_numeros(x, y) > image_numeros(x - 1, y))
+				image_contours(x, y) = spot_color;
+			else if (image_numeros(x, y) > image_numeros(x, y - 1))
+				image_contours(x, y) = spot_color;
+			else if ((x + 1) == (image_contours.width() - 1)) {
+				if (image_numeros(x, y) > 0) {
+					image_contours(x, y) = spot_color;
+				}
+			} else if ((y + 1) == (image_contours.height() - 1)) {
+				if (image_numeros(x, y) > 0) {
+					image_contours(x, y) = spot_color;
+				}
+			} else if ((x - 1) == 0) {
+				if (image_numeros(x, y) > 0) {
+					image_contours(x, y) = spot_color;
+				}
+			} else if ((y - 1) == 0) {
+				if (image_numeros(x, y) > 0) {
+					image_contours(x, y) = spot_color;
+				}
+			}
+		}
+
+	// fin contour
+
+	//	printf("Quantif- mask\n");
+	//mark = image_depart masquée par mark
+	cimg_for_insideXY(image_contours,x,y,1)
+		{
+			if (image_contours(x, y) == 1)
+				image_contours(x, y) = image_depart(x, y);
+		}
+
+}
+;
+
+void imageContours::draw_contours(const detection & my_detection, bool inverse) {
+	imageContours & image_contours = *this;
+
+	// dessin des croix au centre des spots
+	//unsigned short int stroke_color = 65535-inverse*65535;
+	unsigned short int stroke_color = 0; //black 
+	detection::const_iterator it;
+	int xx, yy;
+	for (it = my_detection.begin(); it != my_detection.end(); ++it) {
+		const spot & the_spot = (*it).second;
+		yy = (int) the_spot.get_ty();
+		for (xx = std::max<int>((int) the_spot.get_tx() - 2, 0); xx < std::min<
+				int>((int) the_spot.get_tx() + 3, image_contours.width()); xx++) {
+
+			image_contours(xx, yy) = stroke_color;
+		}
+		xx = (int) the_spot.get_tx();
+		for (yy = std::max<int>((int) the_spot.get_ty() - 2, 0); yy < std::min<
+				int>((int) the_spot.get_ty() + 3, image_contours.height()); yy++) {
+			image_contours(xx, yy) = stroke_color;
+		}
+	}
+
+}
+;
diff --git a/src/images/imageContours.h b/src/images/imageContours.h
new file mode 100644
index 0000000..17cb5a4
--- /dev/null
+++ b/src/images/imageContours.h
@@ -0,0 +1,28 @@
+#ifndef IMAGECONTOURS_H_
+#define IMAGECONTOURS_H_
+
+#include "imageIntensity.h"
+#include "imageNumber.h"
+#include "../detection.h"
+//dessiner les contours des spots
+class imageContours: public imageIntensity {
+public:
+	imageContours(const char * & filename) :
+		imageIntensity(filename) {
+	}
+
+	imageContours() {
+	}
+
+	~imageContours() {
+	}
+
+	void compute_from_gel_image_and_numeros(
+			const imageIntensity & image_depart,
+			const imageNumber & image_numeros);
+	void draw_contours(const detection & my_detection, bool inverse);
+
+private:
+};
+
+#endif /*IMAGECONTOURS_H_*/
diff --git a/src/images/imageCoule.cpp b/src/images/imageCoule.cpp
new file mode 100644
index 0000000..618c477
--- /dev/null
+++ b/src/images/imageCoule.cpp
@@ -0,0 +1,40 @@
+#include "imageCoule.h"
+
+#include <iostream>
+
+using namespace std;
+
+void imageCoule::compute_from_direct(const imageDirect & direct) {
+	imageCoule & image_dest = *this;
+	image_dest.resize(direct.width(), direct.height(), 1, 1);
+	image_dest.fill(0);
+	// processing
+	cimg_forXY(direct,x,y)
+		{
+			int xx = x;
+			int yy = y;
+			int i = 0;
+			while ((i < 100) && (image_dest(xx, yy) == 0)
+					&& direct.move_to_spot_center(xx, yy)) {
+				i++;
+			}
+			// on ne fait rien en cas de plat (= case 100): xx et yy sont inchangés
+			if (direct.is_burned_pixel(xx, yy)) {
+				//
+				image_dest(xx, yy) = _max_value;
+			} else {
+				if (image_dest(xx, yy) != _max_value) {
+					image_dest(xx, yy) += 1;
+				}
+			}
+		}
+	/* in image coule:
+	 *  0 means the beads was rolling
+	 *  !=0 means N beads stopped here
+	 *  65535 means the zone is saturated
+	 */
+	//save("/tmp/coule.tiff");
+	//cout << "imageCoule::compute_from_direct end" << endl;
+}
+;
+
diff --git a/src/images/imageCoule.h b/src/images/imageCoule.h
new file mode 100644
index 0000000..7ded68b
--- /dev/null
+++ b/src/images/imageCoule.h
@@ -0,0 +1,28 @@
+#ifndef IMAGECOULE_H_
+#define IMAGECOULE_H_
+
+#include "imageIntensity.h"
+#include "imageDirect.h"
+
+//Faire couler les beads
+class imageCoule: public imageIntensity {
+public:
+	imageCoule(const char * & filename) :
+		imageIntensity(filename) {
+	}
+	;
+	imageCoule() {
+	}
+	;
+	~imageCoule() {
+	}
+	;
+
+	void compute_from_direct(const imageDirect & direct);
+	//void copy_from_image (const cimg_library::CImg<unsigned short int> & image1);
+
+private:
+	static const unsigned short int _max_value = 65535;
+};
+
+#endif /*IMAGECOULE_H_*/
diff --git a/src/images/imageDeNovo.cpp b/src/images/imageDeNovo.cpp
new file mode 100644
index 0000000..bc3abaf
--- /dev/null
+++ b/src/images/imageDeNovo.cpp
@@ -0,0 +1,150 @@
+#include "imageDeNovo.h"
+
+#include <iostream>
+#include <fstream>
+
+using namespace std;
+
+/** \brief create a theoretical spot on a gel image
+ *
+ * \param posx x position of the spot center
+ * \param posy y position of the spot center
+ * \param max_intensity_percentage the theoretical maximum peak intensity given in percentage (0-100)
+ * \param sx gaussian sx
+ * \param sy gaussian sy
+ *
+ */
+
+void imageDeNovo::addSpot(unsigned int posx, unsigned int posy,
+		float max_intensity_percentage, float sx, float sy) {
+
+	imageDeNovo & image = *this;
+
+	unsigned short int max_intensity = (unsigned short int) ((float) _max_value
+			* ((float) max_intensity_percentage / (float) 100));
+	//	cout << _max_value << "    "<< max_intensity_percentage <<"    "<< max_intensity << endl;
+
+	unsigned int maxx = (unsigned int) sx * 3;
+	unsigned int maxy = (unsigned int) sy * 3;
+	unsigned int minx = 0;
+	unsigned int miny = 0;
+	if (posx > maxx) {
+		minx = posx - maxx;
+	}
+	maxx = posx + maxx;
+	if (maxx > (unsigned int) image.width()) {
+		maxx = image.width();
+	}
+
+	if (posy > maxy) {
+		miny = posy - maxy;
+	}
+	maxy = posy + maxy;
+	if (maxy > (unsigned int) image.height()) {
+		maxy = image.height();
+	}
+
+	for (unsigned int x = minx; x < maxx; x++) {
+		for (unsigned int y = miny; y < maxy; y++) {
+			double fnx = (double) x - (double) posx;
+			double fny = (double) y - (double) posy;
+			double valx = (fnx * fnx) / (2 * (sx * sx));
+			double valy = (fny * fny) / (2 * (sy * sy));
+
+			double tmp = (((double) max_intensity) * (double) std::exp(-valx
+					- valy)) + image(x, y);
+			if (tmp > (double) _max_value) {
+				image(x, y) = _max_value;
+				//cout << image(x,y) << endl;
+			} else {
+				image(x, y) = (unsigned short int) tmp;
+			}
+		}
+	}
+
+	//generate the spot profile:
+	/*
+	 cimg_for_insideXY(image,x,y,0) {
+	 double fnx = (double)x - (double) posx;
+	 double fny = (double)y - (double) posy;
+	 double valx=(fnx*fnx)/(2*(sx*sx));
+	 double valy=(fny*fny)/(2*(sy*sy));
+
+	 double tmp = (((double) max_intensity) * (double)std::exp(-valx-valy)) + image(x,y);
+	 if (tmp > (double) _max_value) {
+	 image(x,y) = _max_value;
+	 //cout << image(x,y) << endl;
+	 }
+	 else {
+	 image(x,y) = (unsigned short int)tmp;
+	 }
+	 //cout << fnx << " " << fny << " "<< std::exp(-valx-valy) << " " << x << " " << y << " " << image(x,y) << endl;
+	 }
+	 */
+}
+
+bool imageDeNovo::build_image_with_spot_file(const QString & filename) {
+	ifstream ifs;
+
+	ifs.open(filename.toStdString().c_str(), ifstream::in);
+	if (ifs.is_open() == 0) {
+		ifs.clear();
+		cerr << "ERROR : denovo spot file " << filename.toStdString() << " not found" << endl;
+		return (false);
+	}
+	std::string temp;
+
+	ifs >> temp;
+	ifs >> temp;
+
+	//define image height and width :
+	unsigned int height;
+	unsigned int width;
+
+	ifs >> height;
+	ifs >> width;
+	if (!ifs.good()) {
+		cerr << "height and width not found" << endl;
+		return (false);
+	}
+	this->resize(height, width, 1, 1);
+	this->fill(0);
+
+	unsigned int posx;
+	unsigned int posy;
+	float max_intensity_percentage;
+	float sx;
+	float sy;
+
+	ifs >> temp;
+	ifs >> temp;
+	ifs >> temp;
+	ifs >> temp;
+	ifs >> temp;
+
+	if (!ifs.good()) {
+		cerr << "denovo spot file not well formated" << endl;
+	}
+
+	while (ifs.good()) {
+		ifs >> posx;
+		if (!ifs.good()) {
+			break;
+		}
+		ifs >> posy;
+		ifs >> max_intensity_percentage;
+		ifs >> sx;
+		ifs >> sy;
+		if (ifs.good()) {
+			this->addSpot(posx, posy, max_intensity_percentage, sx, sy);
+		} else {
+			cerr << "missing parameters to add spot " << posx << posy << endl;
+			return (false);
+		}
+	}
+
+	ifs.close();
+	return (true);
+
+}
+;
diff --git a/src/images/imageDeNovo.h b/src/images/imageDeNovo.h
new file mode 100644
index 0000000..146f597
--- /dev/null
+++ b/src/images/imageDeNovo.h
@@ -0,0 +1,22 @@
+#ifndef IMAGEDENOVO_H_
+#define IMAGEDENOVO_H_
+
+#include "imageIntensity.h"
+
+using namespace std;
+
+class imageDeNovo : public imageIntensity {
+public:
+	imageDeNovo() {
+	};
+	~imageDeNovo() {
+	};
+
+	void addSpot(unsigned int posx, unsigned int posy, float max_intensity_percentage, float sx, float sy);
+
+	bool build_image_with_spot_file ( const QString & filename );
+
+private:
+};
+
+#endif /*IMAGEDENOVO_H_*/
diff --git a/src/images/imageDetection.cpp b/src/images/imageDetection.cpp
new file mode 100644
index 0000000..0f7673f
--- /dev/null
+++ b/src/images/imageDetection.cpp
@@ -0,0 +1,14 @@
+#include "imageDetection.h"
+
+#include "../detection.h"
+
+imageDetection::imageDetection(detection & my_detection) {
+	imageDetection & detection_image = *this;
+	resize (my_detection.get_gel_image_width(),my_detection.get_gel_image_height(),1,1);
+	fill(0);
+
+	detection::iterator it;
+	for (it = my_detection.begin (); it != my_detection.end (); ++it) {
+		detection_image((*it).second.get_x(), (*it).second.get_y())=65000;
+	}
+};
diff --git a/src/images/imageDetection.h b/src/images/imageDetection.h
new file mode 100644
index 0000000..be0e5b1
--- /dev/null
+++ b/src/images/imageDetection.h
@@ -0,0 +1,23 @@
+#ifndef IMAGEDETECTION_H_
+#define IMAGEDETECTION_H_
+
+#include "imageIntensity.h"
+
+class detection;
+
+//images des spots détectés
+class imageDetection : public cimg_library::CImg<unsigned short int> {
+public:
+
+	imageDetection(detection &);
+
+	imageDetection(const char * & filename) : cimg_library::CImg<unsigned short int>(filename){
+	};
+
+	~imageDetection() {
+	};
+
+private:
+};
+
+#endif /*IMAGEDETECTION_H_*/
diff --git a/src/images/imageDirect.cpp b/src/images/imageDirect.cpp
new file mode 100644
index 0000000..edd1a47
--- /dev/null
+++ b/src/images/imageDirect.cpp
@@ -0,0 +1,171 @@
+#include "imageDirect.h"
+#include "imagePaths.h"
+
+#include <iostream>
+
+using namespace std;
+
+void imageDirect::save(const QString & filename) {
+	cimg_library::CImg<unsigned short int>::save(filename.toStdString().c_str());
+}
+
+/** \brief compute the direction of the greatest slop at each pixel
+ * 
+ * the resulting image describes the directions with a code :
+ * 0 - 7 are possible directions
+ * if there is no slope, the value will be 1000
+ * edges of the images directions are set to 100
+ * 
+ */
+
+void imageDirect::compute_from_gel_image(const imageIntensity & gel_image,
+		const parameterDirect & parameter_direct) {
+	//	gel_image.save("/tmp/gel_image.tiff");
+
+	//unsigned short int burned_threshold(imageDirect::_max_value);
+	unsigned short int burned_threshold(
+			parameter_direct.get_burned_pixel_threshold());
+	//cerr << "imageDirect::compute_from_gel_image begin" << endl;
+
+	imageDirect & image_direct = *this;
+	image_direct.resize(gel_image.width(), gel_image.height(), 1, 1);
+	//	cimg_library::CImg<float> grad(3,3,1,1);
+	float grad[8];
+	// processing
+	// calcul des 8 gradients
+	CImg_3x3(I,unsigned short int);
+	int color_channel(0);
+	cimg_for3x3 (gel_image,x,y,0,color_channel,I, unsigned short int)
+		{
+
+			grad[0] = Ipc - Inc;
+			grad[1] = Icp - Icn;
+			grad[2] = (Ipp - Inn) / 1.4;
+			grad[3] = (Inp - Ipn) / 1.4;
+			grad[4] = -grad[0];
+			grad[5] = -grad[1];
+			grad[6] = -grad[2];
+			grad[7] = -grad[3];
+			/*
+			 grad[0]=Inc-Ipc;
+			 grad[1]=Icn-Icp;
+			 grad[2]=(Inn-Ipp)/1.4;
+			 grad[3]=(Ipn-Inp)/1.4;
+			 grad[4]=-grad[0];
+			 grad[5]=-grad[1];
+			 grad[6]=-grad[2];
+			 grad[7]=-grad[3];
+			 */
+			// calcul de la direction de plus grande pente
+			int j = 0;
+			int mind = 100; // si plat, mind restera 100
+			float mmm = 0;
+			while (j < 8) {
+				if (grad[j] > mmm) {
+					mmm = grad[j];
+					mind = j;
+				}
+				j++;
+			}
+			image_direct(x, y) = mind;
+			// si saturé on transmet la valeur saturée. attention valeur arbitraire
+			if (gel_image(x, y, color_channel) > burned_threshold)
+				set_burned_pixel(x, y);
+		}
+
+	//le contour de l'image doit être à 100: ne rien faire
+	cimg_for_borderXY(image_direct,x,y,1)
+			image_direct(x, y) = 100;
+
+	//cerr << "imageDirect::compute_from_gel_image end" << endl;
+
+	//image_dest.save("/tmp/direct.tiff");
+	//save("/tmp/direct.tiff");
+	//cerr << "imageDirect::compute_from_gel_image end" << endl;
+
+}
+;
+
+/** \brief move x and y to the next pixel toward the spot center
+ * 
+ * \param & x reference to x coordinate of the spot to move
+ * \param & y reference to y coordinate of the spot to move
+ * 
+ * \result bool TRUE if the move is possible, FALSE otherwise
+ *  
+ */
+
+bool imageDirect::move_to_spot_center(int & xx, int & yy) const {
+	//moving bead according to directions toward the top
+	switch (*(data(xx, yy))) {
+	case 0:
+		xx--;
+		break; //move left
+	case 1:
+		yy--;
+		break; //move up
+	case 2: {
+		xx--;
+		yy--;
+	}
+		break; //move up left
+	case 3: {
+		xx++;
+		yy--;
+	}
+		break; //move up right
+	case 4:
+		xx++;
+		break; // move right
+	case 5:
+		yy++;
+		break; // move down
+	case 6: {
+		xx++;
+		yy++;
+	}
+		break; // move down right
+	case 7: {
+		xx--;
+		yy++;
+	}
+		break; // move down left
+	default:
+		return (false);
+	}
+	return (true);
+}
+
+/** \brief get the number of confluents pointing to this pixel
+ * 
+ * \param x x coordinate of the spot
+ * \param y y coordinate of the spot
+ * \param paths reference on the paths
+ * \param minflux the minimum flux to take confluent into account
+ * \return the number of confluents
+ */
+
+int imageDirect::get_nb_confluent_for_pixel(int x, int y,
+		const imagePaths & paths, int minflux) const {
+	const imageDirect & direct = *this;
+	int nb_confluent(0);
+	if (direct(x + 1, y - 1) == 7 && paths(x + 1, y - 1) > minflux)
+		nb_confluent++;
+	if (direct(x + 1, y) == 0 && paths(x + 1, y) > minflux)
+		nb_confluent++;
+	if (direct(x + 1, y + 1) == 2 && paths(x + 1, y + 1) > minflux)
+		nb_confluent++;
+	if (direct(x, y + 1) == 1 && paths(x, y + 1) > minflux)
+		nb_confluent++;
+	if (direct(x - 1, y + 1) == 3 && paths(x - 1, y + 1) > minflux)
+		nb_confluent++;
+	if (direct(x - 1, y) == 4 && paths(x - 1, y) > minflux)
+		nb_confluent++;
+	if (direct(x - 1, y - 1) == 6 && paths(x - 1, y - 1) > minflux)
+		nb_confluent++;
+	if (direct(x, y - 1) == 5 && paths(x, y - 1) > minflux)
+		nb_confluent++;
+
+	return (nb_confluent);
+}
+
diff --git a/src/images/imageDirect.h b/src/images/imageDirect.h
new file mode 100644
index 0000000..6e61c77
--- /dev/null
+++ b/src/images/imageDirect.h
@@ -0,0 +1,62 @@
+#ifndef IMAGEDIRECT_H_
+#define IMAGEDIRECT_H_
+
+#include "imageIntensity.h"
+#include "parameterDirect.h"
+
+class imagePaths;
+
+/** \brief stores the directions of the greater slopes for each pixel
+ * 
+ * the resulting image describes the directions to the spot center with a code :
+ * 0 - 7 are possible directions
+ * if there is no slope, the value will be 1000
+ * edges of the images directions are set to 100
+ * burned pixels are coded with the _max_value (saturated zone)
+ * 
+ */
+class imageDirect: public cimg_library::CImg<unsigned short int> {
+public:
+	imageDirect(const QString & filename) :
+		cimg_library::CImg<unsigned short int>(filename.toStdString().c_str()) {
+	}
+
+	imageDirect() {
+	}
+
+	~imageDirect() {
+	}
+
+	void compute_from_gel_image(const imageIntensity & gel_image,
+			const parameterDirect & parameter_direct);
+
+	/** \brief set the pixel at position x, y as burned
+	 */
+	void set_burned_pixel(int x, int y) {
+		//float *ptr = data(x, y);
+		*(data(x, y)) = _max_value;
+	}
+
+	/** \brief tell if the pixel is burned (saturated zone)
+	 */
+	bool is_burned_pixel(int x, int y) const {
+		if (*(data(x, y)) == _max_value) {
+			return (true);
+		}
+		return (false);
+	}
+
+	bool move_to_spot_center(int & xx, int & yy) const;
+
+	int get_nb_confluent_for_pixel(int x, int y, const imagePaths & paths,
+			int minflux) const;
+	void save(const QString & filename);
+
+private:
+
+public:
+	static const unsigned short int _max_value = 65535;
+
+};
+
+#endif /*IMAGEDIRECT_H_*/
diff --git a/src/images/imageIntensity.cpp b/src/images/imageIntensity.cpp
new file mode 100644
index 0000000..7751deb
--- /dev/null
+++ b/src/images/imageIntensity.cpp
@@ -0,0 +1,119 @@
+#include "imageIntensity.h"
+
+#include <iostream>
+
+using namespace std;
+using namespace cimg_library;
+
+imageIntensity::imageIntensity(int dimx, int dimy, int slices, int channel, bool is_inversed, const QString & image_filename) :
+	cimg_library::CImg<unsigned short int>(dimx, dimy, slices, channel) {
+	_is_inversed = is_inversed;
+	_image_filename = image_filename;
+}
+
+imageIntensity::imageIntensity(const QString & filename) :
+	cimg_library::CImg<unsigned short int>(filename.toStdString().c_str()) {
+	_is_inversed = false;
+	_image_filename = filename;
+
+	if (this->max_intensity() < 256) {
+		this->transform_16_bits();
+	}
+	//cerr << "imageIntensity::imageIntensity bits " << img.max();
+}
+
+void imageIntensity::save(const QString & filename) const {
+	qDebug() << "imageIntensity::save(const QString & filename) begin";
+	//1QFileInfo fileinfo(filename);
+	cimg_library::CImg<unsigned short int>::save(filename.toStdString().c_str());
+	qDebug() << "imageIntensity::save(const QString & filename) end";
+}
+
+unsigned short int imageIntensity::max_intensity() const {
+
+	unsigned short int maximum(0);
+	const imageIntensity & img = *this;
+
+	cimg_forXY(img,x,y)
+		{
+			if (img(x, y) > maximum) {
+				maximum = img(x, y);
+			}
+		}
+	return (maximum);
+}
+
+void imageIntensity::transform_16_bits() {
+	imageIntensity & img = *this;
+	cimg_forXY(img,x,y)
+		{
+			img(x, y) *= 254;
+		}
+}
+
+imageIntensity * imageIntensity::new_image_inversed() const {
+	const imageIntensity & image = *this;
+	imageIntensity * p_image_inversed = new imageIntensity(image.width(),
+			image.height(), 1, 1, !_is_inversed, _image_filename);
+	imageIntensity & image_inversed = *p_image_inversed;
+
+	cimg_forXY(image,x,y)
+		{
+			image_inversed(x, y) = 65535 - image(x, y);
+		}
+
+	return (p_image_inversed);
+}
+
+void imageIntensity::save_inversed(const QString & filename) const {
+	imageIntensity * image_inversed = this->new_image_inversed();
+	image_inversed->save(filename);
+}
+
+/** \brief look for a neighbor pixel with greater intensity
+ *
+ */
+int imageIntensity::amas(int &xx, int &yy, imageCode & mark) const {
+	const imageIntensity & current_image = *this;
+	// si result reste à 1 c'est qu'on n'a pas trouvé de voisin d'intensité supérieure
+	//si result passe à 0 c'est qu'on en a trouvé 1. Mais s'il est déjà marqué, result est mis à -1.
+	//comme on recherche toujours un max, on ne peut pas être éjà passé pour le
+	//spot courant. Donc si on tombe sur du déjà marqué c'est que déjà marqué
+	//à partir d'un autre pixel, et donc il faut abandonner. (result=-1)
+	int result = 1;
+	if ((current_image(xx + 1, yy - 1) - current_image(xx, yy) > 0)) {
+		xx++;
+		yy--;
+		result = 0;
+	} else if ((current_image(xx + 1, yy) - current_image(xx, yy) > 0)) {
+		xx++;
+		result = 0;
+	} else if ((current_image(xx + 1, yy + 1) - current_image(xx, yy) > 0)) {
+		xx++;
+		yy++;
+		result = 0;
+	} else if ((current_image(xx, yy + 1) - current_image(xx, yy) > 0)) {
+		yy++;
+		result = 0;
+	} else if ((current_image(xx - 1, yy + 1) - current_image(xx, yy) > 0)) {
+		xx--;
+		yy++;
+		result = 0;
+	} else if ((current_image(xx - 1, yy) - current_image(xx, yy) > 0)) {
+		xx--;
+		result = 0;
+	} else if ((current_image(xx - 1, yy - 1) - current_image(xx, yy) > 0)) {
+		xx--;
+		yy--;
+		result = 0;
+	} else if ((current_image(xx, yy - 1) - current_image(xx, yy) > 0)) {
+		yy--;
+		result = 0;
+	}
+
+	if (result == 0 && mark(xx, yy) == 1)
+		result = -1;
+	mark(xx, yy) = 1;
+	return result;
+
+}
diff --git a/src/images/imageIntensity.h b/src/images/imageIntensity.h
new file mode 100644
index 0000000..61ba248
--- /dev/null
+++ b/src/images/imageIntensity.h
@@ -0,0 +1,60 @@
+#ifndef IMAGEINTENSITY_H_
+#define IMAGEINTENSITY_H_
+
+#include <QDebug>
+#include <QString>
+
+#include "imageCode.h"
+
+//Images pour lesquelles les valeurs des pixels représentent des valeurs quantitatives
+//L'image de départ et presque toutes les intermédiaires sont des imagesIntensity
+class imageIntensity: public cimg_library::CImg<unsigned short int> {
+public:
+	imageIntensity(int dimx, int dimy, int slices, int channel, bool is_inversed, const QString & _image_filename);
+	imageIntensity(const QString & filename);
+	imageIntensity() {
+		_is_inversed = false;
+	}
+	imageIntensity(const imageIntensity & original) :
+		cimg_library::CImg<unsigned short int>(original) {
+		_is_inversed = original.get_is_inversed();
+		_image_filename = original.get_image_filename();
+	}
+
+	~imageIntensity() {
+		_is_inversed = false;
+	}
+
+	//void image_inverse ();
+	imageIntensity * new_image_inversed() const;
+
+	int amas(int &xx, int &yy, imageCode & mark) const;
+
+	const QString & get_image_filename() const {
+		return _image_filename;
+	}
+
+	const bool get_is_inversed() const {
+		return (_is_inversed);
+	}
+
+	void save(const QString & filename) const;
+
+	void save_inversed(const QString & filename) const;
+
+	unsigned short int max_intensity() const;
+
+private:
+	bool _is_inversed;
+
+	QString _image_filename;
+
+protected:
+	void transform_16_bits();
+
+public:
+	static const unsigned short int _max_value = 65535;
+
+};
+
+#endif /*IMAGEINTENSITY_H_*/
diff --git a/src/images/imageNumber.cpp b/src/images/imageNumber.cpp
new file mode 100644
index 0000000..4f4e20d
--- /dev/null
+++ b/src/images/imageNumber.cpp
@@ -0,0 +1,525 @@
+#include <iostream>
+#include "imageNumber.h"
+
+using namespace std;
+
+void imageNumber::save(const QString & filename) {
+	cimg_library::CImg<short int>::save(filename.toStdString().c_str());
+}
+
+/** \brief fusion of 2 detected spots
+ * 
+ * the image number is updated and the second spot is erased from the detection
+ */
+void imageNumber::fusion(int spot_number_1, int spot_number_2,
+		detection & the_detection) {
+	if (spot_number_1 == spot_number_2) {
+		return;
+	}
+	if (spot_number_1 == -spot_number_2) {
+		return;
+	}
+	imageNumber & image_numeros = *this;
+
+	//cerr << "imageNumber::fusion " << spot_number_1 << " " << spot_number_2 << endl;
+	cimg_for_insideXY(image_numeros,x,y,1)
+		{
+			if (image_numeros(x, y) == spot_number_2) {
+				image_numeros(x, y) = spot_number_1;
+			} else if (image_numeros(x, y) == -spot_number_2) {
+				image_numeros(x, y) = -spot_number_1;
+			}
+		}
+	if (spot_number_2 > 0) {
+		the_detection.erase(spot_number_2);
+	} else {
+		the_detection.erase(-spot_number_2);
+	}
+}
+
+void imageNumber::set_numbers_with_detection(detection & the_detection) {
+	imageNumber & image_numeros = *this;
+
+	qDebug()
+			<< "imageNumber::set_numbers_with_detection(detection & the_detection) begin size"
+			<< the_detection.size();
+	image_numeros.fill(0);
+
+	// préliminaire 1: on numérote les spots.
+	// si un voisin du pixel a déjà été marqué, on le marque avec le meme numero (proc voisin)
+	//sinon on le marque avec un nouveau numero.
+
+
+	detection::iterator it, to_delete;
+	int spot_num, x, y;
+	for (it = the_detection.begin(); it != the_detection.end();) {
+		spot_num = (*it).first;
+		x = (*it).second.get_x();
+		y = (*it).second.get_y();
+		int spot_num_neigbhor = image_numeros.neighbor_number(x, y);
+		if (spot_num_neigbhor == 0) {
+			//the spot has no neighbors, the spot number is set to this pixel
+			image_numeros(x, y) = spot_num;
+			++it;
+		} else {
+			//the spot has a neighbor, the pixel takes the neighbors spot number and
+			// the spot is deleted from the detection:
+			image_numeros(x, y) = spot_num_neigbhor;
+			//provisoirement on ne le marque pas du tout
+			//			image_numeros(x,y)=spot_num;
+			to_delete = it;
+			it++;
+			qDebug()
+					<< "imageNumber::set_numbers_with_detection(detection & the_detection) erase "
+					<< to_delete->first;
+			the_detection.erase(to_delete);
+		}
+	}
+	qDebug()
+			<< "imageNumber::set_numbers_with_detection(detection & the_detection) end size "
+			<< the_detection.size();
+}
+
+void imageNumber::enlarge_and_fusion(detection & my_detection, int enlarge,
+		const imageDirect & direct) {
+	imageNumber & image_numeros = *this;
+
+	/* ici on détermine l'intervale de confiance pour la position de
+	 * chaque spot. fait comme la 1ere passe de l'ancienne version.
+	 * si en s'élargissant les zones de 2 spots se rencontrent, elles fusionnent: il n'y a plus qu'un spot
+	 */
+
+	for (int cpass = 0; cpass < enlarge; cpass++) {
+		//cerr << "imageNumber::enlarge_and_fusion cpass: " << cpass << endl;
+		cimg_for_insideXY(image_numeros,x,y,1)
+			{
+				if (direct.is_burned_pixel(x, y)) {
+					//don't enlarge and fusion when the zone is burned
+				} else {
+					if (image_numeros(x, y) > 0) {
+						if (image_numeros(x + 1, y - 1) == 0) {
+							image_numeros(x + 1, y - 1) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y), image_numeros(x + 1, y
+									- 1), my_detection);
+						}
+						if (image_numeros(x + 1, y) == 0) {
+							image_numeros(x + 1, y) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y),
+									image_numeros(x + 1, y), my_detection);
+						}
+						if (image_numeros(x + 1, y + 1) == 0) {
+							image_numeros(x + 1, y + 1) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y), image_numeros(x + 1, y
+									+ 1), my_detection);
+						}
+						if (image_numeros(x, y + 1) == 0) {
+							image_numeros(x, y + 1) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y),
+									image_numeros(x, y + 1), my_detection);
+						}
+						if (image_numeros(x - 1, y + 1) == 0) {
+							image_numeros(x - 1, y + 1) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y), image_numeros(x - 1, y
+									+ 1), my_detection);
+						}
+						if (image_numeros(x - 1, y) == 0) {
+							image_numeros(x - 1, y) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y),
+									image_numeros(x - 1, y), my_detection);
+						}
+						if (image_numeros(x - 1, y - 1) == 0) {
+							image_numeros(x - 1, y - 1) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y), image_numeros(x - 1, y
+									- 1), my_detection);
+						}
+						if (image_numeros(x, y - 1) == 0) {
+							image_numeros(x, y - 1) = -image_numeros(x, y);
+						} else {
+							fusion(image_numeros(x, y),
+									image_numeros(x, y - 1), my_detection);
+						}
+					}
+				}
+			}
+
+		// 2eme phase: on ne touche qu'aux négatifs: on les met en positifs.
+
+		cimg_for_insideXY(image_numeros,x,y,1)
+			{
+				if (image_numeros(x, y) < 0) {
+					image_numeros(x, y) = -image_numeros(x, y);
+				}
+			}
+		// fin cpass sans test sur image de départ
+	}
+}
+
+void imageNumber::bouche_trou() {
+	imageNumber & image_numeros = *this;
+	//bouche-trou bas de gamme
+	const int ncols = image_numeros.width();
+	const int nrows = image_numeros.height();
+	int nc, nr, ncdep, nnc;
+	int couldep, coularr;
+	for (nr = 1; nr < nrows - 1; nr++) {
+		nc = 0;
+		while (nc < ncols - 1) {
+			while (nc < ncols - 1 && image_numeros(nc, nr) > 0) {
+				nc++;
+			}
+			if (nc < ncols - 1) {
+				ncdep = nc;
+				couldep = image_numeros(nc - 1, nr);
+				while (nc < ncols - 1 && image_numeros(nc, nr) == 0) {
+					nc++;
+				}
+				if (nc < ncols - 1) {
+					coularr = image_numeros(nc, nr);
+					if (couldep == coularr) {
+						for (nnc = ncdep; nnc < nc; nnc++) {
+							image_numeros(nnc, nr) = couldep;
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+void imageNumber::enlarge(int enlarge) {
+	imageNumber & image_numeros = *this;
+
+	// --------------------------------------------------------------------------
+	// partie à boucler en principe tant que ca bouge encore.
+
+	// 1ere phase: on marque en négatif les voisins des surfaces déjà marquées,
+	//avec la meme valeur, en négatif pour les distinguer des plus anciens,
+	// et donc n'avancer que d'un pixel à la fois pour tout le monde
+	// c'est important car sinon je suis obligé de calculer des distances quand
+	// il y a conflit entre 2 spots pour l'occupation d'un pixel.
+	// attention au signe de la différence dans img2: contre-intuitif car
+	//ici fortes valeurs= zones claires=fond
+
+	//attention, pour boucher d'entrée les trous qui sont dus à un départ
+	//trop loin du max, on ne fait pas de test sur l'intensité dans l'image au
+	//cours des 4 premières passes.
+
+	/* ici on détermine l'intervale de confiance pour la position de
+	 * chaque spot. fait comme la 1ere passe de l'ancienne version.
+	 */
+
+	for (int cpass = 0; cpass < enlarge; cpass++) {
+		//		cout << "imageNumber::enlarge cpass: " << cpass << endl;
+		cimg_for_insideXY(image_numeros,x,y,1)
+			{
+				if (image_numeros(x, y) > 0) {
+					if (image_numeros(x + 1, y - 1) == 0) {
+						image_numeros(x + 1, y - 1) = -image_numeros(x, y);
+					}
+
+					if (image_numeros(x + 1, y) == 0) {
+						image_numeros(x + 1, y) = -image_numeros(x, y);
+					}
+
+					if (image_numeros(x + 1, y + 1) == 0) {
+						image_numeros(x + 1, y + 1) = -image_numeros(x, y);
+					}
+
+					if (image_numeros(x, y + 1) == 0) {
+						image_numeros(x, y + 1) = -image_numeros(x, y);
+					}
+
+					if (image_numeros(x - 1, y + 1) == 0) {
+						image_numeros(x - 1, y + 1) = -image_numeros(x, y);
+					}
+
+					if (image_numeros(x - 1, y) == 0) {
+						image_numeros(x - 1, y) = -image_numeros(x, y);
+					}
+
+					if (image_numeros(x - 1, y - 1) == 0) {
+						image_numeros(x - 1, y - 1) = -image_numeros(x, y);
+					}
+
+					if (image_numeros(x, y - 1) == 0) {
+						image_numeros(x, y - 1) = -image_numeros(x, y);
+					}
+
+				}
+			}
+
+		// 2eme phase: on ne touche qu'aux négatifs: on les met en positifs.
+
+		cimg_for_insideXY(image_numeros,x,y,1)
+			{
+				if (image_numeros(x, y) < 0) {
+					image_numeros(x, y) = -image_numeros(x, y);
+				}
+			}
+		// fin cpass sans test sur image de départ
+	}
+}
+
+void imageNumber::set_burned_zone_number(const imageDirect & direct,
+		int number, int x, int y, unsigned int & depth) {
+
+	depth++;
+	if (depth > 1000) {
+		depth--;
+		return;
+	}
+
+	imageNumber & image_number = *this;
+	//if the spot is burned
+	if (direct.is_burned_pixel(x, y)) {
+		if (image_number(x, y) == 0) {
+			// set image_number of the whole burned area :
+			image_number(x, y) = number;
+			set_burned_zone_number(direct, number, x + 1, y, depth);
+			set_burned_zone_number(direct, number, x, y + 1, depth);
+			set_burned_zone_number(direct, number, x - 1, y, depth);
+			set_burned_zone_number(direct, number, x, y - 1, depth);
+		}
+	}
+	depth--;
+}
+
+void imageNumber::enlarge_burned_areas(const imageDirect & direct,
+		detection & the_detection) {
+	//imageNumber & image_number = *this;
+	detection::iterator it;
+	for (it = the_detection.begin(); it != the_detection.end(); ++it) {
+		spot & the_spot = (*it).second;
+		int x(the_spot.get_x()), y(the_spot.get_y());
+		//if the spot is burned
+		if (direct.is_burned_pixel(x, y)) {
+			unsigned int depth(0);
+			set_burned_zone_number(direct, the_spot.get_number(), x + 1, y,
+					depth);
+			set_burned_zone_number(direct, the_spot.get_number(), x, y + 1,
+					depth);
+			set_burned_zone_number(direct, the_spot.get_number(), x - 1, y,
+					depth);
+			set_burned_zone_number(direct, the_spot.get_number(), x, y - 1,
+					depth);
+		}
+
+	}
+
+}
+
+/** set image numbers with the detection and directions
+ * 
+ * 1) attribute numbers from the detection (with the spot coordinates)
+ * 2) enlarge burned areas
+ * 3) enlarge spot areas with enlarge parameter
+ * 4) with directions, drop beads and let them flow to the spot area. This will give the full spot area
+ */
+
+void imageNumber::compute_from_direct(const imageDirect & direct,
+		detection & my_detection, const parameterNumber & parameter_number) {
+
+	qDebug() << "imageNumber::compute_from_direct begin";
+
+	int enlarge_param = parameter_number.get_enlarge();
+	int enlarge_fusion_param = parameter_number.get_enlarge_fusion();
+
+	// un petit coup de median d'abord
+	// devenu inutile ici
+	/*
+	 cout << "Quantif- lissage de l'image" << endl;
+	 int n;
+	 gel_image.blur_median(n=3);
+	 */
+
+	imageNumber & image_numeros = *this;
+	image_numeros.resize(direct.width(), direct.height(), 1, 1); //dest
+
+	//	printf("Quantif- reperage spots\n");
+
+
+	// 1) attribute numbers from the detection (with the spot coordinates)
+	set_numbers_with_detection(my_detection);
+
+	cout << "Quantif- spots left: " << my_detection.size() << endl;
+
+	// 2) enlarge burned areas
+	enlarge_burned_areas(direct, my_detection);
+
+	// 3) enlarge spot areas with enlarge parameter
+	enlarge_and_fusion(my_detection, enlarge_fusion_param, direct);
+	enlarge(enlarge_param);
+
+	// 4) with directions, drop beads and let them flow to the spot area. This will give the full spot area
+
+	/* maintenant je coule à partir de direct, et le numéro trouvé à l'arrivée dans image_numeros
+	 * est donné au point de départ. Si le point d'arrivée n'est pas affecté à un spot, je donne
+	 * la valeur 0
+	 */
+	cimg_forXY(direct,x,y)
+		{
+			int xx = x;
+			int yy = y;
+			int i = 0;
+			int curr;
+			// on s'arrete des qu'on penetre dans une zone occupée par un carré de spot
+			// attention ca risque de faire aller jusqu'à 100 pour bcp de spots du bruit de
+			// fond
+			while (i < 100 && image_numeros(xx, yy) == 0) {
+				i++;
+				curr = (int) direct(xx, yy);
+				switch (curr) {
+				case 0:
+					xx--;
+					break;
+				case 1:
+					yy--;
+					break;
+				case 2: {
+					xx--;
+					yy--;
+				}
+					break;
+				case 3: {
+					xx++;
+					yy--;
+				}
+					break;
+				case 4:
+					xx++;
+					break;
+				case 5:
+					yy++;
+					break;
+				case 6: {
+					xx++;
+					yy++;
+				}
+					break;
+				case 7: {
+					xx--;
+					yy++;
+				}
+					break;
+				}
+			}
+			// on ne fait rien en cas de plat (= case -1): xx et yy sont inchangés
+			// est-ce judicieusement fait ?
+
+			// on attribue au pixel de départ le numero du pixel d'arrivée.
+			// si le pixel d'arrivée est négatif (déjà passé) ou nul (pas de spot là), on met -1
+			if (image_numeros(xx, yy) > 0)
+				image_numeros(x, y) = image_numeros(xx, yy);
+			else
+				image_numeros(x, y) = -1;
+		}
+
+	// juste par sécurité on supprime les pixels à -1 qui n'ont servi qu'à voir où on était
+	// déjà passé.
+	cimg_forXY(image_numeros,x,y)
+		{
+			if (image_numeros(x, y) < 0)
+				image_numeros(x, y) = 0;
+		}
+
+	//	printf ("Quantif- bouche-trou\n");
+
+	bouche_trou();
+	qDebug() << "imageNumber::compute_from_direct end";
+
+}
+
+/** \brief look for a neighboring pixel with a number > 0
+ *
+ * if a neighbor pixel with a number is found, return the neighbor pixel number
+ */
+// procedure voisin
+int imageNumber::neighbor_number(int x, int y) const {
+	const imageNumber & image_numeros = *this;
+	if (image_numeros(x + 1, y - 1) > 0) {
+		return (image_numeros(x + 1, y - 1));
+	} else if (image_numeros(x + 1, y) > 0) {
+		return image_numeros(x + 1, y);
+	} else if (image_numeros(x + 1, y + 1) > 0) {
+		return image_numeros(x + 1, y + 1);
+	} else if (image_numeros(x, y + 1) > 0) {
+		return image_numeros(x, y + 1);
+	} else if (image_numeros(x - 1, y + 1) > 0) {
+		return image_numeros(x - 1, y + 1);
+	} else if (image_numeros(x - 1, y) > 0) {
+		return image_numeros(x - 1, y);
+	} else if (image_numeros(x - 1, y - 1) > 0) {
+		return image_numeros(x - 1, y - 1);
+	} else if (image_numeros(x, y - 1) > 0) {
+		return image_numeros(x, y - 1);
+	} else
+		return 0;
+}
+
+void imageNumber::expand_areas_down_the_slope(const imageIntensity & gel_image,
+		int npass) {
+	//int nspot=my_detection.size();
+	imageNumber & image_numeros = *this;
+
+	for (int cpass = 0; cpass < npass; cpass++) {
+		//		cout << "imageNumber::expand_areas_down_the_slope " << cpass << endl;
+		cimg_for_insideXY(image_numeros,x,y,1)
+			{
+				if (image_numeros(x, y) > 0) {
+					if (image_numeros(x + 1, y - 1) == 0 && (gel_image(x + 1, y
+							- 1) < gel_image(x, y))) {
+						image_numeros(x + 1, y - 1) = -image_numeros(x, y);
+					}
+					if (image_numeros(x + 1, y) == 0 && (gel_image(x + 1, y)
+							< gel_image(x, y))) {
+						image_numeros(x + 1, y) = -image_numeros(x, y);
+					}
+					if (image_numeros(x + 1, y + 1) == 0 && (gel_image(x + 1, y
+							+ 1) < gel_image(x, y))) {
+						image_numeros(x + 1, y + 1) = -image_numeros(x, y);
+					}
+					if (image_numeros(x, y + 1) == 0 && (gel_image(x, y + 1)
+							< gel_image(x, y))) {
+						image_numeros(x, y + 1) = -image_numeros(x, y);
+					}
+					if (image_numeros(x - 1, y + 1) == 0 && (gel_image(x - 1, y
+							+ 1) < gel_image(x, y))) {
+						image_numeros(x - 1, y + 1) = -image_numeros(x, y);
+					}
+					if (image_numeros(x - 1, y) == 0 && (gel_image(x - 1, y)
+							< gel_image(x, y))) {
+						image_numeros(x - 1, y) = -image_numeros(x, y);
+					}
+					if (image_numeros(x - 1, y - 1) == 0 && (gel_image(x - 1, y
+							- 1) < gel_image(x, y))) {
+						image_numeros(x - 1, y - 1) = -image_numeros(x, y);
+					}
+					if (image_numeros(x, y - 1) == 0 && (gel_image(x, y - 1)
+							< gel_image(x, y))) {
+						image_numeros(x, y - 1) = -image_numeros(x, y);
+					}
+				}
+			}
+
+		// 2eme phase: on ne touche qu'aux négatifs: on les met en positifs.
+
+		cimg_forXY(image_numeros,x,y)
+			{
+				if (image_numeros(x, y) < 0) {
+					image_numeros(x, y) = -image_numeros(x, y);
+				}
+			}
+
+		// fin npass
+	}
+	bouche_trou();
+
+}
+
diff --git a/src/images/imageNumber.h b/src/images/imageNumber.h
new file mode 100644
index 0000000..1c82bbe
--- /dev/null
+++ b/src/images/imageNumber.h
@@ -0,0 +1,43 @@
+#ifndef IMAGENUMBER_H_
+#define IMAGENUMBER_H_
+
+#include "../detection.h"
+#include "imageCode.h"
+#include "imageDirect.h"
+#include "parameterNumber.h"
+
+//stocker les numéros de spots
+class imageNumber: public imageCode {
+public:
+	//imageNumber(const char * & filename): cimg_library::CImg<unsigned short int>(filename){};
+	imageNumber() {
+	}
+
+	~imageNumber() {
+	}
+
+	void compute_from_direct(const imageDirect & direct,
+			detection & my_detection, const parameterNumber & parameter_number);
+	void expand_areas_down_the_slope(const imageIntensity & gel_image,
+			int npass);
+	void save(const QString & filename);
+
+private:
+
+	void
+	fusion(int spot_number_1, int spot_number_2, detection & the_detection);
+	void set_numbers_with_detection(detection & the_detection);
+	void enlarge_and_fusion(detection & my_detection, int enlarge,
+			const imageDirect & direct);
+	void enlarge(int enlarge);
+	void bouche_trou();
+
+	void enlarge_burned_areas(const imageDirect & direct,
+			detection & the_detection);
+	void set_burned_zone_number(const imageDirect & direct, int number, int x,
+			int y, unsigned int & depth);
+
+	int neighbor_number(int x, int y) const;
+};
+
+#endif /*IMAGENUMBER_H_*/
diff --git a/src/images/imagePaths.cpp b/src/images/imagePaths.cpp
new file mode 100644
index 0000000..e3121c1
--- /dev/null
+++ b/src/images/imagePaths.cpp
@@ -0,0 +1,38 @@
+#include "imagePaths.h"
+
+#include <iostream>
+
+using namespace std;
+
+void imagePaths::save(const QString & filename) {
+	cimg_library::CImg<unsigned short int>::save(filename.toStdString().c_str());
+}
+
+void imagePaths::compute_from_direct_and_coule(const imageDirect & direct,
+		const imageCoule & coule) {
+	imagePaths & image_dest = *this;
+	image_dest.resize(direct.width(), direct.height(), 1, 1);
+	image_dest.fill(0);
+	// processing
+	//cout << "imagePaths::compute_from_direct_and_coule begin" << endl;
+	cimg_forXY(coule,x,y)
+		{
+			if (direct.is_burned_pixel(x, y)) {
+				image_dest(x, y) = _max_value;
+			} else {
+				int xx = x;
+				int yy = y;
+				while (coule(xx, yy) == 0) {
+					//while the bead has not reached the end
+					if (direct.move_to_spot_center(xx, yy)) {
+						//draw path
+						if (image_dest(xx, yy) != _max_value) {
+							image_dest(xx, yy) += 1;
+						}
+					}
+				}
+			}
+		}
+	//image_dest.save("/tmp/path.tiff");
+	//cout << "imagePaths::compute_from_direct_and_coule end" << endl;
+}
diff --git a/src/images/imagePaths.h b/src/images/imagePaths.h
new file mode 100644
index 0000000..07d9d02
--- /dev/null
+++ b/src/images/imagePaths.h
@@ -0,0 +1,29 @@
+#ifndef IMAGEPATHS_H_
+#define IMAGEPATHS_H_
+
+#include "imageDirect.h"
+#include "imageCoule.h"
+
+//Calcul des chemins pris par les beads
+class imagePaths: public cimg_library::CImg<unsigned short int> {
+public:
+	imagePaths(const char * & filename) :
+		cimg_library::CImg<unsigned short int>(filename) {
+	}
+
+	imagePaths() {
+	}
+
+	~imagePaths() {
+	}
+
+	void compute_from_direct_and_coule(const imageDirect & direct,
+			const imageCoule & coule);
+
+	void save(const QString & filename);
+
+private:
+	static const unsigned short int _max_value = 65535;
+};
+
+#endif /*IMAGEPATHS_H_*/
diff --git a/src/images/imageProb.cpp b/src/images/imageProb.cpp
new file mode 100644
index 0000000..ca682c1
--- /dev/null
+++ b/src/images/imageProb.cpp
@@ -0,0 +1,167 @@
+#include "imageProb.h"
+#include <iostream>
+#include <vector>
+using namespace std;
+
+class kernel_prob {
+public:
+
+	kernel_prob(float sx, float sy) {
+		generate(sx, sy);
+	}
+
+	~kernel_prob() {
+	}
+
+	void update(float sx, float sy) {
+		if ((sx != _sx) || (sy != _sy)) {
+			//cerr << "kernel_prob::update " << sx << " " << sy << endl;
+			generate(sx, sy);
+		}
+	}
+
+	void generate(float sx, float sy) {
+		_ncols = 51;
+		_nrows = 51;
+		_kern.resize(_ncols, vector<float> (_nrows, 0));
+		int nc0 = _ncols / 2;
+		int nr0 = _nrows / 2;
+		//int nc;
+		//int nr;
+		for (int nc = 0; nc < _ncols; nc++) {
+			for (int nr = 0; nr < _nrows; nr++) {
+				float fnc = nc - nc0;
+				float fnr = nr - nr0;
+				float valx = (fnc * fnc) / (2 * sx * sx);
+				float valy = (fnr * fnr) / (2 * sy * sy);
+				_kern[nc][nr] = std::exp(-valx - valy);
+			}
+		}
+		_sx = sx;
+		_sy = sy;
+	}
+
+	int _ncols;
+	int _nrows;
+	vector<vector<float> > _kern;
+
+private:
+	float _sx;
+	float _sy;
+};
+
+void imageProb::compute_from_image_intensity(const imageIntensity & coule,
+		const parameterProb & parameter_prob) {
+	imageProb & image_prob = *this;
+
+	float threshold = parameter_prob.get_threshold();
+	float sx = parameter_prob.get_sx();
+	float sy = parameter_prob.get_sy();
+	/*
+	 * Pour traiter les pixels des bords sans faire de cas particulier,
+	 * création d'une image prob plus grande que l'image d'origine.
+	 * 20 pixels de plus dans chaque dimension, elle dépasse de 10 dans
+	 * les 4 directions. Cette bordure est ensuite enlevée par crop en
+	 * fin de programme.
+	 */
+	unsigned int margin(50);
+	unsigned int half_margin(margin / 2);
+	image_prob.resize(coule.width() + margin, coule.height() + margin, 1, 1);
+	image_prob.fill(0);
+	//    image_dest.blur(3.0); attention c'est pas pareil, voir util convol
+	// generation du kernel
+	//21 devrait être aussi en parametre
+
+	kernel_prob kern(sx, sy);
+
+	// processing
+	cimg_forXY(coule,x,y)
+		{
+			float ratio = (float) y / (float) coule.height();
+			//the kernel might be updated depending on the position on the gel :
+			kern.update(parameter_prob.get_sx_with_gel_ratio_position(ratio),
+					parameter_prob.get_sy_with_gel_ratio_position(ratio));
+			if (coule(x, y) != _max_value) {
+				if (coule(x, y) > threshold) {
+					// printf(" %d %d %f\n", x, y, img1(x,y));
+					int x0 = x - (kern._ncols - 1) / 2;
+					int y0 = y - (kern._nrows - 1) / 2;
+					for (int nc = 0; nc < kern._ncols; nc++) {
+						for (int nr = 0; nr < kern._nrows; nr++) {
+							image_prob(x0 + nc + half_margin, y0 + nr
+									+ half_margin)
+									+= (unsigned short int) (kern._kern[nc][nr]
+											* coule(x, y));
+						}
+					}
+				}
+			} else {
+				image_prob(x + half_margin, y + half_margin) = _max_value;
+			}
+		}
+	image_prob.crop(half_margin, half_margin, coule.width() + half_margin,
+			coule.height() + half_margin, false);
+	cimg_for_borderXY(image_prob,x,y,1)
+			image_prob(x, y) = 0;
+}
+
+void imageProb::mark_burned_pixels(imageCode & mark, int & cx, int & cy) const {
+	//cerr << "imageProb::mark_burned_pixels begin" << endl;
+	//cerr << "cx "<< cx << "cy " << cy << endl;
+	int xmin(cx), xmax(cx), ymin(cy), ymax(cy);
+	unsigned int depth(0);
+	rec_mark_burned_pixels(mark, cx, cy, xmin, xmax, ymin, ymax, depth);
+
+	cx = (xmin + xmax) / 2;
+	cy = (ymin + ymax) / 2;
+	//cerr << "imageProb::mark_burned_pixels end" << endl;
+	//cerr << "cx "<< cx << "cy " << cy << endl;
+}
+
+void imageProb::rec_mark_burned_pixels(imageCode & mark, int x, int y,
+		int & xmin, int & xmax, int & ymin, int & ymax, unsigned int & depth) const {
+	//cerr << "imageProb::rec_mark_burned_pixels begin " << x << " " << y << endl;
+	if ((x >= width()) || (y >= height()) || (x < 0) || (y < 0)) {
+		return;
+	}
+	depth++;
+	if (depth > 1000) {
+		depth--;
+		return;
+	}
+	const imageProb & image_prob = *this;
+	if (mark(x, y) == 0) {
+		if (image_prob(x, y) == imageProb::_max_value) {
+			mark(x, y) = 1;
+			if (x < xmin)
+				xmin = x;
+			if (y < ymin)
+				ymin = y;
+			if (x > xmax)
+				xmax = x;
+			if (y > ymax)
+				ymax = y;
+
+			rec_mark_burned_pixels(mark, x + 1, y, xmin, xmax, ymin, ymax,
+					depth);
+			rec_mark_burned_pixels(mark, x, y + 1, xmin, xmax, ymin, ymax,
+					depth);
+			rec_mark_burned_pixels(mark, x - 1, y, xmin, xmax, ymin, ymax,
+					depth);
+			rec_mark_burned_pixels(mark, x, y - 1, xmin, xmax, ymin, ymax,
+					depth);
+			rec_mark_burned_pixels(mark, x - 1, y + 1, xmin, xmax, ymin, ymax,
+					depth);
+			rec_mark_burned_pixels(mark, x + 1, y + 1, xmin, xmax, ymin, ymax,
+					depth);
+			rec_mark_burned_pixels(mark, x + 1, y - 1, xmin, xmax, ymin, ymax,
+					depth);
+			rec_mark_burned_pixels(mark, x - 1, y - 1, xmin, xmax, ymin, ymax,
+					depth);
+		}
+	}
+	depth--;
+	return;
+	//cerr << "imageProb::rec_mark_burned_pixels end" << endl;
+}
+
diff --git a/src/images/imageProb.h b/src/images/imageProb.h
new file mode 100644
index 0000000..faa18ea
--- /dev/null
+++ b/src/images/imageProb.h
@@ -0,0 +1,34 @@
+#ifndef IMAGEPROB_H_
+#define IMAGEPROB_H_
+
+#include "imageIntensity.h"
+#include "parameterProb.h"
+
+/** stores probabilities about spot positions
+ * 
+ * 
+ */
+
+class imageProb: public imageIntensity {
+public:
+	imageProb(const char * & filename) :
+		imageIntensity(filename) {
+	}
+
+	imageProb() {
+	}
+
+	~imageProb() {
+	}
+
+	void compute_from_image_intensity(const imageIntensity & coule,
+			const parameterProb & parameter_prob);
+
+	void mark_burned_pixels(imageCode & mark, int & x, int & y) const;
+
+private:
+	void rec_mark_burned_pixels(imageCode & mark, int x, int y, int & xmin,
+			int & xmax, int & ymin, int & ymax, unsigned int & depth) const;
+};
+
+#endif /*IMAGEPROB_H_*/
diff --git a/src/images/parameterConfluent.cpp b/src/images/parameterConfluent.cpp
new file mode 100644
index 0000000..2a6db56
--- /dev/null
+++ b/src/images/parameterConfluent.cpp
@@ -0,0 +1,18 @@
+#include "parameterConfluent.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+using namespace std;
+
+parameterConfluent::parameterConfluent() {
+	//default parameters:
+	_minflux = 100;
+	_minpath = 2;
+	_winconfl = 300;
+	_minbeads = 200;
+	_intmax = 50000;
+	_minpct = 20;
+}
+
+
diff --git a/src/images/parameterConfluent.h b/src/images/parameterConfluent.h
new file mode 100644
index 0000000..e66adf1
--- /dev/null
+++ b/src/images/parameterConfluent.h
@@ -0,0 +1,72 @@
+#ifndef PARAMETERCONFLUENT_H_
+#define PARAMETERCONFLUENT_H_
+
+/** \brief handle parameters for imageConfluent
+ *
+ */
+
+class parameterConfluent {
+public:
+	parameterConfluent();
+
+	~parameterConfluent() {
+	}
+
+	void set_minflux(int flux) {
+		_minflux = flux;
+	}
+
+	void set_minpath(int flux) {
+		_minpath = flux;
+	}
+
+	void set_winconfl(int flux) {
+		_winconfl = flux;
+	}
+
+	void set_minbeads(int flux) {
+		_minbeads = flux;
+	}
+
+	void set_intmax(int flux) {
+		_intmax = flux;
+	}
+
+	void set_minpct(int flux) {
+		_minpct = flux;
+	}
+
+	const int get_minflux() const {
+		return (_minflux);
+	}
+
+	const int get_minpath() const {
+		return (_minpath);
+	}
+
+	const int get_winconfl() const {
+		return (_winconfl);
+	}
+
+	const int get_minbeads() const {
+		return (_minbeads);
+	}
+
+	const int get_intmax() const {
+		return (_intmax);
+	}
+
+	const int get_minpct() const {
+		return (_minpct);
+	}
+
+private:
+	int _minflux;
+	int _minpath;
+	int _winconfl;
+	int _minbeads;
+	int _intmax;
+	int _minpct;
+};
+
+#endif /*PARAMETERCONFLUENT_H_*/
diff --git a/src/images/parameterDirect.h b/src/images/parameterDirect.h
new file mode 100644
index 0000000..679fed6
--- /dev/null
+++ b/src/images/parameterDirect.h
@@ -0,0 +1,30 @@
+#ifndef PARAMETERDIRECT_H_
+#define PARAMETERDIRECT_H_
+
+/** \brief handle parameters for parameterDirect
+ *
+ */
+
+class parameterDirect {
+public:
+	parameterDirect() {
+		//default parameters for Ag gel images:
+		_burned_pixel_threshold = 63000;
+	};
+	~parameterDirect() {
+	};
+
+	//bool open ( const char * filename );
+	void set_burned_pixel_threshold(unsigned short int threshold) {
+		_burned_pixel_threshold = threshold;
+	};
+
+	const unsigned short int get_burned_pixel_threshold() const {
+		return(_burned_pixel_threshold);
+	};
+
+private:
+	unsigned short int _burned_pixel_threshold;
+};
+
+#endif /*PARAMETERDIRECT_H_*/
diff --git a/src/images/parameterNumber.cpp b/src/images/parameterNumber.cpp
new file mode 100644
index 0000000..4ff5dc7
--- /dev/null
+++ b/src/images/parameterNumber.cpp
@@ -0,0 +1,13 @@
+#include "parameterNumber.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+using namespace std;
+
+parameterNumber::parameterNumber() {
+	//default parameters for Ag gel images:
+	_enlarge = 3;
+	_enlarge_fusion = 3;
+	_npass = 3;
+}
diff --git a/src/images/parameterNumber.h b/src/images/parameterNumber.h
new file mode 100644
index 0000000..d7c75d0
--- /dev/null
+++ b/src/images/parameterNumber.h
@@ -0,0 +1,45 @@
+#ifndef PARAMETERNUMBER_H_
+#define PARAMETERNUMBER_H_
+
+/** \brief handle parameters for imageNumber
+ * quantification parameters
+ */
+
+class parameterNumber {
+public:
+	parameterNumber();
+
+	~parameterNumber() {
+	}
+
+	void set_enlarge(int flux) {
+		_enlarge = flux;
+	}
+
+	void set_enlarge_fusion(int flux) {
+		_enlarge_fusion = flux;
+	}
+
+	void set_npass(int flux) {
+		_npass = flux;
+	}
+
+	const int get_enlarge() const {
+		return (_enlarge);
+	}
+
+	const int get_enlarge_fusion() const {
+		return (_enlarge_fusion);
+	}
+
+	const int get_npass() const {
+		return (_npass);
+	}
+
+private:
+	int _enlarge;
+	int _enlarge_fusion;
+	int _npass;
+};
+
+#endif /*PARAMETERNUMBER_H_*/
diff --git a/src/images/parameterProb.cpp b/src/images/parameterProb.cpp
new file mode 100644
index 0000000..4583a15
--- /dev/null
+++ b/src/images/parameterProb.cpp
@@ -0,0 +1,49 @@
+#include "parameterProb.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+using namespace std;
+
+parameterProb::parameterProb() {
+	//default parameters for Ag gel images:
+	_threshold = 10;
+	_sx = 3;
+	_sy = 3;
+	_sx_bottom = 0;
+	_sy_bottom = 0;
+}
+
+/** \brief give a sx depending on the gel ratio position
+ * 
+ * the gel is divided in "nslices" zones that have different sx
+ * 
+ * \param float ratio_position betwee 0 and 1 => 0.5 is half of the gel
+ */
+const float parameterProb::get_sx_with_gel_ratio_position(float ratio_position) const {
+	//cerr << "parameterProb::get_sx_with_gel_ratio_position " << ratio_position << " " << _sx_bottom << endl;
+	float result = _sx;
+	if (_sx_bottom == 0)
+		return (result);
+
+	float difference(_sx_bottom - _sx);
+	unsigned int nslices(10);
+	float step_size(difference / ((float) (nslices - 1)));
+	unsigned int step((unsigned int) (ratio_position * ((float) nslices)));
+	result = result + (step * step_size);
+	return (result);
+}
+
+const float parameterProb::get_sy_with_gel_ratio_position(float ratio_position) const {
+	float result = _sy;
+	if (_sy_bottom == 0)
+		return (result);
+
+	float difference(_sy_bottom - _sy);
+	unsigned int nslices(10);
+	float step_size(difference / (nslices - 1));
+	unsigned int step((unsigned int) (ratio_position * ((float) nslices)));
+	result = result + (step * step_size);
+	return (result);
+}
+
diff --git a/src/images/parameterProb.h b/src/images/parameterProb.h
new file mode 100644
index 0000000..9194832
--- /dev/null
+++ b/src/images/parameterProb.h
@@ -0,0 +1,66 @@
+#ifndef PARAMETERPROB_H_
+#define PARAMETERPROB_H_
+
+/** \brief handle parameters for imageProb
+ *
+ */
+
+class parameterProb {
+public:
+	parameterProb();
+	~parameterProb() {
+	}
+
+	void set_threshold(float flux) {
+		_threshold = flux;
+	}
+
+	void set_sx(float flux) {
+		_sx = flux;
+	}
+
+	void set_sy(float flux) {
+		_sy = flux;
+	}
+
+	void set_sx_bottom(float flux) {
+		_sx_bottom = flux;
+	}
+
+	void set_sy_bottom(float flux) {
+		_sy_bottom = flux;
+	}
+
+	const float get_threshold() const {
+		return (_threshold);
+	}
+
+	const float get_sx() const {
+		return (_sx);
+	}
+
+	const float get_sy() const {
+		return (_sy);
+	}
+
+	const float get_sx_bottom() const {
+		return (_sx_bottom);
+	}
+
+	const float get_sy_bottom() const {
+		return (_sy_bottom);
+	}
+
+	const float get_sx_with_gel_ratio_position(float ratio_position) const;
+	const float get_sy_with_gel_ratio_position(float ratio_position) const;
+
+private:
+	float _threshold;
+	float _sx;
+	float _sy;
+
+	float _sx_bottom;
+	float _sy_bottom;
+};
+
+#endif /*PARAMETERPROB_H_*/
diff --git a/src/parameterDetection.cpp b/src/parameterDetection.cpp
new file mode 100644
index 0000000..06ce197
--- /dev/null
+++ b/src/parameterDetection.cpp
@@ -0,0 +1,12 @@
+#include "parameterDetection.h"
+
+#include <iostream>
+#include <fstream>
+#include <string>
+using namespace std;
+
+parameterDetection::parameterDetection() {
+	//default parameters for Ag gel images:
+	_minproba = 400;
+}
+;
diff --git a/src/parameterDetection.h b/src/parameterDetection.h
new file mode 100644
index 0000000..b8d8378
--- /dev/null
+++ b/src/parameterDetection.h
@@ -0,0 +1,27 @@
+#ifndef PARAMETERDETECTION_H_
+#define PARAMETERDETECTION_H_
+
+/** \brief handle parameters for detection
+ *
+ */
+
+class parameterDetection {
+public:
+	parameterDetection();
+	~parameterDetection() {
+	}
+
+	void set_minproba(float threshold) {
+		_minproba = threshold;
+	}
+
+	const float get_minproba() const {
+		return (_minproba);
+	}
+
+private:
+	float _minproba;
+
+};
+
+#endif /*PARAMETERDETECTION_H_*/
diff --git a/src/parameters.cpp b/src/parameters.cpp
new file mode 100644
index 0000000..cefcbfd
--- /dev/null
+++ b/src/parameters.cpp
@@ -0,0 +1,62 @@
+#include "parameters.h"
+
+#include <string>
+using namespace std;
+
+parameters::parameters(const string config_filename) :
+	ConfigFile(config_filename.c_str()) {
+}
+
+const parameterDetection & parameters::get_parameter_detection() {
+	_parameter_detection.set_minproba(read("minproba",
+			_parameter_detection.get_minproba()));
+	return (_parameter_detection);
+}
+
+const parameterDirect & parameters::get_parameter_direction() {
+	_parameter_direction.set_burned_pixel_threshold(read(
+			"burned_pixel_threshold",
+			_parameter_direction.get_burned_pixel_threshold()));
+	return (_parameter_direction);
+}
+
+const parameterConfluent & parameters::get_parameter_confluent() {
+	_parameter_confluent.set_minflux(read("minflux",
+			_parameter_confluent.get_minflux()));
+	_parameter_confluent.set_minpath(read("minpath",
+			_parameter_confluent.get_minpath()));
+	_parameter_confluent.set_winconfl(read("winconfl",
+			_parameter_confluent.get_winconfl()));
+	_parameter_confluent.set_minbeads(read("minbeads",
+			_parameter_confluent.get_minbeads()));
+	_parameter_confluent.set_intmax(read("confluent_intmax",
+			_parameter_confluent.get_intmax()));
+	_parameter_confluent.set_minpct(read("confluent_minpct",
+			_parameter_confluent.get_minpct()));
+	return (_parameter_confluent);
+}
+
+const parameterNumber & parameters::get_parameter_number() {
+
+	_parameter_number.set_enlarge(read("quantification_enlarge",
+			_parameter_number.get_enlarge()));
+	_parameter_number.set_enlarge_fusion(read("quantification_enlarge_fusion",
+			_parameter_number.get_enlarge_fusion()));
+	_parameter_number.set_npass(read("npass",
+			_parameter_number.get_npass()));
+	return (_parameter_number);
+}
+
+const parameterProb & parameters::get_parameter_prob() {
+
+	_parameter_prob.set_threshold(read("prob_threshold",
+			_parameter_prob.get_threshold()));
+	_parameter_prob.set_sx(read("sx", _parameter_prob.get_sx()));
+	_parameter_prob.set_sy(read("sy", _parameter_prob.get_sy()));
+	_parameter_prob.set_sx_bottom(read("sx_bottom",
+			_parameter_prob.get_sx_bottom()));
+	_parameter_prob.set_sy_bottom(read("sy_bottom",
+			_parameter_prob.get_sy_bottom()));
+	return (_parameter_prob);
+}
+
diff --git a/src/parameters.h b/src/parameters.h
new file mode 100644
index 0000000..f448369
--- /dev/null
+++ b/src/parameters.h
@@ -0,0 +1,36 @@
+#ifndef PARAMETERS_H_
+#define PARAMETERS_H_
+
+#include "ConfigFile/ConfigFile.h"
+#include "parameterDetection.h"
+#include "images/parameterDirect.h"
+#include "images/parameterConfluent.h"
+#include "images/parameterNumber.h"
+#include "images/parameterProb.h"
+
+class parameters : public ConfigFile {
+public:
+	parameters(const string config_filename);
+	~parameters(){};
+
+	const parameterDetection & get_parameter_detection();
+	
+	const parameterDirect & get_parameter_direction();
+
+	const parameterConfluent & get_parameter_confluent();
+
+	const parameterNumber & get_parameter_number();
+
+	const parameterProb & get_parameter_prob();
+
+private:
+
+	parameterDetection _parameter_detection;
+	parameterDirect _parameter_direction;
+	parameterConfluent _parameter_confluent;
+	parameterNumber _parameter_number;
+	parameterProb _parameter_prob;
+	
+};
+
+#endif /*PARAMETERS_H_*/
diff --git a/src/qtbeads/CMakeLists.txt b/src/qtbeads/CMakeLists.txt
new file mode 100644
index 0000000..c5c7b1b
--- /dev/null
+++ b/src/qtbeads/CMakeLists.txt
@@ -0,0 +1,124 @@
+
+ # this command finds Qt4 libraries and sets all required variables
+  # note that it's Qt4, not QT4 or qt4
+FIND_PACKAGE( Qt4 REQUIRED )
+
+  
+  # add some useful macros and variables
+  # (QT_USE_FILE is a variable defined by FIND_PACKAGE( Qt4 ) that contains a path to CMake script)
+INCLUDE( ${QT_USE_FILE} )
+
+SET( BEADS_FOR_QT_CPP
+	../ConfigFile/ConfigFile
+	../parameters
+	../encode/base64
+	../images/imageIntensity.cpp
+	../images/imageDirect.cpp
+	../images/imageCode
+	../images/imageCoule
+	../images/imagePaths
+	../images/imageConfluent
+	../images/imageProb
+	../images/parameterProb
+	../parameterDetection
+	../images/parameterConfluent
+	../detection
+	../images/imageDetection
+	../images/imageNumber
+	../images/parameterNumber
+	../images/imageContours
+	../spotPROTICdbDocument
+	../spot
+	../spotDocument
+	../spot_document_gnumeric
+	../spotSvgDocument
+	../images/imageDeNovo
+	../CImg
+)
+  
+# with SET() command you can change variables or define new ones
+  # here we define SAMPLE_SRCS variable that contains a list of all .cpp files
+  # note that we don't need \ at the end of line
+SET( QTBEADS_SRCS
+	qtbeads.cpp
+	main_window.cpp
+	properties.cpp
+	beads_results.cpp
+	QCimg.cpp
+	q_gel_image.cpp
+	q_gel_image_scroll.cpp
+#	qtbeads_error.h
+)
+  
+  # another list, this time it includes all header files that should be treated with moc
+SET( QTBEADS_MOC_HDRS
+	main_window.h
+	q_gel_image.h
+)  
+  # some .ui files
+  #SET( SAMPLE_UIS
+  #     ./src/ui/Dialog1.ui
+  #     ./src/ui/Dialog2.ui
+  #)
+  
+  # and finally an resource file
+  #SET( SAMPLE_RCS
+  #     ./src/rc/sample.qrc
+  #)
+  
+  # enable warnings
+ADD_DEFINITIONS( -Wall )
+  
+  # by default only QtCore and QtGui modules are enabled
+  # other modules must be enabled like this:
+ #SET( QT_USE_QT3SUPPORT TRUE )   
+ # SET( QT_USE_QTXML TRUE )
+    
+  
+#****************************
+#I18N
+# Translation files
+SET(GLOB TRANS translations/*.ts)
+# add translations ...
+QT4_ADD_TRANSLATION(QM ${TRANS})
+
+
+
+  
+#set(CMAKE_MODULE_PATH ${abiftake_SOURCE_DIR}/Modules)
+#set(Qwt5_DIR ${abiftake_SOURCE_DIR}/Modules)
+
+#FIND_PACKAGE(Qwt5 REQUIRED) 
+  
+  
+  
+  # this command will generate rules that will run rcc on all files from SAMPLE_RCS
+  # in result SAMPLE_RC_SRCS variable will contain paths to files produced by rcc
+QT4_ADD_RESOURCES( QTBEADS_SRCS ${SAMPLE_RCS} )
+  
+  # this will run uic on .ui files:
+  #QT4_WRAP_UI( SAMPLE_UI_HDRS ${SAMPLE_UIS} )
+  
+  # and finally this will run moc:
+QT4_WRAP_CPP( QTBEADS_MOC_SRCS ${QTBEADS_MOC_HDRS} )
+  
+  # we need this to be able to include headers produced by uic in our code
+  # (CMAKE_BINARY_DIR holds a path to the build directory, while INCLUDE_DIRECTORIES() works just like INCLUDEPATH from qmake)
+  #INCLUDE_DIRECTORIES( ${CMAKE_BINARY_DIR} ${Qwt5_INCLUDE_DIR})
+INCLUDE_DIRECTORIES( ${CMAKE_BINARY_DIR})
+  
+  # here we instruct CMake to build "sample" executable from all of the source files
+ADD_EXECUTABLE( qtbeads ${BEADS_FOR_QT_CPP} ${QTBEADS_SRCS} ${QTBEADS_MOC_SRCS} ${SAMPLE_RC_SRCS} ${SAMPLE_UI_HDRS} ${QM} )
+
+
+  # last thing we have to do is to tell CMake what libraries our executable needs,
+  # luckily FIND_PACKAGE prepared QT_LIBRARIES variable for us:
+  #TARGET_LINK_LIBRARIES( abiftake ${QT_LIBRARIES} ${Qwt5_Qt4_LIBRARY} ${EXTRA_CIMG_LIBRARY})
+
+IF(WIN32)
+	TARGET_LINK_LIBRARIES( qtbeads ${QT_LIBRARIES} ${EXTRA_CIMG_LIBRARY})
+ELSE(WIN32)
+	TARGET_LINK_LIBRARIES( qtbeads ${QT_LIBRARIES} m ${PTHREADS_LIBRARY} ${EXTRA_CIMG_LIBRARY})
+ENDIF(WIN32)
+
+  
diff --git a/src/qtbeads/QCimg.cpp b/src/qtbeads/QCimg.cpp
new file mode 100644
index 0000000..adeda9d
--- /dev/null
+++ b/src/qtbeads/QCimg.cpp
@@ -0,0 +1,52 @@
+/*
+ * QCimg.cpp
+ *
+ *  Created on: 27 nov. 2009
+ *      Author: olivier
+ */
+
+#include "QCimg.h"
+#include <QTemporaryFile>
+#include <iostream>
+
+QCimg::QCimg(const imageIntensity & cimg_image) :
+	QImage(cimg_image.width(), cimg_image.height(), QImage::Format_RGB32) {
+
+	qDebug()
+				<< "QCimg::QCimg(const imageIntensity & cimg_image) begin ";
+
+	//bool QImage::loadFromData ( const QByteArray & data, const char * format = 0 )
+	/*	const unsigned short int * p_buffer = cimg.data;
+	 unsigned int size = cimg.size();
+
+	 loadFromData ( (uchar *) p_buffer, size);*/
+
+	QImage & image = *this;
+	QRgb value;
+	float ratio = (float) 255 / (float) imageIntensity::_max_value;
+
+	cimg_forXY(cimg_image,x,y)
+		{
+			unsigned int ivalue = (cimg_image(x, y) * ratio);
+			value = qRgb(ivalue, ivalue, ivalue);
+			image.setPixel(x, y, value);
+		}
+
+	/*
+	 QTemporaryFile tempfile;
+	 tempfile.open ();
+	 std::cout << tempfile.fileName ().toStdString() << endl;
+	 //cimg.save(tempfile.fileName ().toStdString().c_str());
+	 cimg.save_pnm(tempfile.fileName ().toStdString().c_str());
+	 load (tempfile.fileName (), "PPM");
+	 int i;
+	 cin >> i;
+	 */
+
+	qDebug()
+				<< "QCimg::QCimg(const imageIntensity & cimg_image) end ";
+
+}
+
+QCimg::~QCimg() {
+}
diff --git a/src/qtbeads/QCimg.h b/src/qtbeads/QCimg.h
new file mode 100644
index 0000000..24696ab
--- /dev/null
+++ b/src/qtbeads/QCimg.h
@@ -0,0 +1,22 @@
+/*
+ * QCimg.h
+ *
+ *  Created on: 27 nov. 2009
+ *      Author: olivier
+ */
+
+#ifndef QCIMG_H_
+#define QCIMG_H_
+
+#include <QImage>
+#include "../images/imageIntensity.h"
+
+using namespace std;
+
+class QCimg: public QImage {
+public:
+	QCimg(const imageIntensity & cimg);
+	virtual ~QCimg();
+};
+
+#endif /* QCIMG_H_ */
diff --git a/src/qtbeads/beads_results.cpp b/src/qtbeads/beads_results.cpp
new file mode 100644
index 0000000..9dff799
--- /dev/null
+++ b/src/qtbeads/beads_results.cpp
@@ -0,0 +1,113 @@
+/*
+ * beads_results.cpp
+ *
+ *  Created on: 29 nov. 2009
+ *      Author: olivier
+ */
+
+#include "../spotPROTICdbDocument.h"
+#include "../spotSvgDocument.h"
+#include "../spot_document_gnumeric.h"
+
+#include "beads_results.h"
+#include "../images/imageContours.h"
+
+BeadsResults::BeadsResults(const Properties & initial_properties) :
+	_properties(initial_properties) {
+
+}
+
+BeadsResults::~BeadsResults() {
+}
+
+void BeadsResults::saveImageContour(const QFileInfo & filename) const {
+	imageContours contours;
+	contours.compute_from_gel_image_and_numeros(
+			*_properties.get_p_working_image(), *_p_number_image);
+	contours.draw_contours(*_p_detection, _properties.isInverse());
+	const imageIntensity * image_inversed = contours.new_image_inversed();
+
+	image_inversed->save(filename.filePath());
+	//QCimg image(*image_inversed);
+	delete (image_inversed);
+}
+
+void BeadsResults::saveProticDbMl(const QFileInfo & filename) const {
+	qDebug() << "BeadsResults::saveProticDbMl begin";
+	detection detection(*_p_detection);
+
+	qDebug()
+			<< "BeadsResults::saveProticDbMl   detection.quantify_spots_with_image begin";
+	detection.quantify_spots_with_image(*_properties.get_p_working_image(),
+			*_p_number_image);
+	qDebug()
+			<< "BeadsResults::saveProticDbMl   detection.quantify_spots_with_image end";
+
+	spotPROTICdbDocument proticdbml_file;
+	proticdbml_file.set_gel_image_file_name(
+			_properties.getGelImageFileInfo().filePath());
+	proticdbml_file.open(filename.filePath());
+	proticdbml_file.write_detection(detection);
+	proticdbml_file.close();
+	qDebug() << "BeadsResults::saveProticDbMl end";
+
+}
+
+void BeadsResults::saveText(const QFileInfo & filename) const {
+	detection detection(*_p_detection);
+
+	detection.quantify_spots_with_image(*_properties.get_p_working_image(),
+			*_p_number_image);
+
+	spotDocument fspot;
+	fspot.open(filename.filePath());
+	fspot.write_detection(detection);
+	fspot.close();
+}
+
+void BeadsResults::saveGnumeric(const QFileInfo & filename) const {
+	detection detection(*_p_detection);
+
+	detection.quantify_spots_with_image(*_properties.get_p_working_image(), *_p_number_image);
+
+	SpotDocumentGnumeric fspot;
+	fspot.open(filename.filePath());
+	fspot.write_detection(detection);
+	fspot.close();
+}
+
+void BeadsResults::saveSvg(const QFileInfo & filename) const {
+	qDebug() << "BeadsResults::saveSvg(const QFileInfo & filename) begin";
+	detection detection(*_p_detection);
+	detection.store_spot_edges(*_p_number_image);
+	spotSvgDocument svg_file;
+	svg_file.set_gel_image_file_name(
+			_properties.getGelImageFileInfo().filePath());
+	svg_file.set_css_class("detection", "fill:blue;fill-opacity:1;stroke:none");
+	svg_file.set_css_class("contour",
+			"stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:none");
+
+	qDebug() << "BeadsResults::saveSvg(const QFileInfo & filename) embed";
+	svg_file.embed_gel_image(_properties.getGelImageFileInfo().filePath());
+	//svg_file.embed_gel_image();
+
+	svg_file.open(filename.filePath());
+	svg_file.write_detection(detection, "detection");
+	svg_file.draw_spot_numbers(detection, "detection");
+
+	qDebug() << "BeadsResults::saveSvg(const QFileInfo & filename) quantify";
+	detection.quantify_spots_with_image(*_properties.get_p_working_image(), *_p_number_image);
+	svg_file.write_detection(detection, "quantif");
+	svg_file.draw_contours(detection, "contour");
+
+	qDebug() << "BeadsResults::saveSvg(const QFileInfo & filename) close";
+	svg_file.close();
+	qDebug() << "BeadsResults::saveSvg(const QFileInfo & filename) end";
+}
+
+int BeadsResults::getSpotNumber(int x, int y) const {
+	const imageNumber & image_numeros = *_p_number_image;
+	if (image_numeros(x, y) > 0)
+		return image_numeros(x, y);
+	return (-1);
+}
diff --git a/src/qtbeads/beads_results.h b/src/qtbeads/beads_results.h
new file mode 100644
index 0000000..8cd3910
--- /dev/null
+++ b/src/qtbeads/beads_results.h
@@ -0,0 +1,45 @@
+/*
+ * beads_results.h
+ *
+ *  Created on: 29 nov. 2009
+ *      Author: olivier
+ */
+
+#ifndef BEADS_RESULTS_H_
+#define BEADS_RESULTS_H_
+
+#include "properties.h"
+
+#include "../images/imageDetection.h"
+#include "../images/imageNumber.h"
+
+class BeadsResults {
+public:
+	BeadsResults(const Properties & initial_properties);
+	virtual ~BeadsResults();
+	void setDetection(detection * p_detection) {
+		_p_detection = p_detection;
+	}
+	void setNumberImage(const imageNumber * p_number_image) {
+		_p_number_image = p_number_image;
+	}
+
+	detection * getDetection() const {
+		return (_p_detection);
+	}
+
+	void saveImageContour(const QFileInfo & filename) const;
+	void saveProticDbMl(const QFileInfo & filename) const;
+	void saveText(const QFileInfo & filename) const;
+	void saveSvg(const QFileInfo & filename) const;
+	void saveGnumeric(const QFileInfo & filename) const;
+
+	int getSpotNumber(int x, int y) const;
+
+private:
+	const Properties _properties;
+	detection * _p_detection;
+	const imageNumber * _p_number_image;
+};
+
+#endif /* BEADS_RESULTS_H_ */
diff --git a/src/qtbeads/main_window.cpp b/src/qtbeads/main_window.cpp
new file mode 100644
index 0000000..c34e489
--- /dev/null
+++ b/src/qtbeads/main_window.cpp
@@ -0,0 +1,612 @@
+/*
+ * main_windows.cpp
+ *
+ *  Created on: 27 nov. 2009
+ *      Author: langella
+ */
+
+#include <QtGui>
+#include <iostream>
+#include <QMenu>
+#include <QMenuBar>
+#include <QFileDialog>
+#include "main_window.h"
+#include "QCimg.h"
+#include "../config.h"
+
+#include "../images/imageCoule.h"
+#include "../images/imageProb.h"
+#include "../images/imagePaths.h"
+#include "../images/imageDetection.h"
+#include "../images/imageConfluent.h"
+#include "../images/imageContours.h"
+#include "../images/imageDeNovo.h"
+#include "../parameters.h"
+
+bool copy_file(QString inputfile, QString outputfile) {
+	QFile input(inputfile);
+	if (!input.open(QIODevice::ReadOnly)) {
+		// error
+		return false;
+	}
+	QFile output(outputfile);
+	if (!output.open(QIODevice::WriteOnly)) {
+		// error
+		return false;
+	}
+	QTextStream inputstream(&input);
+	QTextStream outputstream(&output);
+	outputstream << inputstream.readAll();
+	output.close();
+	input.close();
+	return true;
+}
+
+MainWindow::MainWindow() {
+	_moving_gel_image = false;
+	QFileInfo beads_icon;
+	beads_icon.setFile("/usr/share/beads/beads_icon.svg");
+	if (beads_icon.exists()) {
+		setWindowIcon(QIcon(beads_icon.filePath()));
+	}
+	beads_icon.setFile("/usr/local/share/beads/beads_icon.svg");
+	if (beads_icon.exists()) {
+		setWindowIcon(QIcon(beads_icon.filePath()));
+	}
+
+	_p_results = NULL;
+	setMenuBar(menuBar());
+	setStatusBar(QMainWindow::statusBar());
+
+	scrollArea = new QGelImageScroll;
+	setCentralWidget(scrollArea);
+
+	positionLabel = new QLabel;
+	positionLabel->setText("0, 0");
+	statusBar()->addWidget(positionLabel);
+
+	createActions();
+	createMenus();
+
+	setWindowTitle(tr("Beads ").append(BEADS_VERSION));
+	resize(500, 400);
+
+connect(scrollArea->getGelImage(), SIGNAL(mouseMoved(QMouseEvent *)), this, SLOT(
+				moveOnImage(QMouseEvent *)));
+connect(scrollArea->getGelImage(), SIGNAL(doubleClicked(QMouseEvent *)), this, SLOT(
+				doubleClickOnImage(QMouseEvent *)));
+//connect(imageLabel, SIGNAL(moveGelImage(QMouseEvent *)), this, SLOT(
+//				moveGelImage(QMouseEvent *)));
+
+}
+
+MainWindow::~MainWindow() {
+}
+
+void MainWindow::moveOnImage(QMouseEvent * event) {
+	int x(scrollArea->getGelImage()->getX(event->x()));
+	int y(scrollArea->getGelImage()->getY(event->y()));
+	_before_x = x;
+	_before_y = y;
+
+	QString message(QString::number(x) + ", " + QString::number(y));
+
+	if ((_p_results != NULL) && (_p_results->getSpotNumber(x, y) != -1)) {
+		int spot_num(_p_results->getSpotNumber(x, y));
+		message += " spot : " + QString::number(spot_num);
+
+		if (_p_results->getDetection() != NULL) {
+			QString
+					picked_num(
+							_p_results->getDetection()->find(spot_num)->second.get_picked_num());
+			if (picked_num != "") {
+				message += " picked num : " + picked_num;
+			}
+		}
+	}
+	positionLabel->setText(message);
+
+}
+
+void MainWindow::doubleClickOnImage(QMouseEvent * event) {
+	int x(scrollArea->getGelImage()->getX(event->x()));
+	int y(scrollArea->getGelImage()->getY(event->y()));
+
+	if ((_p_results != NULL) && (_p_results->getSpotNumber(x, y) != -1)) {
+		int spot_num(_p_results->getSpotNumber(x, y));
+		bool ok(false);
+		QString
+				old_picked_num(
+						_p_results->getDetection()->find(spot_num)->second.get_picked_num());
+
+		QString picked_num = QInputDialog::getText(this, tr(
+				"edit spot %1 picked num").arg(spot_num),
+				tr("spot picked num:"), QLineEdit::Normal, old_picked_num, &ok);
+		if (ok) {
+			_p_results->getDetection()->find(spot_num)->second.set_picked_num(
+					picked_num);
+		}
+	}
+
+}
+
+void MainWindow::moveGelImage(QMouseEvent * event) {
+
+	if (_moving_gel_image) {
+
+	} else {
+		_moving_gel_image = true;
+		int dx(event->x() - _before_x);
+		int dy(event->y() - _before_y);
+
+		_before_x = event->x();
+		_before_y = event->y();
+
+		qDebug() << event->x() << " " << event->y() << endl;
+		//scrollArea->widget()->move(event->x(), event->y());
+		scrollArea->horizontalScrollBar()->setSliderPosition(
+				scrollArea->horizontalScrollBar()->value() - dx);
+		scrollArea->verticalScrollBar()->setSliderPosition(
+				scrollArea->verticalScrollBar()->value() - dy);
+		_moving_gel_image = false;
+	}
+
+}
+
+void MainWindow::openFile() {
+	QString fileName = QFileDialog::getOpenFileName(this, tr(
+			"Open qtbeads file"), QDir::currentPath());
+	if (!fileName.isEmpty()) {
+		QImage image(fileName);
+		if (image.isNull()) {
+			QMessageBox::information(this, tr("qtbeads"),
+					tr("Cannot load %1.").arg(fileName));
+			return;
+		}
+		this->setCursor(Qt::BusyCursor);
+		_properties.setGelImageFilename(fileName);
+		scrollArea->setGelImage(image);
+
+		printAct->setEnabled(true);
+		fitToWindowAct->setEnabled(true);
+		runDetectionAct->setEnabled(true);
+		inverseAct->setEnabled(true);
+		updateActions();
+		this->setCursor(Qt::ArrowCursor);
+
+		if (!fitToWindowAct->isChecked())
+			scrollArea->adjustSize();
+	}
+}
+
+void MainWindow::openParamFile() {
+	QString fileName = QFileDialog::getOpenFileName(this, tr(
+			"Open qtbeads parameter file"), QDir::currentPath());
+	if (!fileName.isEmpty()) {
+		_properties.setParamFilename(fileName);
+	}
+}
+
+//saveAct
+
+void MainWindow::saveFile() {
+	QString
+			fileName =
+					QFileDialog::getSaveFileName(
+							this,
+							tr("Save File"),
+							QDir::currentPath().append("/untitled.png"),
+							tr(
+									"Images *.png *.jpg (*.png *.jpg);;Text *.txt (*.txt);;Gnumeric *.gnumeric (*.gnumeric);;PROTICdbML *.xml (*.xml);;Scalable Vector Graphic *.svg (*.svg)"));
+
+	if (!fileName.isEmpty()) {
+		this->setCursor(Qt::BusyCursor);
+		QFileInfo to_save(fileName);
+		if ((to_save.suffix() == "PNG") || (to_save.suffix() == "png")
+				|| (to_save.suffix() == "jpg") || (to_save.suffix() == "JPG")) {
+			_p_results->saveImageContour(to_save);
+		}
+		if ((to_save.suffix() == "XML") || (to_save.suffix() == "xml")) {
+			_p_results->saveProticDbMl(to_save);
+		}
+		if ((to_save.suffix() == "txt") || (to_save.suffix() == "TXT")) {
+			_p_results->saveText(to_save);
+		}
+		if ((to_save.suffix() == "svg") || (to_save.suffix() == "SVG")) {
+			_p_results->saveSvg(to_save);
+		}
+		if (to_save.suffix() == "gnumeric") {
+			_p_results->saveGnumeric(to_save);
+		}
+		this->setCursor(Qt::ArrowCursor);
+	}
+}
+
+void MainWindow::saveStepsDirectory() {
+
+	QFileDialog * choose_directory = new QFileDialog();
+	choose_directory->setFileMode(QFileDialog::Directory);
+
+	if (choose_directory->exec() == QDialog::Accepted) {
+		this->setCursor(Qt::BusyCursor);
+		QDir directory(choose_directory->selectedFiles().value(0));
+		qDebug() << "MainWindow::saveStepsDirectory() " << directory.path();
+		this->runDetection(directory);
+		this->setCursor(Qt::ArrowCursor);
+	}
+	delete (choose_directory);
+}
+
+void MainWindow::print() {
+	Q_ASSERT(scrollArea->getGelImage()->pixmap());
+	QPrintDialog dialog(&printer, this);
+	if (dialog.exec()) {
+		QPainter painter(&printer);
+		QRect rect = painter.viewport();
+		QSize size = scrollArea->getGelImage()->pixmap()->size();
+		size.scale(rect.size(), Qt::KeepAspectRatio);
+		painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
+		painter.setWindow(scrollArea->getGelImage()->pixmap()->rect());
+		painter.drawPixmap(0, 0, *scrollArea->getGelImage()->pixmap());
+	}
+}
+
+void MainWindow::zoomIn() {
+	scaleImage(1.25);
+}
+
+void MainWindow::zoomOut() {
+	scaleImage(0.8);
+}
+
+void MainWindow::normalSize() {
+	scrollArea->adjustSize();
+	scrollArea->setScaleFactor(1.0);
+
+}
+
+void MainWindow::fitToWindow() {
+	bool fitToWindow = fitToWindowAct->isChecked();
+	scrollArea->setWidgetResizable(fitToWindow);
+	if (!fitToWindow) {
+		normalSize();
+	}
+	updateActions();
+}
+
+void MainWindow::about() {
+	QMessageBox::about(
+			this,
+			tr("About QTbeads %1").arg(BEADS_VERSION),
+			tr(
+					"<div><a href=\"http://pappso.inra.fr/bioinfo/beads/index.php\">QTbeads</a> %1 is a 2-DE electrophoresis gel image spot detection software developped by <a href=\"http://pappso.inra.fr/\">PAPPSO</a></div><div>beads is freely available under the terms of the <a href=\"http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.html\">CeCILL</a> licence</div>").arg(
+					BEADS_VERSION));
+}
+
+void MainWindow::createActions() {
+
+	openAct = new QAction(tr("&Open..."), this);
+	openAct->setShortcut(tr("Ctrl+O"));
+	connect(openAct, SIGNAL(triggered()), this, SLOT(openFile()));
+
+	saveAct = new QAction(tr("&Save..."), this);
+	//openAct->setShortcut(tr("Ctrl+O"));
+	saveAct->setEnabled(false);
+	connect(saveAct, SIGNAL(triggered()), this, SLOT(saveFile()));
+
+	saveStepsAct = new QAction(tr("Save s&teps..."), this);
+	//openAct->setShortcut(tr("Ctrl+O"));
+	saveStepsAct->setEnabled(false);
+	connect(saveStepsAct, SIGNAL(triggered()), this, SLOT(saveStepsDirectory()));
+
+	printAct = new QAction(tr("&Print..."), this);
+	printAct->setShortcut(tr("Ctrl+P"));
+	printAct->setEnabled(false);
+	connect(printAct, SIGNAL(triggered()), this, SLOT(print()));
+
+	exitAct = new QAction(tr("E&xit"), this);
+	exitAct->setShortcut(tr("Ctrl+Q"));
+	connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
+
+	zoomInAct = new QAction(tr("Zoom &In (25%)"), this);
+	zoomInAct->setShortcut(tr("Ctrl++"));
+	zoomInAct->setEnabled(false);
+	connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));
+
+	zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this);
+	zoomOutAct->setShortcut(tr("Ctrl+-"));
+	zoomOutAct->setEnabled(false);
+	connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));
+
+	normalSizeAct = new QAction(tr("&Normal Size"), this);
+	normalSizeAct->setShortcut(tr("Ctrl+S"));
+	normalSizeAct->setEnabled(false);
+	connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize()));
+
+	fitToWindowAct = new QAction(tr("&Fit to Window"), this);
+	fitToWindowAct->setEnabled(false);
+	fitToWindowAct->setCheckable(true);
+	fitToWindowAct->setShortcut(tr("Ctrl+F"));
+	connect(fitToWindowAct, SIGNAL(triggered()), this, SLOT(fitToWindow()));
+
+	aboutAct = new QAction(tr("&About"), this);
+	connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
+
+	runDetectionAct = new QAction(tr("&Run"), this);
+	runDetectionAct->setEnabled(false);
+	connect(runDetectionAct, SIGNAL(triggered()), this, SLOT(runDetection()));
+
+	inverseAct = new QAction(tr("Inverse"), this);
+	inverseAct->setEnabled(false);
+	inverseAct->setCheckable(true);
+	inverseAct->setChecked(true);
+	inverseGelImage(true);
+	connect(inverseAct, SIGNAL(triggered(bool)), this, SLOT(inverseGelImage(bool)));
+
+	openParamFileAct = new QAction(tr("Parameters"), this);
+	connect(openParamFileAct, SIGNAL(triggered()), this, SLOT(openParamFile()));
+
+	//aboutQtAct = new QAction(tr("About &Qt"), this);
+	//connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
+}
+
+void MainWindow::createMenus() {
+	fileMenu = new QMenu(tr("&File"), this);
+	fileMenu->addAction(openAct);
+	fileMenu->addAction(saveAct);
+	fileMenu->addAction(saveStepsAct);
+	fileMenu->addAction(printAct);
+	fileMenu->addSeparator();
+	fileMenu->addAction(exitAct);
+
+	viewMenu = new QMenu(tr("&View"), this);
+	viewMenu->addAction(zoomInAct);
+	viewMenu->addAction(zoomOutAct);
+	viewMenu->addAction(normalSizeAct);
+	//viewMenu->addSeparator();
+	//viewMenu->addAction(fitToWindowAct);
+
+	detectionMenu = new QMenu(tr("&Detection"), this);
+
+	detectionMenu->addAction(openParamFileAct);
+	detectionMenu->addAction(inverseAct);
+	detectionMenu->addSeparator();
+	detectionMenu->addAction(runDetectionAct);
+
+	/*
+	 QMenu * menuDetection = new QMenu(tr("Detection"));
+	 menubar->addMenu(menuDetection);
+	 menuDetection->addAction(tr("Run"), this, SLOT(runDetection()));
+	 */
+
+	helpMenu = new QMenu(tr("&Help"), this);
+	helpMenu->addAction(aboutAct);
+	//helpMenu->addAction(aboutQtAct);
+
+	menuBar()->addMenu(fileMenu);
+	menuBar()->addMenu(viewMenu);
+	menuBar()->addMenu(detectionMenu);
+	menuBar()->addMenu(helpMenu);
+}
+
+void MainWindow::updateActions() {
+	zoomInAct->setEnabled(!fitToWindowAct->isChecked());
+	zoomOutAct->setEnabled(!fitToWindowAct->isChecked());
+	normalSizeAct->setEnabled(!fitToWindowAct->isChecked());
+}
+
+void MainWindow::scaleImage(double factor) {
+	Q_ASSERT(scrollArea->getGelImage()->pixmap());
+	scrollArea->scaleImage(factor);
+
+	adjustScrollBar(scrollArea->horizontalScrollBar(), factor);
+	adjustScrollBar(scrollArea->verticalScrollBar(), factor);
+
+	zoomInAct->setEnabled(scrollArea->getScaleFactor() < 3.0);
+	zoomOutAct->setEnabled(scrollArea->getScaleFactor() > 0.333);
+}
+
+void MainWindow::adjustScrollBar(QScrollBar *scrollBar, double factor) {
+	scrollBar->setValue(int(factor * scrollBar->value() + ((factor - 1)
+			* scrollBar->pageStep() / 2)));
+}
+
+void MainWindow::inverseGelImage(bool checked) {
+	qDebug() << "MainWindow::inverseGelImage (bool checked) begin " << checked;
+
+	_properties.setInverse(checked);
+
+	if (_p_results == NULL) {
+		if (_properties.gelImageIsLoaded()) {
+			const imageIntensity * p_gel_image = _properties.get_p_working_image()->new_image_inversed();
+			QCimg image(
+					*p_gel_image);
+			scrollArea->setGelImage(image);
+			delete p_gel_image;
+		}
+	}
+	qDebug() << "MainWindow::inverseGelImage (bool checked) end "
+			<< _properties.isInverse();
+}
+
+void MainWindow::runDetection() {
+	this->runDetection(QString(""));
+}
+
+void MainWindow::runDetection(QDir & directory_to_save_all_steps) {
+	this->runDetection(directory_to_save_all_steps.path());
+}
+
+void MainWindow::runDetection(QString directory_to_save_all_steps) {
+	qDebug()
+			<< "MainWindow::runDetection (QString directory_to_save_all_steps) begin"
+			<< directory_to_save_all_steps;
+	try {
+		qDebug() << "MainWindow::runDetection set cursor ";
+		this->setCursor(Qt::BusyCursor);
+		parameters beads_params(
+				_properties.getParamFileInfo().filePath().toStdString());
+		qDebug() << "Loading parameter file "
+				<< _properties.getParamFileInfo().fileName();
+
+		if (!directory_to_save_all_steps.isEmpty()) {
+			copy_file(_properties.getParamFileInfo().filePath(),
+					directory_to_save_all_steps + "/parameters.conf");
+		}
+
+		const imageIntensity * p_gel_image = _properties.get_p_working_image();
+		qDebug() << "MainWindow::runDetection p_gel_image->get_is_inversed() "
+				<< p_gel_image->get_is_inversed();
+		qDebug() << "MainWindow::runDetection _properties.isInverse() "
+				<< _properties.isInverse();
+
+		qDebug() << "Computing bead directions " << endl;
+		imageDirect directions;
+		const parameterDirect & parameter_direct =
+				beads_params.get_parameter_direction();
+		directions.compute_from_gel_image(*p_gel_image, parameter_direct);
+
+		if (!directory_to_save_all_steps.isEmpty()) {
+			directions.save(directory_to_save_all_steps + "/DIRECTIONS.png");
+		}
+
+		qDebug() << "Beads are rolling" << endl;
+		imageCoule rolling;
+		rolling.compute_from_direct(directions);
+		if (!directory_to_save_all_steps.isEmpty()) {
+			rolling.save(directory_to_save_all_steps + "/BEADS.png");
+		}
+
+		//using confluent method :
+		qDebug() << "Computing bead paths" << endl;
+		imagePaths path;
+		path.compute_from_direct_and_coule(directions, rolling);
+
+		if (!directory_to_save_all_steps.isEmpty()) {
+			path.save(directory_to_save_all_steps + "/PATHS.png");
+		}
+
+		qDebug() << "Detecting merging paths and arrival positions" << endl;
+		const parameterConfluent & parameter_confl =
+				beads_params.get_parameter_confluent();
+		qDebug() << " minflux : " << parameter_confl.get_minflux() << endl;
+		qDebug() << " minpath : " << parameter_confl.get_minpath() << endl;
+		qDebug() << " winconfl : " << parameter_confl.get_winconfl() << endl;
+		qDebug() << " minbeads : " << parameter_confl.get_minbeads() << endl;
+
+		imageConfluent confluent;
+		confluent.compute_from_direct_and_paths(directions, path, *p_gel_image,
+				parameter_confl);
+		if (!directory_to_save_all_steps.isEmpty()) {
+			confluent.save(directory_to_save_all_steps + "/SELECT.png");
+		}
+
+		qDebug() << "Computing probabilites of spot positions" << endl;
+		const parameterProb & parameter_prob =
+				beads_params.get_parameter_prob();
+		qDebug() << " threshold : " << parameter_prob.get_threshold() << endl;
+		qDebug() << " sx : " << parameter_prob.get_sx() << endl;
+		qDebug() << " sy : " << parameter_prob.get_sy() << endl;
+
+		imageProb dest_image_prob;
+
+		dest_image_prob.compute_from_image_intensity(confluent, parameter_prob);
+		if (!directory_to_save_all_steps.isEmpty()) {
+			dest_image_prob.save(directory_to_save_all_steps
+					+ "/PROBABILITIES.png");
+		}
+
+		qDebug() << "Detecting spots" << endl;
+		const parameterDetection & parameter_detection =
+				beads_params.get_parameter_detection();
+		qDebug() << " minproba : " << parameter_detection.get_minproba()
+				<< endl;
+
+		detection * p_the_detection_from_prob = new detection();
+		p_the_detection_from_prob->detect_from_prob(dest_image_prob,
+				parameter_detection);
+
+		//imageDetection img_detect(*p_the_detection_from_prob);
+
+		qDebug() << "Quantification" << endl;
+		const parameterNumber & parameter_number =
+				beads_params.get_parameter_number();
+
+		imageNumber * p_numbers = new imageNumber();
+		//quantification des spots:
+		p_numbers->compute_from_direct(directions, *p_the_detection_from_prob,
+				parameter_number);
+
+		int npass = parameter_number.get_npass();
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "image numbers expand_areas_down_the_slope before";
+
+		p_numbers->expand_areas_down_the_slope(*p_gel_image, npass);
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "image numbers expand_areas_down_the_slope after";
+
+		if (!directory_to_save_all_steps.isEmpty()) {
+			p_numbers->save(directory_to_save_all_steps + "/NUMBERS.png");
+		}
+
+		imageContours contours;
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "compute_from_gel_image_and_numeros before";
+		contours.compute_from_gel_image_and_numeros(*p_gel_image, *p_numbers);
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "compute_from_gel_image_and_numeros after";
+		contours.draw_contours(*p_the_detection_from_prob,
+				_properties.isInverse());
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "draw_contours finished";
+		const imageIntensity * image_inversed = contours.new_image_inversed();
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "new_image_inversed finished";
+
+		if (!directory_to_save_all_steps.isEmpty()) {
+			image_inversed->save(directory_to_save_all_steps + "/CONTOURS.png");
+		}
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "coucou";
+
+		if (directory_to_save_all_steps.isEmpty()) {
+			QCimg image(*image_inversed);
+			scrollArea->setGelImage(image);
+
+			if (_p_results != NULL) {
+				delete (_p_results);
+			}
+			_p_results = new BeadsResults(_properties);
+			//_p_results->setGelImage(p_gel_image);
+			_p_results->setDetection(p_the_detection_from_prob);
+			_p_results->setNumberImage(p_numbers);
+		}
+		qDebug()
+				<< "MainWindow::runDetection (QString directory_to_save_all_steps) "
+				<< "deleting (image_inversed)";
+		delete (image_inversed);
+
+		saveAct->setEnabled(true);
+		saveStepsAct->setEnabled(true);
+		this->setCursor(Qt::ArrowCursor);
+	} catch (qtbeadsError& e) {
+
+		qDebug() << "an error occured " << e.getMessage();
+		QMessageBox::critical(this, tr("qtbeads"),
+				tr("Detection aborted : ").append(e.getMessage()));
+		/*
+		 QMessageBox::about(
+		 this,
+		 tr("About QTbeads %1").arg(BEADS_VERSION),
+		 tr(
+		 "<div><a href=\"http://pappso.inra.fr/bioinfo/beads/index.php\">QTbeads</a> %1 is a 2-DE electrophoresis gel image spot detection software developped by <a href=\"http://pappso.inra.fr/\">PAPPSO</a></div><div>beads is freely available under the terms of the <a href=\"http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.html\">CeCILL</a> licence</div>").arg(
+		 BEADS_VERSION));*/
+	}
+}
diff --git a/src/qtbeads/main_window.h b/src/qtbeads/main_window.h
new file mode 100644
index 0000000..0536a89
--- /dev/null
+++ b/src/qtbeads/main_window.h
@@ -0,0 +1,98 @@
+/*
+ * main_windows.h
+ *
+ *  Created on: 27 nov. 2009
+ *      Author: langella
+ *
+ *      http://doc.trolltech.com/4.2/widgets-imageviewer.html
+ */
+
+#ifndef MAIN_WINDOWS_H_
+#define MAIN_WINDOWS_H_
+
+#include <QPrinter>
+#include <QMainWindow>
+#include <QScrollBar>
+
+#include "properties.h"
+#include "beads_results.h"
+#include "qtbeads_error.h"
+#include "q_gel_image_scroll.h"
+
+class QAction;
+class QLabel;
+class QMenu;
+class QScrollArea;
+//class QScrollBar;
+
+using namespace std;
+
+class MainWindow: public QMainWindow {
+Q_OBJECT
+public:
+	MainWindow();
+	virtual ~MainWindow();
+
+private slots :
+	void openFile();
+	void openParamFile();
+	void saveFile();
+	void saveStepsDirectory();
+	void runDetection();
+	void runDetection(QDir & directory_to_save_all_steps);
+	void inverseGelImage(bool checked);
+
+	void print();
+	void zoomIn();
+	void zoomOut();
+	void normalSize();
+	void fitToWindow();
+	void about();
+	void moveOnImage(QMouseEvent * event);
+	void doubleClickOnImage(QMouseEvent * event);
+	void moveGelImage(QMouseEvent * event);
+
+private:
+	void createActions();
+	void createMenus();
+	void updateActions();
+	void scaleImage(double factor);
+	void adjustScrollBar(QScrollBar *scrollBar, double factor);
+	void runDetection(QString directory_to_save_all_steps);
+
+	QLabel *positionLabel;
+	QGelImageScroll *scrollArea;
+
+	QPrinter printer;
+
+	QAction *openAct;
+	QAction *openParamFileAct;
+	QAction *saveAct;
+	QAction *saveStepsAct;
+	QAction *printAct;
+	QAction *exitAct;
+	QAction *zoomInAct;
+	QAction *zoomOutAct;
+	QAction *normalSizeAct;
+	QAction *fitToWindowAct;
+	QAction *aboutAct;
+	QAction *aboutQtAct;
+	QAction *runDetectionAct;
+	QAction *inverseAct;
+
+	QMenu *fileMenu;
+	QMenu *viewMenu;
+	QMenu *detectionMenu;
+	QMenu *helpMenu;
+
+	Properties _properties;
+	BeadsResults * _p_results;
+
+	int _before_x;
+	int _before_y;
+
+	bool _moving_gel_image;
+
+};
+
+#endif /* MAIN_WINDOWS_H_ */
diff --git a/src/qtbeads/properties.cpp b/src/qtbeads/properties.cpp
new file mode 100644
index 0000000..6dfc89c
--- /dev/null
+++ b/src/qtbeads/properties.cpp
@@ -0,0 +1,227 @@
+/*
+ * properties.cpp
+ *
+ *  Created on: 27 nov. 2009
+ *      Author: olivier
+ */
+
+#include "properties.h"
+#include "qtbeads_error.h"
+#include <QDebug>
+#include <string>
+#include <iostream>
+#include <fstream>
+
+void create_default_parameter_file(const string filename) {
+	ofstream param_stream;
+	param_stream.open(filename.c_str());
+	param_stream << "#" << filename << endl;
+	param_stream << "# beads configuration file" << endl;
+	param_stream << "# default parameters, optimized for silver staining"
+			<< endl;
+	param_stream << endl;
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for DIRECTIONS    #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "burned_pixel_threshold = 63000" << endl;
+	param_stream << endl;
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for SELECT        #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "minflux = 100" << endl;
+	param_stream << "minpath = 2" << endl;
+	param_stream << "winconfl = 300" << endl;
+	param_stream << "minbeads = 200" << endl;
+	param_stream << "confluent_intmax = 60000" << endl;
+	param_stream << "confluent_minpct = 1" << endl;
+	param_stream << endl;
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for PROBABILITIES #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "prob_threshold = 10" << endl;
+	param_stream << "sx = 3" << endl;
+	param_stream << "sy = 3" << endl;
+	param_stream << "sx_bottom = 5" << endl;
+	param_stream << "sy_bottom = 5" << endl;
+	param_stream << endl;
+
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for detections #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "minproba  = 400" << endl;
+	param_stream << endl;
+
+	param_stream << "################################" << endl;
+	param_stream << "# parameters for quantification #" << endl;
+	param_stream << "################################" << endl;
+	param_stream << endl;
+	param_stream << "quantification_enlarge_fusion = 3" << endl;
+	param_stream << "npass = 10" << endl;
+	param_stream << endl;
+	param_stream.close();
+}
+
+Properties::Properties() {
+	_p_param_file_info = NULL;
+	_p_original_gel_image_file_info = NULL;
+	_p_working_gel_image = NULL;
+	_inversed = false;
+
+	//create default parameter file :
+	QFileInfo * newfile = new QFileInfo("beads.conf");
+	if (newfile->exists()) {
+	} else {
+		create_default_parameter_file(newfile->filePath().toStdString());
+	}
+	if (newfile->exists()) {
+	} else {
+		throw qtbeadsError(
+				QObject::tr(
+						"unable to create default parameter file for beads in ").append(
+						newfile->filePath()));
+	}
+	_p_param_file_info = newfile;
+
+}
+
+Properties::Properties(const Properties & src) {
+	_p_param_file_info = NULL;
+	_p_original_gel_image_file_info = NULL;
+
+	qDebug() << "Properties::Properties(const Properties & src) begin ";
+	if (_p_param_file_info != NULL) {
+		qDebug()
+				<< "Properties::Properties(const Properties & src) delete (_p_param_file_info) "
+				<< _p_param_file_info;
+		delete (_p_param_file_info);
+	}
+	if (_p_original_gel_image_file_info != NULL) {
+		qDebug()
+				<< "Properties::Properties(const Properties & src) delete (_p_original_gel_image_file_info) "
+				<< _p_original_gel_image_file_info;
+		delete (_p_original_gel_image_file_info);
+	}
+	_p_param_file_info = new QFileInfo(src.getParamFileInfo());
+	_p_original_gel_image_file_info = new QFileInfo(src.getGelImageFileInfo());
+	_p_working_gel_image = new imageIntensity(*(src.get_p_working_image()));
+	_inversed = src.isInverse();
+
+	qDebug() << "Properties::Properties(const Properties & src) end ";
+
+}
+
+Properties::~Properties() {
+	if (_p_param_file_info != NULL) {
+		delete (_p_param_file_info);
+	}
+	if (_p_original_gel_image_file_info != NULL) {
+		delete (_p_original_gel_image_file_info);
+	}
+	_p_param_file_info = NULL;
+	_p_original_gel_image_file_info = NULL;
+	if (_p_working_gel_image == NULL) {
+		delete _p_working_gel_image;
+	}
+}
+
+const imageIntensity * Properties::get_p_working_image() const {
+
+	if (_p_working_gel_image == NULL) {
+		throw qtbeadsError(QObject::tr(
+						"no gel image loaded... please open a gel image first"));
+	}
+	return _p_working_gel_image;
+
+}
+
+const QFileInfo & Properties::getGelImageFileInfo() const {
+	qDebug() << "QFileInfo & Properties::getGelImageFileInfo() begin";
+	if (_p_original_gel_image_file_info == NULL) {
+		throw qtbeadsError(QObject::tr(
+				"no gel image loaded... please open a gel image first"));
+	}
+	qDebug() << "QFileInfo & Properties::getGelImageFileInfo() end";
+	return (*_p_original_gel_image_file_info);
+}
+
+const QFileInfo & Properties::getParamFileInfo() const {
+	if (_p_param_file_info == NULL) {
+		throw qtbeadsError(QObject::tr(
+				"no ParamFileInfo... please set the parameter file first"));
+		/*
+		 //create default parameter file :
+		 QFileInfo * newfile = new QFileInfo("beads.conf");
+		 _p_param_file_info = newfile;
+		 if (newfile->exists()) {
+		 } else {
+		 create_default_parameter_file(newfile->filePath().toStdString());
+		 }
+		 */
+	}
+	return (*_p_param_file_info);
+
+}
+
+void Properties::setGelImageFilename(const QString & filename) {
+	QFileInfo * newfile = new QFileInfo(filename);
+	if (newfile->exists()) {
+		if (_p_original_gel_image_file_info != NULL) {
+			delete _p_original_gel_image_file_info;
+			/*
+			throw qtbeadsError(
+					QObject::tr(
+							"A gel image file is already associated to these properties : ").append(
+							getGelImageFileInfo().filePath()));
+						*/
+		}
+		_p_original_gel_image_file_info = newfile;
+		if (_p_working_gel_image != NULL) {
+			delete _p_working_gel_image;
+		}
+		imageIntensity * p_gel_image_loaded = new imageIntensity(
+				getGelImageFileInfo().filePath());
+		if (isInverse()) {
+			qDebug() << "Properties::setGelImageFilename inverse " << isInverse() ;
+			//gel_image.image_inverse();
+			_p_working_gel_image = p_gel_image_loaded->new_image_inversed();
+			delete (p_gel_image_loaded);
+		} else {
+			qDebug() << "Properties::setGelImageFilename not inverse " << isInverse() ;
+			_p_working_gel_image = p_gel_image_loaded;
+		}
+
+	} else {
+		throw qtbeadsError(QObject::tr(
+				"This gel image filename does not exists : ").append(filename));
+	}
+}
+
+void Properties::setParamFilename(const QString & filename) {
+	QFileInfo * newfile = new QFileInfo(filename);
+	if (newfile->exists()) {
+		_p_param_file_info = newfile;
+	}
+}
+
+void Properties::setInverse(bool ok) {
+	_inversed = ok;
+
+	qDebug() << "Properties::setInverse " << ok ;
+	if (_p_working_gel_image != NULL) {
+		qDebug() << "Properties::setInverse _p_working_gel_image != NULL"  ;
+		if (isInverse() != _p_working_gel_image->get_is_inversed()) {
+			imageIntensity * new_working_image =
+					_p_working_gel_image->new_image_inversed();
+			delete (_p_working_gel_image);
+			_p_working_gel_image = new_working_image;
+		}
+		qDebug() << "Properties::setInverse _p_working_gel_image->inv " << _p_working_gel_image->get_is_inversed() ;
+	}
+	qDebug() << "Properties::setInverse end " ;
+
+}
+
diff --git a/src/qtbeads/properties.h b/src/qtbeads/properties.h
new file mode 100644
index 0000000..a4a7515
--- /dev/null
+++ b/src/qtbeads/properties.h
@@ -0,0 +1,48 @@
+/*
+ * properties.h
+ *
+ *  Created on: 27 nov. 2009
+ *      Author: olivier
+ */
+
+#ifndef PROPERTIES_H_
+#define PROPERTIES_H_
+
+#include <QString>
+#include <QFileInfo>
+#include "../images/imageIntensity.h"
+
+using namespace std;
+
+class Properties {
+public:
+	Properties();
+	virtual ~Properties();
+	Properties(const Properties & src);
+
+	const imageIntensity * get_p_working_image() const;
+	bool gelImageIsLoaded() const {
+		if (_p_working_gel_image == NULL) {
+			return false;
+		}
+		return true;
+	}
+
+
+	void setGelImageFilename(const QString & filename);
+	const QFileInfo & getGelImageFileInfo() const;
+	void setParamFilename(const QString & filename);
+	const QFileInfo & getParamFileInfo() const;
+	bool isInverse() const {
+		return (_inversed);
+	}
+	void setInverse(bool ok);
+
+private:
+	imageIntensity * _p_working_gel_image;
+	QFileInfo * _p_param_file_info;
+	QFileInfo * _p_original_gel_image_file_info;
+	bool _inversed;
+};
+
+#endif /* PROPERTIES_H_ */
diff --git a/src/qtbeads/q_gel_image.cpp b/src/qtbeads/q_gel_image.cpp
new file mode 100644
index 0000000..f700c7e
--- /dev/null
+++ b/src/qtbeads/q_gel_image.cpp
@@ -0,0 +1,66 @@
+/*
+ * q_gel_image.cpp
+ *
+ *  Created on: 11 déc. 2009
+ *      Author: olivier
+ */
+
+#include "q_gel_image.h"
+
+QGelImage::QGelImage() {
+	setMouseTracking(true);
+	_mousePressed = false;
+}
+
+QGelImage::~QGelImage() {
+}
+
+void QGelImage::mouseMoveEvent(QMouseEvent *event) // Fonction protected appartenant à QWidget
+{
+	if (_mousePressed) {
+		emit moveGelImage(event);
+	} else {
+		emit mouseMoved(event);
+	}
+}
+
+int QGelImage::getBeforeX() const {
+	return (_before_x);
+
+}
+int QGelImage::getBeforeY() const {
+	return (_before_y);
+
+}
+
+void QGelImage::mouseDoubleClickEvent(QMouseEvent *event) {
+	emit doubleClicked(event);
+}
+
+void QGelImage::mousePressEvent(QMouseEvent * ev) {
+	_mousePressed = true;
+}
+
+void QGelImage::mouseReleaseEvent(QMouseEvent * ev) {
+	_mousePressed = false;
+}
+
+void QGelImage::setQCimg(QCimg & image) {
+	this->setPixmap(QPixmap::fromImage(image));
+}
+
+void QGelImage::setScaleFactor(double scale_factor) {
+	_scaleFactor = scale_factor;
+}
+
+void QGelImage::scaleImage(double factor) {
+	_scaleFactor *= factor;
+	this->resize(_scaleFactor * this->pixmap()->size());
+}
+
+int QGelImage::getX(int real_x) const {
+	return ((1 / _scaleFactor) * real_x);
+}
+int QGelImage::getY(int real_y) const {
+	return ((1 / _scaleFactor) * real_y);
+}
diff --git a/src/qtbeads/q_gel_image.h b/src/qtbeads/q_gel_image.h
new file mode 100644
index 0000000..1dfbf2b
--- /dev/null
+++ b/src/qtbeads/q_gel_image.h
@@ -0,0 +1,52 @@
+/*
+ * q_gel_image.h
+ *
+ *  Created on: 11 déc. 2009
+ *      Author: olivier
+ */
+
+#ifndef Q_GEL_IMAGE_H_
+#define Q_GEL_IMAGE_H_
+
+#include <QLabel>
+#include "QCimg.h"
+
+class QGelImage: public QLabel {
+Q_OBJECT
+public:
+	QGelImage();
+	virtual ~QGelImage();
+
+	void setQCimg(QCimg & image);
+
+	void setScaleFactor(double scale_factor);
+	void scaleImage(double factor);
+	double getScaleFactor() const {
+		return (_scaleFactor);
+	}
+	int getX(int real_x) const;
+	int getY(int real_y) const;
+	int getBeforeX() const;
+	int getBeforeY() const;
+
+	signals:
+	void mouseMoved(QMouseEvent *event);
+	void moveGelImage(QMouseEvent *event);
+	void doubleClicked(QMouseEvent *event);
+
+protected:
+	void mouseMoveEvent(QMouseEvent *event);
+	void mouseDoubleClickEvent(QMouseEvent *event);
+	void mousePressEvent(QMouseEvent * event);
+	void mouseReleaseEvent(QMouseEvent * event);
+
+private:
+	double _scaleFactor;
+	bool _mousePressed;
+
+	int _before_x;
+	int _before_y;
+
+};
+
+#endif /* Q_GEL_IMAGE_H_ */
diff --git a/src/qtbeads/q_gel_image_scroll.cpp b/src/qtbeads/q_gel_image_scroll.cpp
new file mode 100644
index 0000000..f98a8d9
--- /dev/null
+++ b/src/qtbeads/q_gel_image_scroll.cpp
@@ -0,0 +1,46 @@
+/*
+ * q_gel_image_scroll.cpp
+ *
+ *  Created on: 3 janv. 2010
+ *      Author: olivier
+ */
+
+#include "q_gel_image_scroll.h"
+
+QGelImageScroll::QGelImageScroll() {
+	imageLabel = new QGelImage();
+	imageLabel->setBackgroundRole(QPalette::Base);
+	imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
+	imageLabel->setScaledContents(true);
+	setBackgroundRole(QPalette::Dark);
+	setWidget(imageLabel);
+}
+
+QGelImageScroll::~QGelImageScroll() {
+}
+
+const QGelImage * QGelImageScroll::getGelImage() const {
+	return (imageLabel);
+}
+
+void QGelImageScroll::setGelImage(const QImage & image) {
+	imageLabel->setPixmap(QPixmap::fromImage(image));
+	imageLabel->setScaleFactor(1.0);
+}
+
+void QGelImageScroll::setGelImage(const QCimg & image) {
+	imageLabel->setPixmap(QPixmap::fromImage(image));
+	imageLabel->setScaleFactor(1.0);
+}
+
+void QGelImageScroll::adjustSize() {
+	imageLabel->adjustSize();
+}
+
+void QGelImageScroll::setScaleFactor(double scale_factor) {
+	imageLabel->setScaleFactor(scale_factor);
+}
+
+void QGelImageScroll::scaleImage(double factor) {
+	imageLabel->scaleImage(factor);
+}
diff --git a/src/qtbeads/q_gel_image_scroll.h b/src/qtbeads/q_gel_image_scroll.h
new file mode 100644
index 0000000..926ac7f
--- /dev/null
+++ b/src/qtbeads/q_gel_image_scroll.h
@@ -0,0 +1,36 @@
+/*
+ * q_gel_image_scroll.h
+ *
+ *  Created on: 3 janv. 2010
+ *      Author: olivier
+ */
+
+#ifndef Q_GEL_IMAGE_SCROLL_H_
+#define Q_GEL_IMAGE_SCROLL_H_
+
+#include <QScrollArea>
+#include "q_gel_image.h"
+
+class QGelImageScroll: public QScrollArea {
+public:
+	QGelImageScroll();
+	virtual ~QGelImageScroll();
+
+	const QGelImage * getGelImage() const;
+
+	void setGelImage(const QImage & image);
+	void setGelImage(const QCimg & image);
+
+	void adjustSize();
+	void setScaleFactor(double scale_factor);
+	double getScaleFactor() const {
+		return (imageLabel->getScaleFactor());
+	}
+	void scaleImage(double factor);
+
+private:
+	QGelImage *imageLabel;
+
+};
+
+#endif /* Q_GEL_IMAGE_SCROLL_H_ */
diff --git a/src/qtbeads/qtbeads.cpp b/src/qtbeads/qtbeads.cpp
new file mode 100644
index 0000000..b2f1263
--- /dev/null
+++ b/src/qtbeads/qtbeads.cpp
@@ -0,0 +1,14 @@
+#include <QDebug>
+#include <QApplication>
+#include "../config.h"
+#include "main_window.h"
+
+int main(int argc, char * * argv) {
+	QApplication a(argc, argv);
+	QLocale::setDefault(QLocale::system());
+
+	MainWindow w;
+	w.show();
+
+	return a.exec();
+}
diff --git a/src/qtbeads/qtbeads_error.h b/src/qtbeads/qtbeads_error.h
new file mode 100644
index 0000000..cf38d97
--- /dev/null
+++ b/src/qtbeads/qtbeads_error.h
@@ -0,0 +1,35 @@
+/*
+ * qtbeads_error.h
+ *
+ *  Created on: 27 nov. 2009
+ *      Author: olivier
+ */
+
+#ifndef QTBEADS_ERROR_H_
+#define QTBEADS_ERROR_H_
+
+#include <exception>
+#include <QString>
+
+class qtbeadsError: public std::exception {
+public:
+	qtbeadsError(QString message = "") throw () :
+		_message(message) {
+	}
+
+	virtual const QString & getMessage() const throw () {
+		return _message;
+	}
+
+	virtual const char* what() const throw () {
+		return _message.toStdString().c_str();
+	}
+
+	virtual ~qtbeadsError() throw () {
+	}
+
+private:
+	QString _message; //Description de l'erreur
+};
+
+#endif /* QTBEADS_ERROR_H_ */
diff --git a/src/qtbeads/translations/beads_fr.qm b/src/qtbeads/translations/beads_fr.qm
new file mode 100644
index 0000000..8c17405
Binary files /dev/null and b/src/qtbeads/translations/beads_fr.qm differ
diff --git a/src/qtbeads/translations/beads_fr.ts b/src/qtbeads/translations/beads_fr.ts
new file mode 100644
index 0000000..f621557
--- /dev/null
+++ b/src/qtbeads/translations/beads_fr.ts
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="fr_FR">
+<context>
+    <name>MainWindow</name>
+    <message>
+        <source>Beads </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Open qtbeads file</source>
+        <translation>Ouvrir un fichier qtbeads</translation>
+    </message>
+    <message>
+        <source>qtbeads</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Cannot load %1.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Open qtbeads parameter file</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Save File</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Images *.png *.jpg (*.png *.jpg);;Text *.txt (*.txt);;PROTICdbML *.xml (*.xml);;Scalable Vector Graphic *.svg (*.svg)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>About QTbeads %1</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source><div>QTbeads %1 is a 2-DE electrophoresis gel image spot detection software developped by <a href="http://pappso.inra.fr/">PAPPSO</a></div><div>beads is freely available under the terms of the <a href="http://www.cecill.info/licences/Licence_CeCILL_V1.1-US.html">CeCILL</a> licence</div></source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Open...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+O</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Save...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Print...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+P</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>E&xit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+Q</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Zoom &In (25%)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl++</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Zoom &Out (25%)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+-</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Normal Size</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+S</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Fit to Window</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Ctrl+F</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&About</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Run</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Inverse</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>Parameters</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>About &Qt</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&File</source>
+        <translation>&Fichier</translation>
+    </message>
+    <message>
+        <source>&View</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Detection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <source>&Help</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>QObject</name>
+    <message>
+        <source>no gel image loaded... please open a gel image first</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+</TS>
diff --git a/src/spot.cpp b/src/spot.cpp
new file mode 100644
index 0000000..9dfe289
--- /dev/null
+++ b/src/spot.cpp
@@ -0,0 +1,307 @@
+#include <iostream>
+#include <list>
+#include "spot.h"
+
+#include "images/imageNumber.h"
+
+using namespace std;
+
+bool pixel_is_neighbour(int x1, int y1, int x2, int y2);
+float distance_between_2_points(int x1, int y1, int x2, int y2);
+
+float distance_between_2_points(int x1, int y1, int x2, int y2) {
+	float result = (float) (x1 - x2) * (float) (x1 - x2);
+	result += (float) (y1 - y2) * (float) (y1 - y2);
+
+	result = sqrt(result);
+	//cout << "spot::distance_between_2_points " << result << " x1 " << x1 << " y1 " << y1 << " x2 " << x2 << " y2 " << y2 << endl;
+
+	return (result);
+}
+
+bool pixel_is_neighbour(int x1, int y1, int x2, int y2) {
+	if ((abs(x1 - x2) <= 1) && (abs(y1 - y2) <= 1)) {
+		return true;
+	}
+	return (false);
+}
+
+spot::spot() {
+	_vol = 0;
+	_tx = 0;
+	_ty = 0;
+	_tmin = 65535;
+	_tmax = 0;
+	_area = 0;
+	_number = 0;
+	_bckgnd = 0;
+
+	_edge_x.resize(0);
+	_edge_y.resize(0);
+}
+
+spot::spot(const spot & src) {
+	_vol = src._vol;
+	_tx = src._tx;
+	_ty = src._ty;
+	_tmin = src._tmin;
+	_tmax = src._tmax;
+	_area = src._area;
+	_number = src._number;
+	_bckgnd = src._bckgnd;
+
+	_edge_x = src._edge_x;
+	_edge_y = src._edge_y;
+
+	_picked_num = src._picked_num;
+
+}
+
+spot::~spot() {
+	_edge_x.resize(0);
+	_edge_y.resize(0);
+}
+
+void spot::set_picked_num(const QString & picked_num) {
+	_picked_num = picked_num;
+}
+void spot::compute_background() {
+	_bckgnd = _area * _tmin;
+}
+
+bool spot::is_in_edge(int x, int y) {
+	bool is_present = false;
+	unsigned int i;
+	for (i = 0; i < _edge_x.size(); i++) {
+		if ((x == _edge_x[i]) && (y == _edge_y[i])) {
+			is_present = true;
+			break;
+		}
+	}
+	return (is_present);
+}
+
+void spot::add_edge_point(int x, int y) {
+
+	//cout << "spot::add_edge_coordinate x " << x << " y " << y << " on " << _number << endl;
+	//check that this coordinate is not already inserted:
+	if (is_in_edge(x, y))
+		return;
+
+	if (_edge_x.size() > 1) {
+		float distance_front = distance_between_2_points(_edge_x.front(),
+				_edge_y.front(), x, y);
+		float distance_back = distance_between_2_points(_edge_x.back(),
+				_edge_y.back(), x, y);
+		if (distance_back < distance_front) {
+			//cout << "push_back spot::add_edge_coordinate distance_front " << distance_front << " distance_back " << distance_back << endl;
+			_edge_x.push_back(x);
+			_edge_y.push_back(y);
+		} else {
+			//cout << "push_front spot::add_edge_coordinate distance_front " << distance_front << " distance_back " << distance_back << endl;
+			_edge_x.push_front(x);
+			_edge_y.push_front(y);
+		}
+	} else {
+		_edge_x.push_back(x);
+		_edge_y.push_back(y);
+	}
+
+	//cout << "end spot::add_edge_coordinate x " << x << " y " << y << " on " << _number << endl;
+	//cin >> x;
+
+}
+
+void spot::sort_edge_points() {
+
+	//cout << "spot::sort_edge_points " << endl;
+	unsigned int size(_edge_x.size());
+	if (size < 3) {
+		return;
+	}
+
+	deque<int> new_x_left;
+	deque<int> new_y_left;
+	/*
+	 unsigned int i;
+	 list<int> new_x_right;
+	 list<int> new_y_right;
+	 list<int>::iterator ix_list, iy_list;
+
+	 //new_x_left.push_back(_edge_x[0]);
+	 //new_y_left.push_back(_edge_y[0]);
+	 for (i = 0; i < size; i++) {
+	 new_x_right.push_back(_edge_x[i]);
+	 new_y_right.push_back(_edge_y[i]);
+	 }
+
+	 bool find=false;
+
+	 while ((new_x_right.size() > 0) and (new_x_left.size() >= 0)) {
+	 ix_list = new_x_right.begin();
+	 iy_list = new_y_right.begin();
+
+	 if (new_x_left.size() == 0) {
+	 new_x_left.push_back(*ix_list);
+	 new_y_left.push_back(*iy_list);
+	 new_x_right.erase(ix_list);
+	 new_y_right.erase(iy_list);
+	 ix_list++;
+	 iy_list++;
+	 } else {
+	 find = false;
+
+	 while ((find == false) and (ix_list != new_x_right.end())) {
+	 if (pixel_is_neighbour(new_x_left.back(), new_y_left.back(),
+	 *ix_list, *iy_list)) {
+	 find = true;
+	 new_x_left.push_back(*ix_list);
+	 new_y_left.push_back(*iy_list);
+	 new_x_right.erase(ix_list);
+	 new_y_right.erase(iy_list);
+	 }
+	 ix_list++;
+	 iy_list++;
+	 }
+
+	 if (find == false) {
+	 new_x_left.pop_front();
+	 new_y_left.pop_front();
+	 }
+
+	 }
+	 }
+
+	 _edge_x.resize(new_x_left.size());
+	 _edge_y.resize(new_y_left.size());
+	 for (i = 0; i < new_x_left.size(); i++) {
+	 _edge_x[i] = new_x_left[i];
+	 _edge_y[i] = new_y_left[i];
+	 }
+	 */
+
+	unsigned int i, finish;
+	int min_ipos(0);
+	float distance, d_tmp;
+	deque<int> new_x;
+	deque<int> new_y;
+
+	new_x.push_back(_edge_x[0]);
+	new_y.push_back(_edge_y[0]);
+	_edge_x[0] = -1;
+
+	finish = size;
+	while (finish > 0) {
+		distance = 5000;
+		for (i = 1; i < size; i++) {
+			if (_edge_x[i] < 0)
+				continue;
+			d_tmp = distance_between_2_points(_edge_x[i], _edge_y[i],
+					new_x.back(), new_y.back());
+			if (d_tmp < distance) {
+				distance = d_tmp;
+				min_ipos = i;
+			}
+		}
+		if (distance < 10) {
+			new_x.push_back(_edge_x[min_ipos]);
+			new_y.push_back(_edge_y[min_ipos]);
+		}
+		finish--;
+		_edge_x[min_ipos] = -1;
+	}
+	_edge_x = new_x;
+	_edge_y = new_y;
+
+	//cout << "end spot::sort_edge_points " << endl;
+}
+
+void spot::eliminate_aligned_edge_points() {
+
+	if (_edge_x.size() < 3) {
+		return;
+	}
+	unsigned int i;
+	deque<int> new_x;
+	deque<int> new_y;
+
+	new_x.push_back(_edge_x[0]);
+	new_y.push_back(_edge_y[0]);
+
+	for (i = 2; i < _edge_x.size(); i++) {
+		if (check_alignment(i)) {
+		} else {
+			new_x.push_back(_edge_x[i - 1]);
+			new_y.push_back(_edge_y[i - 1]);
+		}
+	}
+	new_x.push_back(_edge_x[_edge_x.size() - 1]);
+	new_y.push_back(_edge_y[_edge_y.size() - 1]);
+
+	_edge_x = new_x;
+	_edge_y = new_y;
+
+}
+
+/** \brief check alignment of the last 3 edges points
+ *
+ * if spots are aligned
+ */
+bool spot::check_alignment(unsigned int pos) {
+	bool remove = false;
+	if (pos < 3) {
+		return (remove);
+	}
+	int x1 = _edge_x[pos - 2];
+	int y1 = _edge_y[pos - 2];
+	int x2 = _edge_x[pos - 1];
+	int y2 = _edge_y[pos - 1];
+	int x3 = _edge_x[pos];
+	int y3 = _edge_y[pos];
+
+	if ((x1 == x2) && (x2 == x3)) {
+		//aligned
+		if ((y1 < y2) && (y2 < y3)) {
+			//			cout << "t1" << endl;
+			remove = true;
+		}
+		if ((y3 < y2) && (y2 < y1)) {
+			//			cout << "t2" << endl;
+			remove = true;
+		}
+	} else if ((y1 == y2) && (y2 == y3)) {
+		//aligned
+		if ((x1 < x2) && (x2 < x3)) {
+			//			cout << "t3" << endl;
+			remove = true;
+		}
+		if ((x3 < x2) && (x2 < x1)) {
+			//			cout << "t4" << endl;
+			remove = true;
+		}
+	} else if ((x1 - x2) == 0) {
+	} else if ((y1 - y2) == 0) {
+	} else {
+
+		float ratio_x = ((float) (x1 - x3)) / ((float) (x1 - x2));
+		float ratio_y = ((float) (y1 - y3)) / ((float) (y1 - y2));
+
+		if (ratio_x != ratio_y) {
+			//not aligned
+		} else if (ratio_x < 1) {
+			//aligned but going back => keep the second point
+		} else {
+			//aligned and going forward => no need to keep second point:
+			remove = true;
+			//			cout << "spot::check_alignment remove ratio_x " <<  ratio_x << " ratio_y " << ratio_y << " in " << _number << endl;
+		}
+	}
+	//	if (remove) {
+	//		cout << "spot::check_alignment remove x " <<  x2 << " y " << y2 << " in " << _number << endl;
+	//		cout << x1 << " " << x2 << " " << x3 << endl;
+	//		cout << y1 << " " << y2 << " " << y3 << endl;
+	//
+	//	}
+	return (remove);
+
+}
diff --git a/src/spot.h b/src/spot.h
new file mode 100644
index 0000000..86c9b1e
--- /dev/null
+++ b/src/spot.h
@@ -0,0 +1,153 @@
+#ifndef SPOT_
+#define SPOT_
+
+#include <QString>
+#include <deque>
+
+using namespace std;
+/** \brief object to handle spot properties
+ * 
+ * a spot on a 2-DE gel electrophoresis image has :
+ * - a number
+ * - coordinates (x, y)
+ * - barycenter (tx, ty)
+ * - volume, background, area
+ */
+
+class imageNumber;
+
+class spot {
+public:
+	spot();
+	spot(const spot & src);
+	~spot();
+
+	void set_picked_num(const QString & picked_num);
+	const QString & get_picked_num() const {
+		return (_picked_num);
+	}
+
+	void set_number(int number) {
+		_number = number;
+	}
+
+	const int get_number() const {
+		return (_number);
+	}
+
+	void set_vol(long int vol) {
+		_vol = vol;
+	}
+
+	const int get_x() const {
+		return ((int) _tx);
+	}
+
+	void set_x(int x) {
+		_tx = x;
+	}
+
+	const int get_y() const {
+		return ((int) _ty);
+	}
+
+	void set_y(int y) {
+		_ty = y;
+	}
+
+	const float get_tx() const {
+		return (_tx);
+	}
+
+	void set_tx(float x) {
+		_tx = x;
+	}
+
+	const float get_ty() const {
+		return (_ty);
+	}
+
+	const void set_ty(float y) {
+		_ty = y;
+	}
+
+	const long int get_vol() const {
+		return (_vol);
+	}
+
+	void set_area(unsigned long int surface) {
+		_area = surface;
+	}
+
+	const unsigned long int get_area() const {
+		return (_area);
+	}
+
+	void set_tmin(unsigned long int tmin) {
+		_tmin = tmin;
+	}
+
+	const unsigned long int get_tmin() const {
+		return (_tmin);
+	}
+
+	void set_tmax(unsigned long int tmax) {
+		_tmax = tmax;
+	}
+
+	const unsigned long int get_tmax() const {
+		return (_tmax);
+	}
+
+	void compute_background();
+	const unsigned long int get_bckgnd() const {
+		return (_bckgnd);
+	}
+
+	void add_edge_point(int x, int y);
+
+	void sort_edge_points();
+	//void look_for_edges(const imageNumber & image_numeros,int begin_x, int begin_y);
+
+	unsigned int get_nb_edge_points() const {
+		return (_edge_x.size());
+	}
+
+	int get_x_coord_of_edge_point(int i) const {
+		return _edge_x[i];
+	}
+	int get_y_coord_of_edge_point(int i) const {
+		return _edge_y[i];
+	}
+
+	void eliminate_aligned_edge_points();
+
+	bool is_quantified() const {
+		if (_area > 0) {
+			return (true);
+		}
+		return (false);
+	}
+
+private:
+	//void rec_look_for_edges(const imageNumber & image_numeros,int current_x, int current_y);
+	bool is_in_edge(int x, int y);
+	bool check_alignment(unsigned int i);
+
+	int _number;
+	QString _picked_num;
+
+	long int _vol;
+	unsigned long int _tmin;
+	unsigned long int _tmax;
+	unsigned long int _area;
+	unsigned long int _bckgnd;
+	float _tx;
+	float _ty;
+
+	deque<int> _edge_x;
+	deque<int> _edge_y;
+
+};
+
+#endif /*SPOT_*/
diff --git a/src/spotDocument.cpp b/src/spotDocument.cpp
new file mode 100644
index 0000000..4a8931a
--- /dev/null
+++ b/src/spotDocument.cpp
@@ -0,0 +1,81 @@
+#include "spotDocument.h"
+#include <iostream>
+#include <QFileInfo>
+
+spotDocument::spotDocument() {
+	_output_file = NULL;
+	_output_stream = NULL;
+}
+
+spotDocument::~spotDocument() {
+	if (_output_stream != NULL) {
+		delete (_output_stream);
+	}
+	if (_output_file != NULL) {
+		delete (_output_file);
+	}
+}
+
+void spotDocument::open(const QString & filename) {
+	QString output_filename(filename);
+	QFileInfo fileinfo(filename);
+	QString ext = fileinfo.suffix();
+	if (ext != "txt") {
+		output_filename = output_filename + ".txt";
+	}
+
+	_output_file = new QFile(output_filename);
+
+	if (_output_file->exists()) {
+		cerr << "WARNING : " << _output_file->fileName().toStdString()
+				<< " output file already exists" << endl;
+	}
+	if (_output_file->open(QIODevice::WriteOnly)) {
+		_output_stream = new QTextStream(_output_file);
+		_output_stream ->setLocale(QLocale::system());
+	} else {
+		//throw exception
+	}
+
+	*_output_stream << "Spot Volume x y min surface background vol-bckgnd\n";
+
+}
+
+void spotDocument::write_spot(const spot & thespot) {
+	//std::ostringstream tag;
+	*_output_stream << thespot.get_number() << " ";
+	*_output_stream << thespot.get_vol() << " ";
+	*_output_stream << thespot.get_tx() << " ";
+	*_output_stream << thespot.get_ty() << " ";
+	*_output_stream << thespot.get_tmin() << " ";
+	*_output_stream << thespot.get_area() << " ";
+	*_output_stream << thespot.get_bckgnd() << " ";
+	*_output_stream << (thespot.get_vol() - thespot.get_bckgnd()) << "\n";
+
+	//return (tag.str().c_str());
+}
+
+void spotDocument::close() {
+	qDebug() << "spotDocument::close begin";
+	if (_output_stream != NULL) {
+		_output_stream->flush();
+		delete (_output_stream);
+	}
+	if (_output_file != NULL) {
+		_output_file->close();
+		delete (_output_file);
+	}
+	_output_file = NULL;
+	_output_stream = NULL;
+	qDebug() << "spotDocument::close end";
+}
+
+void spotDocument::write_detection(detection & the_detection) {
+
+	detection::iterator it;
+	for (it = the_detection.begin(); it != the_detection.end(); ++it) {
+		spot & the_spot = (*it).second;
+		write_spot(the_spot);
+	}
+}
+
diff --git a/src/spotDocument.h b/src/spotDocument.h
new file mode 100644
index 0000000..568fab3
--- /dev/null
+++ b/src/spotDocument.h
@@ -0,0 +1,40 @@
+#ifndef SPOTDOCUMENT_H_
+#define SPOTDOCUMENT_H_
+
+#include <QDebug>
+#include <QFile>
+#include <QTextStream>
+
+#include "detection.h"
+#include "spot.h"
+#include "config.h"
+
+class spotDocument {
+public:
+	spotDocument();
+
+	~spotDocument();
+
+	void set_gel_image_file_name(const QString & filename) {
+		_gel_image_file_name = filename;
+	}
+	const QString & get_gel_image_file_name() const {
+		return (_gel_image_file_name);
+	}
+
+	virtual void open(const QString & filename);
+	virtual void close();
+
+	virtual void write_detection(detection & the_detection);
+
+	virtual void write_spot(const spot &);
+
+protected:
+
+	QString _gel_image_file_name;
+
+	QFile * _output_file;
+	QTextStream * _output_stream;
+};
+
+#endif /*SPOTDOCUMENT_H_*/
diff --git a/src/spotPROTICdbDocument.cpp b/src/spotPROTICdbDocument.cpp
new file mode 100644
index 0000000..2e4245d
--- /dev/null
+++ b/src/spotPROTICdbDocument.cpp
@@ -0,0 +1,225 @@
+#include <iostream>
+#include <QFileInfo>
+
+#include "spotPROTICdbDocument.h"
+
+void spotPROTICdbDocument::open(const QString & filename) {
+	QString output_filename(filename);
+	QFileInfo fileinfo(filename);
+	QString ext = fileinfo.suffix();
+	if (ext != "xml") {
+		output_filename = output_filename + ".xml";
+	}
+
+	_output_file = new QFile(output_filename);
+	_output_stream = NULL;
+
+	if (_output_file->exists()) {
+		cerr << "WARNING : " << _output_file->fileName().toStdString()
+				<< " xml output file already exists" << endl;
+	}
+	if (_output_file->open(QIODevice::WriteOnly)) {
+
+		_p_xml_stream = new QXmlStreamWriter(_output_file);
+	} else {
+		//throw exception
+	}
+
+	_p_xml_stream->setAutoFormatting(true);
+	_p_xml_stream->writeStartDocument();
+	_p_xml_stream->writeStartElement("PROTICdb");
+	// xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
+	_p_xml_stream->writeNamespace("http://www.w3.org/2001/XMLSchema-instance",
+			"xsi");
+	_p_xml_stream->writeAttribute("http://www.w3.org/2001/XMLSchema-instance",
+			"noNamespaceSchemaLocation",
+			"http://uln/~langella/proticdev/doc/xml_schema/proticdbml/proticdbml.xsd");
+	_p_xml_stream->writeAttribute("version", "1.0");
+	_p_xml_stream->writeAttribute("type", "2Danalyses");
+	_p_xml_stream->writeStartElement("detectMeth");
+	_p_xml_stream->writeAttribute("name", "");
+	_p_xml_stream->writeAttribute("id", "M1");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("scalingMeth");
+	_p_xml_stream->writeAttribute("name", "");
+	_p_xml_stream->writeAttribute("id", "M2");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("project");
+	_p_xml_stream->writeAttribute("id", "p1");
+	_p_xml_stream->writeAttribute("name", "");
+	_p_xml_stream->writeStartElement("gelImages");
+	_p_xml_stream->writeStartElement("gelImage");
+	_p_xml_stream->writeAttribute("id", "I1");
+	QFileInfo file_info(get_gel_image_file_name());
+	_p_xml_stream->writeAttribute("name", file_info.baseName());
+	_p_xml_stream->writeEndElement();
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("matchSet");
+	_p_xml_stream->writeAttribute("name", "");
+
+}
+
+void spotPROTICdbDocument::close() {
+	qDebug() << "spotPROTICdbDocument::close begin";
+	std::vector<const spot *>::const_iterator it;
+
+	_p_xml_stream->writeEndElement(); //matchset
+
+	_p_xml_stream->writeStartElement("detectionList");
+	_p_xml_stream->writeAttribute("master_id", "D1");
+	_p_xml_stream->writeStartElement("detection");
+	_p_xml_stream->writeAttribute("id", "D1");
+	_p_xml_stream->writeAttribute("image_id", "I1");
+	_p_xml_stream->writeAttribute("detect_meth_id", "M1");
+	_p_xml_stream->writeAttribute("type", "native");
+	for (it = _all_spots.begin(); it != _all_spots.end(); ++it) {
+		const spot & thespot(**it);
+
+		//std::ostringstream tag;
+		_p_xml_stream->writeStartElement("spotGel");
+		_p_xml_stream->writeAttribute("id", "sg" + QString::number(
+				thespot.get_number()));
+		_p_xml_stream->writeAttribute("num", QString::number(
+				thespot.get_number()));
+		//*this << "                <spotGel id=\"s" << thespot.get_number()
+		//		<< "\" num=\"" << thespot.get_number();
+		_p_xml_stream->writeAttribute("spot_id", "s" + QString::number(
+				thespot.get_number()));
+
+		if (!thespot.get_picked_num().isEmpty()) {
+			qDebug()
+					<< "spotPROTICdbDocument::close() get_picked_num not empty";
+			_p_xml_stream->writeAttribute("manual_num",
+					thespot.get_picked_num());
+			//*this << "\" manual_num=\"" << thespot.get_picked_num();
+
+
+			//write sample before closing project tag <samples>:
+			_samples.push_back(&thespot);
+			//_samples << "<sample id=\"ms" << thespot.get_number() << "\"";
+			//_samples << " name=\"" << thespot.get_picked_num() << "\"";
+			//_samples << " spotgel_id=\"s" << thespot.get_number() << "\"";
+			//_samples << " taxa=\"\">" << endl;
+			//_samples << "</sample>" << endl;
+		} else {
+			//qDebug() << "spotPROTICdbDocument::write_spot(const spot & thespot) get_picked_num empty";
+		}
+		//*this << "\">" << std::endl;
+		_p_xml_stream->writeStartElement("x");
+		_p_xml_stream->writeCharacters(QString::number(thespot.get_x()));
+		_p_xml_stream->writeEndElement();
+		//*this << "                    <x>" << thespot.get_x() << "</x>"
+		//		<< std::endl;
+		_p_xml_stream->writeStartElement("y");
+		_p_xml_stream->writeCharacters(QString::number(thespot.get_y()));
+		_p_xml_stream->writeEndElement();
+		//*this << "                    <y>" << thespot.get_y() << "</y>"
+		//		<< std::endl;
+		if (thespot.is_quantified()) {
+			_p_xml_stream->writeStartElement("sc");
+			_p_xml_stream->writeAttribute("sc_meth_id", "M2");
+			//*this << "                    <sc sc_meth_id=\"M2\">" << std::endl;
+			_p_xml_stream->writeStartElement("vol");
+			_p_xml_stream->writeCharacters(QString::number(thespot.get_vol()));
+			_p_xml_stream->writeEndElement();
+			//*this << "                        <vol>" << thespot.get_vol()
+			//		<< "</vol>" << std::endl;
+			//*this << "                        <area>" << thespot.get_area() << "</area>" << std::endl;
+			_p_xml_stream->writeStartElement("ud");
+			_p_xml_stream->writeAttribute("acc", "PROTICdbO:0000056");
+			_p_xml_stream->writeAttribute("value", QString::number(
+					thespot.get_area()));
+			_p_xml_stream->writeEndElement();
+			//*this
+			//		<< "                        <ud acc=\"PROTICdbO:0000056\" value=\""
+			//		<< thespot.get_area() << "\"/>" << std::endl;
+			_p_xml_stream->writeStartElement("ud");
+			_p_xml_stream->writeAttribute("acc", "PROTICdbO:0000277");
+			_p_xml_stream->writeAttribute("value", QString::number(
+					thespot.get_bckgnd()));
+			_p_xml_stream->writeEndElement();
+			//*this
+			//		<< "                        <ud acc=\"PROTICdbO:0000277\" value=\""
+			//		<< thespot.get_bckgnd() << "\"/>" << std::endl;
+			_p_xml_stream->writeEndElement();
+			//*this << "                    </sc>" << std::endl;
+		}
+		_p_xml_stream->writeEndElement();
+
+	}
+
+	_p_xml_stream->writeEndElement();
+	//*this << "            </detection>" << std::endl;
+	_p_xml_stream->writeEndElement();
+	//*this << "        </detectionList>" << std::endl;
+
+
+	_p_xml_stream->writeStartElement("samples");
+
+	for (it = _samples.begin(); it != _samples.end(); ++it) {
+		const spot & thespot(**it);
+		_p_xml_stream->writeStartElement("sample");
+		_p_xml_stream->writeAttribute("id", "ms" + QString::number(
+				thespot.get_number()));
+		//_samples << "<sample id=\"ms" << thespot.get_number() << "\"";
+		_p_xml_stream->writeAttribute("name", thespot.get_picked_num());
+		//_samples << " name=\"" << thespot.get_picked_num() << "\"";
+		_p_xml_stream->writeAttribute("spotgel_id", "sg" + QString::number(
+				thespot.get_number()));
+		//_samples << " spotgel_id=\"s" << thespot.get_number() << "\"";
+		_p_xml_stream->writeAttribute("taxa", "");
+		//_samples << " taxa=\"\">" << endl;
+		_p_xml_stream->writeEndElement();
+		//_samples << "</sample>" << endl;
+	}
+
+	_p_xml_stream->writeEndElement();
+	//	_samples << "</samples>" << endl;
+
+	//*this << _samples.str() << std::endl;
+	//cout << mystr;
+
+	//*this << "    </project>" << std::endl;
+	//*this << "</PROTICdb>" << std::endl;
+	//std::ofstream::close();
+	_p_xml_stream->writeEndDocument();
+
+	if (_p_xml_stream != NULL) {
+		delete (_p_xml_stream);
+	}
+	if (_output_file != NULL) {
+		_output_file->close();
+		delete (_output_file);
+	}
+	if (_output_stream != NULL) {
+		delete (_output_stream);
+	}
+	_output_file = NULL;
+	_output_stream = NULL;
+	_p_xml_stream = NULL;
+	qDebug() << "spotPROTICdbDocument::close end";
+
+}
+
+void spotPROTICdbDocument::write_spot(const spot & thespot) {
+	qDebug() << "spotPROTICdbDocument::write_spot(const spot & thespot) begin";
+
+	this->_all_spots.push_back(&thespot);
+
+	_p_xml_stream->writeStartElement("spot");
+	_p_xml_stream->writeAttribute("id", "s" + QString::number(
+			thespot.get_number()));
+	_p_xml_stream->writeAttribute("num", QString::number(thespot.get_number()));
+	/* <matchSet name="">
+	 <spot num="1" id="ms1"/>
+	 </matchSet>*/
+	_p_xml_stream->writeEndElement();
+
+	//*this << "                </spotGel>" << std::endl;
+
+	//return (tag.str().c_str());
+}
+
diff --git a/src/spotPROTICdbDocument.h b/src/spotPROTICdbDocument.h
new file mode 100644
index 0000000..1bcc93f
--- /dev/null
+++ b/src/spotPROTICdbDocument.h
@@ -0,0 +1,28 @@
+#ifndef SPOTPROTICDBDOCUMENT_H_
+#define SPOTPROTICDBDOCUMENT_H_
+
+#include <QXmlStreamWriter>
+
+#include "spotDocument.h"
+
+class spotPROTICdbDocument: public spotDocument {
+public:
+	spotPROTICdbDocument() {
+	}
+
+	~spotPROTICdbDocument() {
+	}
+
+	void open(const QString & filename);
+	virtual void close();
+	void write_spot(const spot &);
+
+protected:
+	std::vector<const spot *> _all_spots;
+	std::vector<const spot *> _samples;
+
+	QXmlStreamWriter * _p_xml_stream;
+
+};
+
+#endif /*SPOTPROTICDBDOCUMENT_H_*/
diff --git a/src/spotSvgDocument.cpp b/src/spotSvgDocument.cpp
new file mode 100644
index 0000000..2eb12b3
--- /dev/null
+++ b/src/spotSvgDocument.cpp
@@ -0,0 +1,380 @@
+#include "spotSvgDocument.h"
+using namespace std;
+#include <QFileInfo>
+#include <iostream>
+#include <fstream>
+#include "encode/base64.h"
+
+spotSvgDocument::spotSvgDocument() {
+	_css_classes["quantif"] = "fill:#ff0000;fill-opacity:1;stroke:none";
+	_css_classes["contour"] = "stroke:#000000;stroke-opacity:1;fill:none";
+
+	_embed_gel_image = false;
+	_gel_image_writed = false;
+}
+
+void spotSvgDocument::set_css_class(const QString & css_class_name,
+		const QString & css_style) {
+	_css_classes[css_class_name] = css_style;
+}
+
+void spotSvgDocument::open(const QString & filename) {
+	QString output_filename(filename);
+	QFileInfo fileinfo(filename);
+	QString ext = fileinfo.suffix();
+	if (ext != "svg") {
+		output_filename = output_filename + ".svg";
+	}
+
+	_output_file = new QFile(output_filename);
+	_output_stream = NULL;
+
+	if (_output_file->exists()) {
+		cerr << "WARNING : " << _output_file->fileName().toStdString()
+				<< " svg file already exists" << endl;
+	}
+	if (_output_file->open(QIODevice::WriteOnly)) {
+
+		_p_xml_stream = new QXmlStreamWriter(_output_file);
+	} else {
+		//throw exception
+	}
+
+	/*
+	 * <?xml version="1.0" standalone="no"?>
+	 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+	 "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+	 <svg width="100%" height="100%" version="1.1"
+	 xmlns="http://www.w3.org/2000/svg">
+
+	 <circle cx="100" cy="50" r="40" stroke="black"
+	 stroke-width="2" fill="red"/>
+
+	 </svg>
+	 *
+	 */
+	_p_xml_stream->setAutoFormatting(true);
+	_p_xml_stream->writeStartDocument();
+	/*
+	 * <svg version="1.1"
+	 baseProfile="full"
+	 xmlns="http://www.w3.org/2000/svg"
+	 xmlns:xlink="http://www.w3.org/1999/xlink"
+	 xmlns:ev="http://www.w3.org/2001/xml-events">
+	 *
+	 */
+	_p_xml_stream->writeStartElement("svg");
+	_p_xml_stream->writeAttribute("baseProfile", "full");
+	_p_xml_stream->writeDefaultNamespace("http://www.w3.org/2000/svg");
+	_p_xml_stream->writeNamespace("http://www.w3.org/1999/xlink", "xlink");
+	_p_xml_stream->writeNamespace("http://www.w3.org/2001/xml-events", "ev");
+	//*this << "<svg ";
+	_p_xml_stream->writeAttribute("width", "100%");
+	//*this << " width=\"100%\"";
+	_p_xml_stream->writeAttribute("height", "100%");
+	//*this << " height=\"100%\"";
+	_p_xml_stream->writeAttribute("version", "1.1");
+	//*this << " version=\"1.1\"";
+
+	//définition des styles (syntaxe CSS):
+	_p_xml_stream->writeStartElement("defs");
+	//*this << "<defs>" << std::endl;
+	_p_xml_stream->writeStartElement("style");
+	_p_xml_stream->writeAttribute("type", "text/css");
+
+	QString style_css;
+	//*this << "\t<style type=\"text/css\"><![CDATA[" << std::endl;
+	std::map<QString, QString>::iterator it;
+	for (it = _css_classes.begin(); it != _css_classes.end(); ++it) {
+		style_css += "\t\t." + (*it).first + " {" + (*it).second + " }\n";
+		//*this << "\t\t." << (*it).first << " {" << (*it).second << " }"
+		//		<< std::endl;
+	}
+	_p_xml_stream->writeCDATA(style_css);
+	_p_xml_stream->writeEndElement();
+
+	//*this << "]]></style>" << std::endl;
+	_p_xml_stream->writeEndElement();
+	//*this << "</defs>" << std::endl;
+
+	_p_xml_stream->writeStartElement("g");
+	//*this << "\t<g>";
+
+	/*
+	 *this << "\t\t<g transform=\"translate(+1,+1)\">";
+	 *this << "\t\t<image y=\"102.40313\" x=\"75.97953\"";
+	 *this << " height=\"564\"";
+	 *this << " width=\"516\"";
+	 *this << " xlink:href=\"" << get_gel_image_file_name() << "\" />\n";
+	 */
+
+}
+
+void spotSvgDocument::close() {
+	qDebug() << "spotSvgDocument::close begin";
+	//*this << "\t\t</g>" << endl;
+	_p_xml_stream->writeEndElement();
+	//*this << "\t</g>" << endl;
+	_p_xml_stream->writeEndDocument();
+	//*this << "</svg>" << std::endl;
+	//std::ofstream::close();
+
+	if (_p_xml_stream != NULL) {
+		delete (_p_xml_stream);
+	}
+	if (_output_file != NULL) {
+		_output_file->close();
+		delete (_output_file);
+	}
+	if (_output_stream != NULL) {
+		delete (_output_stream);
+	}
+	_output_file = NULL;
+	_output_stream = NULL;
+	_p_xml_stream = NULL;
+	qDebug() << "spotSvgDocument::close end";
+
+}
+
+void spotSvgDocument::write_spot_number(const spot & thespot,
+		const QString & cssclass) {
+
+	_p_xml_stream->writeStartElement("text");
+	//*this << "\t\t<text";
+	_p_xml_stream->writeAttribute("class", cssclass);
+	//*this << " class=\"" << cssclass << "\"";
+	_p_xml_stream->writeAttribute("x", QString::number(thespot.get_x()));
+	//*this << " x=\"" << thespot.get_x() << "\"";
+	_p_xml_stream->writeAttribute("y", QString::number(thespot.get_y()));
+	//*this << " y=\"" << thespot.get_y() << "\"";
+	_p_xml_stream->writeAttribute("dx", "-10");
+	//*this << " dx=\"-10\"";
+	_p_xml_stream->writeAttribute("dy", "-10");
+	//*this << " dy=\"-10\"";
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_number()));
+	_p_xml_stream->writeEndElement();
+	//*this << ">" << thespot.get_number() << "</text>" << std::endl;
+}
+
+void spotSvgDocument::write_spot(const spot & thespot, const QString & cssclass) {
+	//style = "fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1";
+	/*
+	 <circle cx="100" cy="50" r="40" stroke="black"
+	 stroke-width="2" fill="red"/>
+	 */
+	//std::ostringstream tag;
+	//*this << "<point x=\"" << thespot.get_x() << "\" y=\"" << thespot.get_y() << "\"></rect>\n";
+
+	_p_xml_stream->writeStartElement("circle");
+	//*this << "\t\t<circle";
+	_p_xml_stream->writeAttribute("class", cssclass);
+	//*this << " class=\"" << cssclass << "\"";
+	_p_xml_stream->writeAttribute("cx", QString::number(thespot.get_x()));
+	//*this << " cx=\"" << thespot.get_x() << "\"";
+	_p_xml_stream->writeAttribute("cy", QString::number(thespot.get_y()));
+	//*this << " cy=\"" << thespot.get_y() << "\"";
+	_p_xml_stream->writeAttribute("r", "1");
+	//*this << " r=\"1\"";
+	_p_xml_stream->writeEndElement();
+	//*this << "/>" << std::endl;
+
+	//return (tag.str().c_str());
+}
+
+void spotSvgDocument::write_spot_contours(const spot & thespot,
+		const QString & cssclass) {
+
+	unsigned int nb_edge_points = thespot.get_nb_edge_points();
+
+	if (nb_edge_points == 0)
+		return;
+	/*
+	 * <path
+	 style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.25pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+	 d="M 154.28571,383.79075 L 297.14286,298.07647 L 437.14286,415.21933 L 294.28571,518.07647 L 154.28571,383.79075 z "
+	 id="path2160" />
+	 */
+	_p_xml_stream->writeComment("spot " + QString::number(thespot.get_number()));
+	//*this << "<!-- spot " << thespot.get_number() << "-->";
+	_p_xml_stream->writeStartElement("path");
+	//*this << "\t\t<path";
+	_p_xml_stream->writeAttribute("class", cssclass);
+	//*this << " class=\"" << cssclass << "\"" << std::endl;
+
+	QString dpath = "M ";
+	//*this << " d=\"M ";
+
+	unsigned int i;
+	for (i = 0; i < nb_edge_points; i++) {
+		if (i > 0) {
+			dpath += " L ";
+			//*this << " L ";
+		}
+		dpath += QString::number(thespot.get_x_coord_of_edge_point(i)) + ","
+				+ QString::number(thespot.get_y_coord_of_edge_point(i));
+		//*this << thespot.get_x_coord_of_edge_point(i) << ","
+		//		<< thespot.get_y_coord_of_edge_point(i);
+	}
+	dpath += " L " + QString::number(thespot.get_x_coord_of_edge_point(0))
+			+ "," + QString::number(thespot.get_y_coord_of_edge_point(0))
+			+ " z ";
+	//*this << " L " << thespot.get_x_coord_of_edge_point(0) << ","
+	//		<< thespot.get_y_coord_of_edge_point(0) << " z ";
+
+	_p_xml_stream->writeAttribute("d", dpath);
+
+	_p_xml_stream->writeEndElement();
+	//*this << "\"" << std::endl;
+	//*this << "/>" << std::endl;
+
+	//return (tag.str().c_str());
+}
+
+void spotSvgDocument::write_gel_image() {
+
+	if (this->_embed_gel_image) {
+		//embed the gel image file using base64 encoding
+		_p_xml_stream->writeStartElement("image");
+		//*this << "\t\t<image";
+		_p_xml_stream->writeAttribute("x", "0");
+		//*this << " x=\"0\"";
+		_p_xml_stream->writeAttribute("y", "0");
+		//*this << " y=\"0\"";
+		_p_xml_stream->writeAttribute("height", QString::number(
+				this->_gel_image_height));
+		//*this << " height=\"" << this->_gel_image_height << "\"";
+		_p_xml_stream->writeAttribute("width", QString::number(
+				this->_gel_image_width));
+		//*this << " width=\"" << this->_gel_image_width << "\"";
+		_p_xml_stream->writeAttribute("http://www.w3.org/1999/xlink", "href",
+				"data:image/jpg;base64,\n" + this->_encoded_gel_image + "\n");
+		//*this << " xlink:href=\"data:image/jpg;base64," << endl
+		//		<< this->_encoded_gel_image << endl << "\"";
+		_p_xml_stream->writeEndElement();
+		//*this << "/>" << std::endl;
+	} else {
+		//the gel image file is only a reference on the gel image (URL)
+
+		_p_xml_stream->writeStartElement("image");
+		//*this << "\t\t<image";
+		_p_xml_stream->writeAttribute("x", "0");
+		//*this << " x=\"0\"";
+		_p_xml_stream->writeAttribute("y", "0");
+		//*this << " y=\"0\"";
+		_p_xml_stream->writeAttribute("height", QString::number(
+				this->_gel_image_height));
+		//*this << " height=\"" << _gel_image_height << "\"";
+		_p_xml_stream->writeAttribute("width", QString::number(
+				this->_gel_image_width));
+		//*this << " width=\"" << _gel_image_width << "\"";
+		_p_xml_stream->writeAttribute("http://www.w3.org/1999/xlink", "href",
+				"./" + get_gel_image_file_name());
+		//*this << " xlink:href=\"./" << get_gel_image_file_name() << "\"";
+		_p_xml_stream->writeEndElement();
+		//*this << "/>" << std::endl;
+	}
+}
+
+void spotSvgDocument::write_detection(const detection & the_detection,
+		const QString & cssclass) {
+
+	_gel_image_height = the_detection.get_gel_image_height();
+	_gel_image_width = the_detection.get_gel_image_width();
+
+	if (_gel_image_writed == false) {
+		this->write_gel_image();
+		_gel_image_writed = true;
+	}
+	//écrit tous les spots dans un group
+	_p_xml_stream->writeStartElement("g");
+	//*this << "\t\t<g>";
+	detection::const_iterator it;
+	for (it = the_detection.begin(); it != the_detection.end(); ++it) {
+		const spot & the_spot = (*it).second;
+		write_spot(the_spot, cssclass);
+	}
+	_p_xml_stream->writeEndElement();
+	//*this << "\t\t</g>";
+
+}
+
+void spotSvgDocument::draw_contours(const detection & the_detection,
+		const QString & cssclass) {
+	_gel_image_height = the_detection.get_gel_image_height();
+	_gel_image_width = the_detection.get_gel_image_width();
+	if (_gel_image_writed == false) {
+		this->write_gel_image();
+		_gel_image_writed = true;
+	}
+	//dessine les bords des spots en SVG
+	_p_xml_stream->writeStartElement("g");
+	//*this << "\t\t<g>";
+	detection::const_iterator it;
+	for (it = the_detection.begin(); it != the_detection.end(); ++it) {
+		const spot & the_spot = (*it).second;
+		write_spot_contours(the_spot, cssclass);
+	}
+	_p_xml_stream->writeEndElement();
+	//*this << "\t\t</g>";
+}
+
+void spotSvgDocument::draw_spot_numbers(detection & my_detection,
+		const QString & cssclass) {
+	_gel_image_height = my_detection.get_gel_image_height();
+	_gel_image_width = my_detection.get_gel_image_width();
+	if (_gel_image_writed == false) {
+		this->write_gel_image();
+		_gel_image_writed = true;
+	}
+	//dessine les étiquettes des spots en SVG
+	_p_xml_stream->writeStartElement("g");
+	//*this << "\t\t<g>";
+	detection::iterator it;
+	for (it = my_detection.begin(); it != my_detection.end(); ++it) {
+		spot & the_spot = (*it).second;
+		write_spot_number(the_spot, cssclass);
+	}
+	_p_xml_stream->writeEndElement();
+	//*this << "\t\t</g>";
+}
+
+void spotSvgDocument::embed_gel_image(const QString & gel_image_file_name) {
+	const imageIntensity gel_image(gel_image_file_name);
+	embed_gel_image(gel_image);
+}
+
+void spotSvgDocument::embed_gel_image(const imageIntensity & gel_image) {
+	qDebug() << "spotSvgDocument::embed_gel_image begin";
+	//gel_image.save("tmp.tiff");
+	gel_image.save("tmp.jpg");
+
+	this->_gel_image_width = gel_image.width();
+	this->_gel_image_height = gel_image.height();
+
+	ifstream file("tmp.jpg", ios::binary | ios::ate);
+	// Since we are at the end, we can get the size
+	//file.seekg(0,ifstream::end);
+	long size = file.tellg();
+	//cout << size;
+	file.seekg(0, ios::beg); //Seek back to the beginning
+	char* unsorted;
+	//Size input and read the file into it
+	unsorted = new char[size];
+	file.read(unsorted, size);
+	file.close();
+	unlink("tmp.jpg");
+	// Change size so it is the number of floats instead of
+	// the number of characters
+	//size = size / 4;
+
+	//const std::string s = "ADP GmbH\nAnalyse Design & Programmierung\nGesellschaft mit beschränkter Haftung" ;
+	qDebug() << "spotSvgDocument::embed_gel_image base64_encode";
+	this->_encoded_gel_image = base64_encode((const unsigned char *) unsorted,
+			size).c_str();
+
+	this->_embed_gel_image = true;
+	qDebug() << "spotSvgDocument::embed_gel_image end";
+
+}
+
diff --git a/src/spotSvgDocument.h b/src/spotSvgDocument.h
new file mode 100644
index 0000000..f8c762b
--- /dev/null
+++ b/src/spotSvgDocument.h
@@ -0,0 +1,69 @@
+#ifndef SPOTSVGDOCUMENT_H_
+#define SPOTSVGDOCUMENT_H_
+
+#include <QXmlStreamWriter>
+
+#include <map>
+#include "spotDocument.h"
+
+class spotSvgDocument: public spotDocument {
+public:
+	spotSvgDocument();
+	~spotSvgDocument() {
+	}
+
+	void open(const QString & filename);
+	virtual void close();
+
+	void embed_gel_image() {
+		embed_gel_image(get_gel_image_file_name());
+	}
+
+	void embed_gel_image(const imageIntensity & gel_image);
+	void embed_gel_image(const QString & gel_image_file_name);
+
+	void write_detection(const detection & the_detection,
+			const QString & cssclass);
+	void
+	draw_contours(const detection & my_detection, const QString & cssclass);
+	void draw_spot_numbers(detection & my_detection, const QString & cssclass);
+
+	void set_css_class(const QString & css_class_name,
+			const QString & css_style);
+
+protected:
+
+	QXmlStreamWriter * _p_xml_stream;
+
+private:
+
+	void write_gel_image();
+
+	void write_spot(const spot & thespot, const QString & cssclass);
+	void
+	write_spot_contours(const spot & thespot, const QString & cssclass);
+	void write_spot_number(const spot & thespot, const QString & cssclass);
+
+	std::map<QString, QString> _css_classes;
+
+	QString _encoded_gel_image;
+	int _gel_image_width;
+	int _gel_image_height;
+
+	bool _embed_gel_image;
+
+	bool _gel_image_writed;
+
+};
+
+/*
+ * image could be embedded in the svg file using base 64 encoding :
+
+ <image width="962.000000" height="725.000000" xlink:href="data:image/jpg;base64,
+ iVBORw0KGgoAAAANSUhEUgAAA8IAAALVCAIAAACELc9tAAFNr0lEQVR42uy9B3Qb1523zX2zb+LE
+ AAAAAABAowEAAAAAAAiD/w9XlqS2KZ32NgAAAABJRU5ErkJggg==
+ " />
+ * 
+ * */
+
+#endif /*SPOTSVGDOCUMENT_H_*/
diff --git a/src/spot_document_gnumeric.cpp b/src/spot_document_gnumeric.cpp
new file mode 100644
index 0000000..60e6d03
--- /dev/null
+++ b/src/spot_document_gnumeric.cpp
@@ -0,0 +1,412 @@
+/*
+ * spot_document_gnumeric.cpp
+ *
+ *  Created on: 12 mars 2010
+ *      Author: olivier
+ */
+
+#include <iostream>
+#include <QFileInfo>
+
+#include "spot_document_gnumeric.h"
+
+SpotDocumentGnumeric::SpotDocumentGnumeric() {
+	irow = 0;
+}
+
+SpotDocumentGnumeric::~SpotDocumentGnumeric() {
+	irow = 0;
+}
+
+void SpotDocumentGnumeric::open(const QString & filename) {
+	QString output_filename(filename);
+	QFileInfo fileinfo(filename);
+	QString ext = fileinfo.suffix();
+	if (ext != "txt") {
+		output_filename = output_filename + ".txt";
+	}
+
+	_output_file = new QFile(output_filename);
+
+	if (_output_file->exists()) {
+		cerr << "WARNING : " << _output_file->fileName().toStdString()
+				<< " output file already exists" << endl;
+	}
+	if (_output_file->open(QIODevice::WriteOnly)) {
+		_output_stream = new QTextStream(_output_file);
+		_output_stream ->setLocale(QLocale::system());
+	} else {
+		//throw exception
+	}
+
+	*_output_stream << "Spot Volume x y min surface background vol-bckgnd\n";
+
+	irow = 0;
+	_output_file = new QFile(filename);
+	_output_stream = NULL;
+
+	if (_output_file->exists()) {
+		cerr << "WARNING : " << _output_file->fileName().toStdString()
+				<< " gnumeric file already exists" << endl;
+	}
+	if (_output_file->open(QIODevice::WriteOnly)) {
+
+		_p_xml_stream = new QXmlStreamWriter(_output_file);
+	} else {
+		//throw exception
+	}
+
+	_p_xml_stream->setAutoFormatting(true);
+	_p_xml_stream->writeStartDocument();
+	_p_xml_stream->writeNamespace("http://www.gnumeric.org/v10.dtd", "gnm");
+	_p_xml_stream->writeNamespace("http://www.w3.org/2001/XMLSchema-instance",
+			"xsi");
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Workbook");
+	_p_xml_stream->writeAttribute("http://www.w3.org/2001/XMLSchema-instance",
+			"schemaLocation", "http://www.gnumeric.org/v9.xsd");
+	//<gnm:Version Epoch="1" Major="9" Minor="9" Full="1.9.9"/>
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Version");
+	_p_xml_stream->writeAttribute("Epoch", "1");
+	_p_xml_stream->writeAttribute("Major", "9");
+	_p_xml_stream->writeAttribute("Minor", "9");
+	_p_xml_stream->writeAttribute("Full", "1.9.9");
+	_p_xml_stream->writeEndElement();
+	//<gnm:Attributes>
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Attributes");
+	//<gnm:Attribute>
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Attribute");
+	//<gnm:type>4</gnm:type>
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "type",
+			"4");
+	//<gnm:name>WorkbookView::show_horizontal_scrollbar</gnm:name>
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "name",
+			"WorkbookView::show_horizontal_scrollbar");
+	//<gnm:value>TRUE</gnm:value>
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "value",
+			"TRUE");
+	_p_xml_stream->writeEndElement();
+
+	/*
+	 *     <gnm:Attribute>
+	 <gnm:type>4</gnm:type>
+	 <gnm:name>WorkbookView::show_vertical_scrollbar</gnm:name>
+	 <gnm:value>TRUE</gnm:value>
+	 </gnm:Attribute>
+	 */
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Attribute");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "type",
+			"4");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "name",
+			"WorkbookView::show_vertical_scrollbar");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "value",
+			"TRUE");
+	_p_xml_stream->writeEndElement();
+
+	/*
+	 <gnm:Attribute>
+	 <gnm:type>4</gnm:type>
+	 <gnm:name>WorkbookView::show_notebook_tabs</gnm:name>
+	 <gnm:value>TRUE</gnm:value>
+	 </gnm:Attribute>
+	 */
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Attribute");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "type",
+			"4");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "name",
+			"WorkbookView::show_notebook_tabs");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "value",
+			"TRUE");
+	_p_xml_stream->writeEndElement();
+	/*
+	 <gnm:Attribute>
+	 <gnm:type>4</gnm:type>
+	 <gnm:name>WorkbookView::do_auto_completion</gnm:name>
+	 <gnm:value>TRUE</gnm:value>
+	 </gnm:Attribute>
+	 */
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Attribute");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "type",
+			"4");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "name",
+			"WorkbookView::do_auto_completion");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "value",
+			"TRUE");
+	_p_xml_stream->writeEndElement();
+	/*
+	 <gnm:Attribute>
+	 <gnm:type>4</gnm:type>
+	 <gnm:name>WorkbookView::is_protected</gnm:name>
+	 <gnm:value>FALSE</gnm:value>
+	 </gnm:Attribute>
+	 *
+	 */
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Attribute");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "type",
+			"4");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "name",
+			"WorkbookView::is_protected");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "value",
+			"FALSE");
+	_p_xml_stream->writeEndElement();
+	//gnm:Attributes
+	_p_xml_stream->writeEndElement();
+
+	/*
+	 *   <office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
+	 *   xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"
+	 *   xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
+	 *   xmlns:ooo="http://openoffice.org/2004/office" office:version="1.1">
+	 <office:meta/>
+	 </office:document-meta>
+	 *
+	 */
+	_p_xml_stream->writeNamespace(
+			"urn:oasis:names:tc:opendocument:xmlns:office:1.0", "office");
+	_p_xml_stream->writeNamespace("http://www.w3.org/1999/xlink", "xlink");
+	_p_xml_stream->writeNamespace("http://purl.org/dc/elements/1.1/", "dc");
+	_p_xml_stream->writeNamespace(
+			"urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "meta");
+	_p_xml_stream->writeNamespace("http://openoffice.org/2004/office", "ooo");
+	_p_xml_stream->writeStartElement(
+			"urn:oasis:names:tc:opendocument:xmlns:office:1.0", "document-meta");
+	_p_xml_stream->writeAttribute(
+			"urn:oasis:names:tc:opendocument:xmlns:office:1.0", "version",
+			"1.1");
+	_p_xml_stream->writeStartElement(
+			"urn:oasis:names:tc:opendocument:xmlns:office:1.0", "meta");
+	_p_xml_stream->writeEndElement();
+	_p_xml_stream->writeEndElement();
+
+	//  <gnm:Calculation ManualRecalc="0" EnableIteration="1" MaxIterations="100"
+	//IterationTolerance="0.001" FloatRadix="2" FloatDigits="53"/>
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Calculation");
+	_p_xml_stream->writeAttribute("ManualRecalc", "0");
+	_p_xml_stream->writeAttribute("EnableIteration", "1");
+	_p_xml_stream->writeAttribute("MaxIterations", "100");
+	_p_xml_stream->writeAttribute("IterationTolerance", "0.001");
+	_p_xml_stream->writeAttribute("FloatRadix", "2");
+	_p_xml_stream->writeAttribute("FloatDigits", "53");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Summary");
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Item");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "name",
+			"biology softwares");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd",
+			"val-string", "http://pappso.inra.fr/");
+	_p_xml_stream->writeEndElement();
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Item");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "name",
+			"author");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd",
+			"val-string", "Olivier Langella <Olivier.Langella at moulon.inra.fr>");
+	_p_xml_stream->writeEndElement();
+	_p_xml_stream->writeEndElement();
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"SheetNameIndex");
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd",
+			"SheetName", "beads");
+	_p_xml_stream->writeEndElement();
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd",
+			"Sheets");
+
+	//QString sheetname("Matrix 1");
+	//oGnumericSheet(xml_stream, sheetname);
+	/*fichier << "<gmr:Sheet ";
+	 fichier
+	 << "DisplayFormulas=\"false\" HideZero=\"false\" HideGrid=\"false\" HideColHeader=\"false\" HideRowHeader=\"false\" DisplayOutlines=\"true\" OutlineSymbolsBelow=\"true\" OutlineSymbolsRight=\"true\">"
+	 << endl;*/
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Sheet");
+	_p_xml_stream->writeAttribute("DisplayFormulas", "false");
+	_p_xml_stream->writeAttribute("HideZero", "false");
+	_p_xml_stream->writeAttribute("HideGrid", "false");
+	_p_xml_stream->writeAttribute("HideColHeader", "false");
+	_p_xml_stream->writeAttribute("HideRowHeader", "false");
+	_p_xml_stream->writeAttribute("DisplayOutlines", "true");
+	_p_xml_stream->writeAttribute("OutlineSymbolsBelow", "true");
+	_p_xml_stream->writeAttribute("OutlineSymbolsRight", "true");
+
+	/*_p_xml_stream->writeAttribute("xml", "lang", "en");
+	 _p_xml_stream->writeStartElement("body");
+	 _p_xml_stream->writeStartElement("table");
+	 _p_xml_stream->writeStartElement("tbody");*/
+
+	// 	fichier.imbue();
+	//	fichier << "<gmr:Name>" << sheetname << "</gmr:Name>" << endl;
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "Name",
+			"beads");
+
+	//fichier << "<gmr:MaxCol>" << (GetNC() + 2) << "</gmr:MaxCol>" << endl;
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd",
+			"MaxCol", "0");
+	//fichier << "<gmr:MaxRow>" << (GetNL() + 5 + _titre.size())
+	//		<< "</gmr:MaxRow>" << endl;
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd",
+			"MaxRow", "0");
+	//fichier << "<gmr:Zoom>1.000000</gmr:Zoom>" << endl;
+	_p_xml_stream->writeTextElement("http://www.gnumeric.org/v10.dtd", "Zoom",
+			"1.000000");
+
+	//fichier << "<gmr:Names/>" << endl;
+	_p_xml_stream->writeEmptyElement("http://www.gnumeric.org/v10.dtd", "Names");
+
+	//fichier << "<gmr:Cells>" << endl;
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cells");
+
+	//*_output_stream << "Spot Volume x y min surface background vol-bckgnd\n";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "0");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("Spot");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "1");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("Volume");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "2");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("x");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "3");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("y");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "4");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("min");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "5");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("surface");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "6");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("background");
+	_p_xml_stream->writeEndElement();
+
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", "0");
+	_p_xml_stream->writeAttribute("Col", "7");
+	_p_xml_stream->writeAttribute("ValueType", "60");
+	_p_xml_stream->writeCharacters("vol-bckgnd");
+	_p_xml_stream->writeEndElement();
+
+	irow++;
+
+}
+
+void SpotDocumentGnumeric::write_spot(const spot & thespot) {
+	//std::ostringstream tag;
+	//*_output_stream << thespot.get_number() << " ";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "0");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_number()));
+	_p_xml_stream->writeEndElement();
+
+	//*_output_stream << thespot.get_vol() << " ";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "1");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_vol()));
+	_p_xml_stream->writeEndElement();
+
+	//*_output_stream << thespot.get_tx() << " ";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "2");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_tx()));
+	_p_xml_stream->writeEndElement();
+
+	//*_output_stream << thespot.get_ty() << " ";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "3");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_ty()));
+	_p_xml_stream->writeEndElement();
+
+	//*_output_stream << thespot.get_tmin() << " ";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "4");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_tmin()));
+	_p_xml_stream->writeEndElement();
+
+	//*_output_stream << thespot.get_area() << " ";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "5");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_area()));
+	_p_xml_stream->writeEndElement();
+
+	//*_output_stream << thespot.get_bckgnd() << " ";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "6");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_bckgnd()));
+	_p_xml_stream->writeEndElement();
+
+	//*_output_stream << (thespot.get_vol() - thespot.get_bckgnd()) << "\n";
+	_p_xml_stream->writeStartElement("http://www.gnumeric.org/v10.dtd", "Cell");
+	_p_xml_stream->writeAttribute("Row", QString::number(irow));
+	_p_xml_stream->writeAttribute("Col", "7");
+	_p_xml_stream->writeAttribute("ValueType", "40");
+	_p_xml_stream->writeCharacters(QString::number(thespot.get_vol()
+			- thespot.get_bckgnd()));
+	_p_xml_stream->writeEndElement();
+
+	irow++;
+	//return (tag.str().c_str());
+}
+
+void SpotDocumentGnumeric::close() {
+	qDebug() << "SpotDocumentGnumeric::close begin";
+	_p_xml_stream->writeEndDocument();
+	if (_output_stream != NULL) {
+		_output_stream->flush();
+		delete (_output_stream);
+	}
+	if (_output_file != NULL) {
+		_output_file->close();
+		delete (_output_file);
+	}
+	_output_file = NULL;
+	_output_stream = NULL;
+	qDebug() << "SpotDocumentGnumeric::close end";
+}
diff --git a/src/spot_document_gnumeric.h b/src/spot_document_gnumeric.h
new file mode 100644
index 0000000..97194b9
--- /dev/null
+++ b/src/spot_document_gnumeric.h
@@ -0,0 +1,32 @@
+/*
+ * spot_document_gnumeric.h
+ *
+ *  Created on: 12 mars 2010
+ *      Author: olivier
+ */
+
+#ifndef SPOT_DOCUMENT_GNUMERIC_H_
+#define SPOT_DOCUMENT_GNUMERIC_H_
+
+#include <QXmlStreamWriter>
+
+#include "spotDocument.h"
+
+class SpotDocumentGnumeric: public spotDocument {
+public:
+	SpotDocumentGnumeric();
+	virtual ~SpotDocumentGnumeric();
+
+	virtual void open(const QString & filename);
+	virtual void close();
+
+	//virtual void write_detection(detection & the_detection);
+	virtual void write_spot(const spot &);
+
+private:
+	int irow;
+	QXmlStreamWriter * _p_xml_stream;
+
+};
+
+#endif /* SPOT_DOCUMENT_GNUMERIC_H_ */

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/beads.git



More information about the debian-med-commit mailing list