summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEd Tanous <ed.tanous@intel.com>2017-04-18 03:52:36 +0300
committerEd Tanous <ed.tanous@intel.com>2017-04-18 03:52:36 +0300
commit93f987d2383258f88fe9b9133af956bcc0f00233 (patch)
tree88bc31b3cbb02b2c372fc48de3010e3b4d7b039b
parent5679522b4bd573b0559a4508963fa7ac6224df28 (diff)
downloadbmcweb-93f987d2383258f88fe9b9133af956bcc0f00233.tar.xz
KVM WORKING! ! !
-rw-r--r--.clang-format2
-rw-r--r--CMakeLists.txt21
-rw-r--r--docs/profile.md5
-rw-r--r--include/CImg.h60168
-rw-r--r--include/aspeed/JTABLES.H303
-rw-r--r--include/ast_jpeg_decoder.hpp1386
-rw-r--r--include/ast_video_puller.hpp80
-rw-r--r--include/ast_video_types.hpp18
-rw-r--r--include/web_kvm.hpp52
-rw-r--r--src/ast_jpeg_decoder_test.cpp156
-rw-r--r--src/getvideo_main.cpp162
-rw-r--r--src/test_resources/aspeedblackscreen.binbin0 -> 27660 bytes
-rw-r--r--src/test_resources/aspeedbluescreen.binbin0 -> 29500 bytes
-rw-r--r--src/test_resources/ubuntu_444_800x600_0chrom_0lum.binbin0 -> 31648 bytes
-rw-r--r--src/token_authorization_middleware.cpp4
-rw-r--r--static/CMakeLists.txt71
16 files changed, 62264 insertions, 164 deletions
diff --git a/.clang-format b/.clang-format
index 17d370f05a..5f8caed922 100644
--- a/.clang-format
+++ b/.clang-format
@@ -72,7 +72,7 @@ PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
-SortIncludes: true
+SortIncludes: false
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9c0ff404ba..01dd5bd2f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,6 +6,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
message("${CMAKE_MODULE_PATH}")
+SET(BUILD_SHARED_LIBRARIES OFF)
+
#set(HUNTER_ROOT /home/ed/hunter)
#SET(HUNTER_STATUS_DEBUG ON)
@@ -43,7 +45,7 @@ endif()
# general
option(BUILD_SHARED_LIBS "Build as shared library" OFF)
-option(BUILD_UT "Enable Unit test" OFF)
+option(BUILD_UT "Enable Unit test" ON)
# This needs to be before the crow and other module includes so headers get overriden correctly
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
@@ -96,6 +98,12 @@ set_target_properties(g3logger PROPERTIES
CLEAN_DIRECT_OUTPUT 1)
target_link_libraries(g3logger ${PLATFORM_LINK_LIBRIES})
+#lib jpeg
+set(BUILD_STATIC ON)
+#include_directories(libjpeg)
+#include_directories(${CMAKE_CURRENT_BINARY_DIR}/libjpeg)
+#add_subdirectory(libjpeg)
+
# Crow
add_definitions(-DCROW_ENABLE_SSL)
@@ -161,12 +169,16 @@ set(UT_FILES
src/security_headers_middleware_test.cpp
src/webassets_test.cpp
src/crow_getroutes_test.cpp
+ src/ast_jpeg_decoder_test.cpp
${CMAKE_BINARY_DIR}/generated/blns.hpp
)
+
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/generated")
+file(COPY src/test_resources DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
# Unit Tests
if(${BUILD_UT})
# big list of naughty strings
@@ -200,16 +212,17 @@ target_link_libraries(bmcweb g3logger)
target_link_libraries(bmcweb ${ZLIB_LIBRARIES})
add_dependencies(bmcweb packagestaticcpp)
-# bmcweb
+# udpclient
add_executable(udpclient src/udpclient.cpp)
target_link_libraries(udpclient Boost::system)
target_link_libraries(udpclient ${CMAKE_THREAD_LIBS_INIT})
-
add_executable(getvideo src/getvideo_main.cpp)
+target_link_libraries(getvideo ${CMAKE_THREAD_LIBS_INIT})
# Visual Studio Code helper
# this needs to be at the end to make sure all includes are handled correctly
+
get_property(C_INCLUDE_DIRS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
-execute_process(COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/scripts/prime_vscode_compile_db.py ${C_INCLUDE_DIRS}) \ No newline at end of file
+execute_process(COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/scripts/prime_vscode_compile_db.py ${C_INCLUDE_DIRS} ${C_SYSTEM_INCLUDE_DIRS}) \ No newline at end of file
diff --git a/docs/profile.md b/docs/profile.md
index c1b8aa0a11..b6dcf98885 100644
--- a/docs/profile.md
+++ b/docs/profile.md
@@ -9,4 +9,7 @@ echo 8 > /proc/sys/kernel/printk
export HOME=/nv
-scp ed@hades.jf.intel.com:/home/ed/webserver/buildarm/getvideo /tmp -i /nv/.ssh/id_rsa && cd /tmp && ./getvideo && scp screen.jpg ed@hades.jf.intel.com:~/screen.foo -i /nv/.ssh/id_rsa \ No newline at end of file
+scp ed@hades.jf.intel.com:/home/ed/webserver/buildarm/getvideo /tmp -i /nv/.ssh/id_rsa && cd /tmp && ./getvideo && scp screen.jpg ed@hades.jf.intel.com:~/screen.foo -i /nv/.ssh/id_rsa
+
+
+kill -12 1480 \ No newline at end of file
diff --git a/include/CImg.h b/include/CImg.h
new file mode 100644
index 0000000000..152f0ac88f
--- /dev/null
+++ b/include/CImg.h
@@ -0,0 +1,60168 @@
+/*
+ #
+ # 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.eu )
+ #
+ # Project manager : David Tschumperle.
+ # ( http://tschumperle.users.greyc.fr/ )
+ #
+ # A complete list of contributors is available in file 'README.txt'
+ # distributed within the CImg package.
+ #
+ # Licenses : This file is 'dual-licensed', you have to choose one
+ # of the two licenses below to apply.
+ #
+ # CeCILL-C
+ # The CeCILL-C license is close to the GNU LGPL.
+ # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html )
+ #
+ # or CeCILL v2.0
+ # The CeCILL license is compatible with the GNU GPL.
+ # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
+ #
+ # This software is governed either by the CeCILL or the CeCILL-C license
+ # under French law and abiding by the rules of distribution of free software.
+ # You can use, modify and or redistribute the software under the terms of
+ # the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA
+ # at the following URL: "http://www.cecill.info".
+ #
+ # As a counterpart to the access to the source code and rights to copy,
+ # modify and redistribute granted by the license, users are provided only
+ # with a limited warranty and the software's author, the holder of the
+ # economic rights, and the successive licensors have only limited
+ # liability.
+ #
+ # In this respect, the user's attention is drawn to the risks associated
+ # with loading, using, modifying and/or developing or reproducing the
+ # software by the user in light of its specific status of free software,
+ # that may mean that it is complicated to manipulate, and that also
+ # therefore means that it is reserved for developers and experienced
+ # professionals having in-depth computer knowledge. Users are therefore
+ # encouraged to load and test the software's suitability as regards their
+ # requirements in conditions enabling the security of their systems and/or
+ # data to be ensured and, more generally, to use and operate it in the
+ # same conditions as regards security.
+ #
+ # The fact that you are presently reading this means that you have had
+ # knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms.
+ #
+*/
+
+// Set version number of the library.
+#ifndef cimg_version
+#define cimg_version 200
+
+/*-----------------------------------------------------------
+ #
+ # Test and possibly auto-set CImg configuration variables
+ # and include required headers.
+ #
+ # If you find that the default configuration variables are
+ # not adapted to your system, you can override their values
+ # before including the header file "CImg.h"
+ # (use the #define directive).
+ #
+ ------------------------------------------------------------*/
+
+// Include standard C++ headers.
+// This is the minimal set of required headers to make CImg-based codes compile.
+#include <cstdio>
+#include <cstdlib>
+#include <cstdarg>
+#include <cstring>
+#include <cmath>
+#include <cfloat>
+#include <climits>
+#include <ctime>
+#include <exception>
+#include <algorithm>
+
+// Detect/configure OS variables.
+//
+// Define 'cimg_OS' to: '0' for an unknown OS (will try to minize library dependencies).
+// '1' for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...).
+// '2' for Microsoft Windows.
+// (auto-detection is performed if 'cimg_OS' is not set by the user).
+#ifndef cimg_OS
+#if defined(unix) || defined(__unix) || defined(__unix__) \
+ || defined(linux) || defined(__linux) || defined(__linux__) \
+ || defined(sun) || defined(__sun) \
+ || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \
+ || defined(__FreeBSD__) || defined (__DragonFly__) \
+ || defined(sgi) || defined(__sgi) \
+ || defined(__MACOSX__) || defined(__APPLE__) \
+ || defined(__CYGWIN__)
+#define cimg_OS 1
+#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \
+ || defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
+#define cimg_OS 2
+#else
+#define cimg_OS 0
+#endif
+#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2)
+#error CImg Library: Invalid configuration variable 'cimg_OS'.
+#error (correct values are '0 = unknown OS', '1 = Unix-like OS', '2 = Microsoft Windows').
+#endif
+#ifndef cimg_date
+#define cimg_date __DATE__
+#endif
+#ifndef cimg_time
+#define cimg_time __TIME__
+#endif
+
+// Disable silly warnings on some Microsoft VC++ compilers.
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4127)
+#pragma warning(disable:4244)
+#pragma warning(disable:4311)
+#pragma warning(disable:4312)
+#pragma warning(disable:4512)
+#pragma warning(disable:4571)
+#pragma warning(disable:4640)
+#pragma warning(disable:4706)
+#pragma warning(disable:4710)
+#pragma warning(disable:4800)
+#pragma warning(disable:4804)
+#pragma warning(disable:4820)
+#pragma warning(disable:4996)
+#define _CRT_SECURE_NO_DEPRECATE 1
+#define _CRT_SECURE_NO_WARNINGS 1
+#define _CRT_NONSTDC_NO_DEPRECATE 1
+#endif
+
+// Define correct string functions for each compiler and OS.
+#if cimg_OS==2 && defined(_MSC_VER)
+#define cimg_sscanf std::sscanf
+#define cimg_sprintf std::sprintf
+#define cimg_snprintf cimg::_snprintf
+#define cimg_vsnprintf cimg::_vsnprintf
+#else
+#include <stdio.h>
+#if defined(__MACOSX__) || defined(__APPLE__)
+#define cimg_sscanf cimg::_sscanf
+#define cimg_sprintf cimg::_sprintf
+#define cimg_snprintf cimg::_snprintf
+#define cimg_vsnprintf cimg::_vsnprintf
+#else
+#define cimg_sscanf std::sscanf
+#define cimg_sprintf std::sprintf
+#define cimg_snprintf snprintf
+#define cimg_vsnprintf vsnprintf
+#endif
+#endif
+
+// Include OS-specific headers.
+#if cimg_OS==1
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#elif cimg_OS==2
+#ifndef std_fopen
+#define std_fopen cimg::win_fopen
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#ifndef _WIN32_IE
+#define _WIN32_IE 0x0400
+#endif
+#include <shlobj.h>
+#include <process.h>
+#include <io.h>
+#endif
+
+// Look for C++11 features.
+#ifndef cimg_use_cpp11
+#if __cplusplus>201100
+#define cimg_use_cpp11 1
+#else
+#define cimg_use_cpp11 0
+#endif
+#endif
+#if cimg_use_cpp11==1
+#include <initializer_list>
+#include <utility>
+#endif
+
+// Convenient macro to define pragma
+#ifdef _MSC_VER
+#define cimg_pragma(x) __pragma(x)
+#else
+#define cimg_pragma(x) _Pragma(#x)
+#endif
+
+// Define own types 'cimg_long/ulong' and 'cimg_int64/uint64' to ensure portability.
+// ( constrained to 'sizeof(cimg_ulong/cimg_long) = sizeof(void*)' and 'sizeof(cimg_int64/cimg_uint64)=8' ).
+#if cimg_OS==2
+
+#define cimg_uint64 unsigned __int64
+#define cimg_int64 __int64
+#define cimg_ulong UINT_PTR
+#define cimg_long INT_PTR
+#ifdef _MSC_VER
+#define cimg_fuint64 "%I64u"
+#define cimg_fint64 "%I64d"
+#else
+#define cimg_fuint64 "%llu"
+#define cimg_fint64 "%lld"
+#endif
+
+#else
+
+#if UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM)
+#define cimg_uint64 unsigned long long
+#define cimg_int64 long long
+#define cimg_fuint64 "%llu"
+#define cimg_fint64 "%lld"
+#else
+#define cimg_uint64 unsigned long
+#define cimg_int64 long
+#define cimg_fuint64 "%lu"
+#define cimg_fint64 "%ld"
+#endif
+
+#if defined(__arm__) || defined(_M_ARM)
+#define cimg_ulong unsigned long long
+#define cimg_long long long
+#else
+#define cimg_ulong unsigned long
+#define cimg_long long
+#endif
+
+#endif
+
+// Configure filename separator.
+//
+// Filename separator is set by default to '/', except for Windows where it is '\'.
+#ifndef cimg_file_separator
+#if cimg_OS==2
+#define cimg_file_separator '\\'
+#else
+#define cimg_file_separator '/'
+#endif
+#endif
+
+// Configure verbosity of output messages.
+//
+// Define 'cimg_verbosity' to: '0' to hide library messages (quiet mode).
+// '1' to output library messages on the console.
+// '2' to output library messages on a basic 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 on VT100-compatible terminals.
+#ifndef cimg_verbosity
+#if cimg_OS==2
+#define cimg_verbosity 2
+#else
+#define cimg_verbosity 1
+#endif
+#elif !(cimg_verbosity==0 || cimg_verbosity==1 || cimg_verbosity==2 || cimg_verbosity==3 || cimg_verbosity==4)
+#error CImg Library: Configuration variable 'cimg_verbosity' is badly defined.
+#error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }).
+#endif
+
+// Configure display framework.
+//
+// Define 'cimg_display' to: '0' to disable display capabilities.
+// '1' to use the X-Window framework (X11).
+// '2' to use the Microsoft GDI32 framework.
+#ifndef cimg_display
+#if cimg_OS==0
+#define cimg_display 0
+#elif cimg_OS==1
+#define cimg_display 1
+#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
+
+// Configure the 'abort' signal handler (does nothing by default).
+// A typical signal handler can be defined in your own source like this:
+// #define cimg_abort_test() if (is_abort) throw CImgAbortException("")
+//
+// where 'is_abort' is a boolean variable defined somewhere in your code and reachable in the method.
+// 'cimg_abort_test2()' does the same but is called more often (in inner loops).
+#if defined(cimg_abort_test) && defined(cimg_use_openmp)
+
+// Define abort macros to be used with OpenMP.
+#ifndef cimg_abort_init
+#define cimg_abort_init bool cimg_abort_go = true; cimg::unused(cimg_abort_go)
+#endif
+#ifndef cimg_abort_try
+#define cimg_abort_try if (cimg_abort_go) try
+#endif
+#ifndef cimg_abort_catch
+#define cimg_abort_catch() catch (CImgAbortException&) { cimg_pragma(omp atomic) cimg_abort_go&=false; }
+#endif
+#ifdef cimg_abort_test2
+#ifndef cimg_abort_try2
+#define cimg_abort_try2 cimg_abort_try
+#endif
+#ifndef cimg_abort_catch2
+#define cimg_abort_catch2() cimg_abort_catch()
+#endif
+#ifndef cimg_abort_catch_fill
+#define cimg_abort_catch_fill() \
+ catch (CImgException& e) { cimg_pragma(omp critical) CImg<charT>::string(e._message).move_to(is_error); \
+ cimg_pragma(omp atomic) cimg_abort_go&=false; }
+#endif
+#endif
+
+#endif
+
+#ifndef cimg_abort_test
+#define cimg_abort_test()
+#endif
+#ifndef cimg_abort_test2
+#define cimg_abort_test2()
+#endif
+#ifndef cimg_abort_init
+#define cimg_abort_init
+#endif
+#ifndef cimg_abort_try
+#define cimg_abort_try
+#endif
+#ifndef cimg_abort_catch
+#define cimg_abort_catch()
+#endif
+#ifndef cimg_abort_try2
+#define cimg_abort_try2
+#endif
+#ifndef cimg_abort_catch2
+#define cimg_abort_catch2()
+#endif
+#ifndef cimg_abort_catch_fill
+#define cimg_abort_catch_fill()
+#endif
+#ifndef std_fopen
+#define std_fopen std::fopen
+#endif
+
+// Include display-specific headers.
+#if cimg_display==1
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <pthread.h>
+#ifdef cimg_use_xshm
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+#ifdef cimg_use_xrandr
+#include <X11/extensions/Xrandr.h>
+#endif
+#endif
+#ifndef cimg_appname
+#define cimg_appname "CImg"
+#endif
+
+// Configure OpenMP support.
+// (http://www.openmp.org)
+//
+// Define 'cimg_use_openmp' to enable OpenMP support.
+//
+// OpenMP directives may be used in a (very) few CImg functions to get
+// advantages of multi-core CPUs.
+#ifdef cimg_use_openmp
+#include <omp.h>
+#define cimg_pragma_openmp(p) cimg_pragma(omp p)
+#else
+#define cimg_pragma_openmp(p)
+#endif
+
+// Configure OpenCV support.
+// (http://opencv.willowgarage.com/wiki/)
+//
+// Define 'cimg_use_opencv' to enable OpenCV support.
+//
+// OpenCV library may be used to access images from cameras
+// (see method 'CImg<T>::load_camera()').
+#ifdef cimg_use_opencv
+#ifdef True
+#undef True
+#define _cimg_redefine_True
+#endif
+#ifdef False
+#undef False
+#define _cimg_redefine_False
+#endif
+#include <cstddef>
+#include "cv.h"
+#include "highgui.h"
+#endif
+
+// Configure LibPNG support.
+// (http://www.libpng.org)
+//
+// Define 'cimg_use_png' to enable LibPNG support.
+//
+// PNG library may be used to get a native support of '.png' files.
+// (see methods 'CImg<T>::{load,save}_png()'.
+#ifdef cimg_use_png
+extern "C" {
+#include "png.h"
+}
+#endif
+
+// Configure LibJPEG support.
+// (http://en.wikipedia.org/wiki/Libjpeg)
+//
+// Define 'cimg_use_jpeg' to enable LibJPEG support.
+//
+// JPEG library may be used to get a native support of '.jpg' files.
+// (see methods 'CImg<T>::{load,save}_jpeg()').
+#ifdef cimg_use_jpeg
+extern "C" {
+#include "jpeglib.h"
+#include "setjmp.h"
+}
+#endif
+
+// Configure LibTIFF support.
+// (http://www.libtiff.org)
+//
+// Define 'cimg_use_tiff' to enable LibTIFF support.
+//
+// TIFF library may be used to get a native support of '.tif' files.
+// (see methods 'CImg[List]<T>::{load,save}_tiff()').
+#ifdef cimg_use_tiff
+extern "C" {
+#define uint64 uint64_hack_
+#define int64 int64_hack_
+#include "tiffio.h"
+#undef uint64
+#undef int64
+}
+#endif
+
+// Configure LibMINC2 support.
+// (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference)
+//
+// Define 'cimg_use_minc2' to enable LibMINC2 support.
+//
+// MINC2 library may be used to get a native support of '.mnc' files.
+// (see methods 'CImg<T>::{load,save}_minc2()').
+#ifdef cimg_use_minc2
+#include "minc_io_simple_volume.h"
+#include "minc_1_simple.h"
+#include "minc_1_simple_rw.h"
+#endif
+
+// Configure Zlib support.
+// (http://www.zlib.net)
+//
+// Define 'cimg_use_zlib' to enable Zlib support.
+//
+// Zlib library may be used to allow compressed data in '.cimgz' files
+// (see methods 'CImg[List]<T>::{load,save}_cimg()').
+#ifdef cimg_use_zlib
+extern "C" {
+#include "zlib.h"
+}
+#endif
+
+// Configure libcurl support.
+// (http://curl.haxx.se/libcurl/)
+//
+// Define 'cimg_use_curl' to enable libcurl support.
+//
+// Libcurl may be used to get a native support of file downloading from the network.
+// (see method 'cimg::load_network()'.)
+#ifdef cimg_use_curl
+#include "curl/curl.h"
+#endif
+
+// Configure Magick++ support.
+// (http://www.imagemagick.org/Magick++)
+//
+// Define 'cimg_use_magick' to enable Magick++ support.
+//
+// Magick++ library may be used to get a native support of various image file formats.
+// (see methods 'CImg<T>::{load,save}()').
+#ifdef cimg_use_magick
+#include "Magick++.h"
+#endif
+
+// Configure FFTW3 support.
+// (http://www.fftw.org)
+//
+// Define 'cimg_use_fftw3' to enable libFFTW3 support.
+//
+// FFTW3 library may be used to efficiently compute the Fast Fourier Transform
+// of image data, without restriction on the image size.
+// (see method 'CImg[List]<T>::FFT()').
+#ifdef cimg_use_fftw3
+extern "C" {
+#include "fftw3.h"
+}
+#endif
+
+// Configure LibBoard support.
+// (http://libboard.sourceforge.net/)
+//
+// Define 'cimg_use_board' to enable Board support.
+//
+// Board library may be used to draw 3d objects in vector-graphics canvas
+// that can be saved as '.ps' or '.svg' files afterwards.
+// (see method 'CImg<T>::draw_object3d()').
+#ifdef cimg_use_board
+#ifdef None
+#undef None
+#define _cimg_redefine_None
+#endif
+#include "Board.h"
+#endif
+
+// Configure OpenEXR support.
+// (http://www.openexr.com/)
+//
+// Define 'cimg_use_openexr' to enable OpenEXR support.
+//
+// OpenEXR library may be used to get a native support of '.exr' files.
+// (see methods 'CImg<T>::{load,save}_exr()').
+#ifdef cimg_use_openexr
+#include "ImfRgbaFile.h"
+#include "ImfInputFile.h"
+#include "ImfChannelList.h"
+#include "ImfMatrixAttribute.h"
+#include "ImfArray.h"
+#endif
+
+// Configure TinyEXR support.
+// (https://github.com/syoyo/tinyexr)
+//
+// Define 'cimg_use_tinyexr' to enable TinyEXR support.
+//
+// TinyEXR is a small, single header-only library to load and save OpenEXR(.exr) images.
+#ifdef cimg_use_tinyexr
+#ifndef TINYEXR_IMPLEMENTATION
+#define TINYEXR_IMPLEMENTATION
+#endif
+#include "tinyexr.h"
+#endif
+
+// Lapack configuration.
+// (http://www.netlib.org/lapack)
+//
+// Define 'cimg_use_lapack' to enable LAPACK support.
+//
+// Lapack library may be used in several CImg methods to speed up
+// matrix computations (eigenvalues, inverse, ...).
+#ifdef cimg_use_lapack
+extern "C" {
+ extern void sgetrf_(int*, int*, float*, int*, int*, int*);
+ extern void sgetri_(int*, float*, int*, int*, float*, int*, int*);
+ extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*);
+ extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*);
+ extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*);
+ extern void dgetrf_(int*, int*, double*, int*, int*, int*);
+ extern void dgetri_(int*, double*, int*, int*, double*, int*, int*);
+ extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*);
+ extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*,
+ int*, double*, int*, double*, int*, int*);
+ extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*);
+ extern void dgels_(char*, int*,int*,int*,double*,int*,double*,int*,double*,int*,int*);
+ extern void sgels_(char*, int*,int*,int*,float*,int*,float*,int*,float*,int*,int*);
+}
+#endif
+
+// Check if min/max/PI macros are defined.
+//
+// CImg does not compile if macros 'min', 'max' or 'PI' are defined,
+// because it redefines functions min(), max() and const variable PI in the cimg:: namespace.
+// so it '#undef' these macros if necessary, and restore them to reasonable
+// values at the end of this file.
+#ifdef min
+#undef min
+#define _cimg_redefine_min
+#endif
+#ifdef max
+#undef max
+#define _cimg_redefine_max
+#endif
+#ifdef PI
+#undef PI
+#define _cimg_redefine_PI
+#endif
+
+// Define 'cimg_library' namespace suffix.
+//
+// You may want to add a suffix to the 'cimg_library' namespace, for instance if you need to work
+// with several versions of the library at the same time.
+#ifdef cimg_namespace_suffix
+#define __cimg_library_suffixed(s) cimg_library_##s
+#define _cimg_library_suffixed(s) __cimg_library_suffixed(s)
+#define cimg_library_suffixed _cimg_library_suffixed(cimg_namespace_suffix)
+#else
+#define cimg_library_suffixed cimg_library
+#endif
+
+/*------------------------------------------------------------------------------
+ #
+ # Define user-friendly macros.
+ #
+ # These CImg macros are prefixed by 'cimg_' and can be used safely in your own
+ # code. They are useful to parse command line options, or to write image loops.
+ #
+ ------------------------------------------------------------------------------*/
+
+// Macros to define program usage, and retrieve command line arguments.
+#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false)
+#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0)
+#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage)
+
+// Macros to define and manipulate local neighborhoods.
+#define CImg_2x2(I,T) T I[4]; \
+ T& I##cc = I[0]; T& I##nc = I[1]; \
+ T& I##cn = I[2]; T& I##nn = I[3]; \
+ I##cc = I##nc = \
+ I##cn = I##nn = 0
+
+#define CImg_3x3(I,T) T I[9]; \
+ T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \
+ T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \
+ T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \
+ I##pp = I##cp = I##np = \
+ I##pc = I##cc = I##nc = \
+ I##pn = I##cn = I##nn = 0
+
+#define CImg_4x4(I,T) T I[16]; \
+ T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \
+ T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \
+ T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \
+ T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \
+ I##pp = I##cp = I##np = I##ap = \
+ I##pc = I##cc = I##nc = I##ac = \
+ I##pn = I##cn = I##nn = I##an = \
+ I##pa = I##ca = I##na = I##aa = 0
+
+#define CImg_5x5(I,T) T I[25]; \
+ T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \
+ T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \
+ T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \
+ T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \
+ T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \
+ I##bb = I##pb = I##cb = I##nb = I##ab = \
+ I##bp = I##pp = I##cp = I##np = I##ap = \
+ I##bc = I##pc = I##cc = I##nc = I##ac = \
+ I##bn = I##pn = I##cn = I##nn = I##an = \
+ I##ba = I##pa = I##ca = I##na = I##aa = 0
+
+#define CImg_2x2x2(I,T) T I[8]; \
+ T& I##ccc = I[0]; T& I##ncc = I[1]; \
+ T& I##cnc = I[2]; T& I##nnc = I[3]; \
+ T& I##ccn = I[4]; T& I##ncn = I[5]; \
+ T& I##cnn = I[6]; T& I##nnn = I[7]; \
+ I##ccc = I##ncc = \
+ I##cnc = I##nnc = \
+ I##ccn = I##ncn = \
+ I##cnn = I##nnn = 0
+
+#define CImg_3x3x3(I,T) T I[27]; \
+ T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \
+ T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \
+ T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \
+ T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \
+ T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \
+ T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \
+ T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \
+ T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \
+ T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \
+ I##ppp = I##cpp = I##npp = \
+ I##pcp = I##ccp = I##ncp = \
+ I##pnp = I##cnp = I##nnp = \
+ I##ppc = I##cpc = I##npc = \
+ I##pcc = I##ccc = I##ncc = \
+ I##pnc = I##cnc = I##nnc = \
+ I##ppn = I##cpn = I##npn = \
+ I##pcn = I##ccn = I##ncn = \
+ I##pnn = I##cnn = I##nnn = 0
+
+#define cimg_get2x2(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \
+ I[3] = (T)(img)(_n1##x,_n1##y,z,c)
+
+#define cimg_get3x3(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \
+ I[3] = (T)(img)(_p1##x,y,z,c), I[4] = (T)(img)(x,y,z,c), I[5] = (T)(img)(_n1##x,y,z,c), \
+ I[6] = (T)(img)(_p1##x,_n1##y,z,c), I[7] = (T)(img)(x,_n1##y,z,c), I[8] = (T)(img)(_n1##x,_n1##y,z,c)
+
+#define cimg_get4x4(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p1##x,_p1##y,z,c), I[1] = (T)(img)(x,_p1##y,z,c), I[2] = (T)(img)(_n1##x,_p1##y,z,c), \
+ I[3] = (T)(img)(_n2##x,_p1##y,z,c), I[4] = (T)(img)(_p1##x,y,z,c), I[5] = (T)(img)(x,y,z,c), \
+ I[6] = (T)(img)(_n1##x,y,z,c), I[7] = (T)(img)(_n2##x,y,z,c), I[8] = (T)(img)(_p1##x,_n1##y,z,c), \
+ I[9] = (T)(img)(x,_n1##y,z,c), I[10] = (T)(img)(_n1##x,_n1##y,z,c), I[11] = (T)(img)(_n2##x,_n1##y,z,c), \
+ I[12] = (T)(img)(_p1##x,_n2##y,z,c), I[13] = (T)(img)(x,_n2##y,z,c), I[14] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[15] = (T)(img)(_n2##x,_n2##y,z,c)
+
+#define cimg_get5x5(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \
+ I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_p2##x,_p1##y,z,c), \
+ I[6] = (T)(img)(_p1##x,_p1##y,z,c), I[7] = (T)(img)(x,_p1##y,z,c), I[8] = (T)(img)(_n1##x,_p1##y,z,c), \
+ I[9] = (T)(img)(_n2##x,_p1##y,z,c), I[10] = (T)(img)(_p2##x,y,z,c), I[11] = (T)(img)(_p1##x,y,z,c), \
+ I[12] = (T)(img)(x,y,z,c), I[13] = (T)(img)(_n1##x,y,z,c), I[14] = (T)(img)(_n2##x,y,z,c), \
+ I[15] = (T)(img)(_p2##x,_n1##y,z,c), I[16] = (T)(img)(_p1##x,_n1##y,z,c), I[17] = (T)(img)(x,_n1##y,z,c), \
+ I[18] = (T)(img)(_n1##x,_n1##y,z,c), I[19] = (T)(img)(_n2##x,_n1##y,z,c), I[20] = (T)(img)(_p2##x,_n2##y,z,c), \
+ I[21] = (T)(img)(_p1##x,_n2##y,z,c), I[22] = (T)(img)(x,_n2##y,z,c), I[23] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[24] = (T)(img)(_n2##x,_n2##y,z,c)
+
+#define cimg_get6x6(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p2##x,_p2##y,z,c), I[1] = (T)(img)(_p1##x,_p2##y,z,c), I[2] = (T)(img)(x,_p2##y,z,c), \
+ I[3] = (T)(img)(_n1##x,_p2##y,z,c), I[4] = (T)(img)(_n2##x,_p2##y,z,c), I[5] = (T)(img)(_n3##x,_p2##y,z,c), \
+ I[6] = (T)(img)(_p2##x,_p1##y,z,c), I[7] = (T)(img)(_p1##x,_p1##y,z,c), I[8] = (T)(img)(x,_p1##y,z,c), \
+ I[9] = (T)(img)(_n1##x,_p1##y,z,c), I[10] = (T)(img)(_n2##x,_p1##y,z,c), I[11] = (T)(img)(_n3##x,_p1##y,z,c), \
+ I[12] = (T)(img)(_p2##x,y,z,c), I[13] = (T)(img)(_p1##x,y,z,c), I[14] = (T)(img)(x,y,z,c), \
+ I[15] = (T)(img)(_n1##x,y,z,c), I[16] = (T)(img)(_n2##x,y,z,c), I[17] = (T)(img)(_n3##x,y,z,c), \
+ I[18] = (T)(img)(_p2##x,_n1##y,z,c), I[19] = (T)(img)(_p1##x,_n1##y,z,c), I[20] = (T)(img)(x,_n1##y,z,c), \
+ I[21] = (T)(img)(_n1##x,_n1##y,z,c), I[22] = (T)(img)(_n2##x,_n1##y,z,c), I[23] = (T)(img)(_n3##x,_n1##y,z,c), \
+ I[24] = (T)(img)(_p2##x,_n2##y,z,c), I[25] = (T)(img)(_p1##x,_n2##y,z,c), I[26] = (T)(img)(x,_n2##y,z,c), \
+ I[27] = (T)(img)(_n1##x,_n2##y,z,c), I[28] = (T)(img)(_n2##x,_n2##y,z,c), I[29] = (T)(img)(_n3##x,_n2##y,z,c), \
+ I[30] = (T)(img)(_p2##x,_n3##y,z,c), I[31] = (T)(img)(_p1##x,_n3##y,z,c), I[32] = (T)(img)(x,_n3##y,z,c), \
+ I[33] = (T)(img)(_n1##x,_n3##y,z,c), I[34] = (T)(img)(_n2##x,_n3##y,z,c), I[35] = (T)(img)(_n3##x,_n3##y,z,c)
+
+#define cimg_get7x7(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \
+ I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \
+ I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_p3##x,_p2##y,z,c), I[8] = (T)(img)(_p2##x,_p2##y,z,c), \
+ I[9] = (T)(img)(_p1##x,_p2##y,z,c), I[10] = (T)(img)(x,_p2##y,z,c), I[11] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[12] = (T)(img)(_n2##x,_p2##y,z,c), I[13] = (T)(img)(_n3##x,_p2##y,z,c), I[14] = (T)(img)(_p3##x,_p1##y,z,c), \
+ I[15] = (T)(img)(_p2##x,_p1##y,z,c), I[16] = (T)(img)(_p1##x,_p1##y,z,c), I[17] = (T)(img)(x,_p1##y,z,c), \
+ I[18] = (T)(img)(_n1##x,_p1##y,z,c), I[19] = (T)(img)(_n2##x,_p1##y,z,c), I[20] = (T)(img)(_n3##x,_p1##y,z,c), \
+ I[21] = (T)(img)(_p3##x,y,z,c), I[22] = (T)(img)(_p2##x,y,z,c), I[23] = (T)(img)(_p1##x,y,z,c), \
+ I[24] = (T)(img)(x,y,z,c), I[25] = (T)(img)(_n1##x,y,z,c), I[26] = (T)(img)(_n2##x,y,z,c), \
+ I[27] = (T)(img)(_n3##x,y,z,c), I[28] = (T)(img)(_p3##x,_n1##y,z,c), I[29] = (T)(img)(_p2##x,_n1##y,z,c), \
+ I[30] = (T)(img)(_p1##x,_n1##y,z,c), I[31] = (T)(img)(x,_n1##y,z,c), I[32] = (T)(img)(_n1##x,_n1##y,z,c), \
+ I[33] = (T)(img)(_n2##x,_n1##y,z,c), I[34] = (T)(img)(_n3##x,_n1##y,z,c), I[35] = (T)(img)(_p3##x,_n2##y,z,c), \
+ I[36] = (T)(img)(_p2##x,_n2##y,z,c), I[37] = (T)(img)(_p1##x,_n2##y,z,c), I[38] = (T)(img)(x,_n2##y,z,c), \
+ I[39] = (T)(img)(_n1##x,_n2##y,z,c), I[40] = (T)(img)(_n2##x,_n2##y,z,c), I[41] = (T)(img)(_n3##x,_n2##y,z,c), \
+ I[42] = (T)(img)(_p3##x,_n3##y,z,c), I[43] = (T)(img)(_p2##x,_n3##y,z,c), I[44] = (T)(img)(_p1##x,_n3##y,z,c), \
+ I[45] = (T)(img)(x,_n3##y,z,c), I[46] = (T)(img)(_n1##x,_n3##y,z,c), I[47] = (T)(img)(_n2##x,_n3##y,z,c), \
+ I[48] = (T)(img)(_n3##x,_n3##y,z,c)
+
+#define cimg_get8x8(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p3##x,_p3##y,z,c), I[1] = (T)(img)(_p2##x,_p3##y,z,c), I[2] = (T)(img)(_p1##x,_p3##y,z,c), \
+ I[3] = (T)(img)(x,_p3##y,z,c), I[4] = (T)(img)(_n1##x,_p3##y,z,c), I[5] = (T)(img)(_n2##x,_p3##y,z,c), \
+ I[6] = (T)(img)(_n3##x,_p3##y,z,c), I[7] = (T)(img)(_n4##x,_p3##y,z,c), I[8] = (T)(img)(_p3##x,_p2##y,z,c), \
+ I[9] = (T)(img)(_p2##x,_p2##y,z,c), I[10] = (T)(img)(_p1##x,_p2##y,z,c), I[11] = (T)(img)(x,_p2##y,z,c), \
+ I[12] = (T)(img)(_n1##x,_p2##y,z,c), I[13] = (T)(img)(_n2##x,_p2##y,z,c), I[14] = (T)(img)(_n3##x,_p2##y,z,c), \
+ I[15] = (T)(img)(_n4##x,_p2##y,z,c), I[16] = (T)(img)(_p3##x,_p1##y,z,c), I[17] = (T)(img)(_p2##x,_p1##y,z,c), \
+ I[18] = (T)(img)(_p1##x,_p1##y,z,c), I[19] = (T)(img)(x,_p1##y,z,c), I[20] = (T)(img)(_n1##x,_p1##y,z,c), \
+ I[21] = (T)(img)(_n2##x,_p1##y,z,c), I[22] = (T)(img)(_n3##x,_p1##y,z,c), I[23] = (T)(img)(_n4##x,_p1##y,z,c), \
+ I[24] = (T)(img)(_p3##x,y,z,c), I[25] = (T)(img)(_p2##x,y,z,c), I[26] = (T)(img)(_p1##x,y,z,c), \
+ I[27] = (T)(img)(x,y,z,c), I[28] = (T)(img)(_n1##x,y,z,c), I[29] = (T)(img)(_n2##x,y,z,c), \
+ I[30] = (T)(img)(_n3##x,y,z,c), I[31] = (T)(img)(_n4##x,y,z,c), I[32] = (T)(img)(_p3##x,_n1##y,z,c), \
+ I[33] = (T)(img)(_p2##x,_n1##y,z,c), I[34] = (T)(img)(_p1##x,_n1##y,z,c), I[35] = (T)(img)(x,_n1##y,z,c), \
+ I[36] = (T)(img)(_n1##x,_n1##y,z,c), I[37] = (T)(img)(_n2##x,_n1##y,z,c), I[38] = (T)(img)(_n3##x,_n1##y,z,c), \
+ I[39] = (T)(img)(_n4##x,_n1##y,z,c), I[40] = (T)(img)(_p3##x,_n2##y,z,c), I[41] = (T)(img)(_p2##x,_n2##y,z,c), \
+ I[42] = (T)(img)(_p1##x,_n2##y,z,c), I[43] = (T)(img)(x,_n2##y,z,c), I[44] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[45] = (T)(img)(_n2##x,_n2##y,z,c), I[46] = (T)(img)(_n3##x,_n2##y,z,c), I[47] = (T)(img)(_n4##x,_n2##y,z,c), \
+ I[48] = (T)(img)(_p3##x,_n3##y,z,c), I[49] = (T)(img)(_p2##x,_n3##y,z,c), I[50] = (T)(img)(_p1##x,_n3##y,z,c), \
+ I[51] = (T)(img)(x,_n3##y,z,c), I[52] = (T)(img)(_n1##x,_n3##y,z,c), I[53] = (T)(img)(_n2##x,_n3##y,z,c), \
+ I[54] = (T)(img)(_n3##x,_n3##y,z,c), I[55] = (T)(img)(_n4##x,_n3##y,z,c), I[56] = (T)(img)(_p3##x,_n4##y,z,c), \
+ I[57] = (T)(img)(_p2##x,_n4##y,z,c), I[58] = (T)(img)(_p1##x,_n4##y,z,c), I[59] = (T)(img)(x,_n4##y,z,c), \
+ I[60] = (T)(img)(_n1##x,_n4##y,z,c), I[61] = (T)(img)(_n2##x,_n4##y,z,c), I[62] = (T)(img)(_n3##x,_n4##y,z,c), \
+ I[63] = (T)(img)(_n4##x,_n4##y,z,c);
+
+#define cimg_get9x9(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p4##x,_p4##y,z,c), I[1] = (T)(img)(_p3##x,_p4##y,z,c), I[2] = (T)(img)(_p2##x,_p4##y,z,c), \
+ I[3] = (T)(img)(_p1##x,_p4##y,z,c), I[4] = (T)(img)(x,_p4##y,z,c), I[5] = (T)(img)(_n1##x,_p4##y,z,c), \
+ I[6] = (T)(img)(_n2##x,_p4##y,z,c), I[7] = (T)(img)(_n3##x,_p4##y,z,c), I[8] = (T)(img)(_n4##x,_p4##y,z,c), \
+ I[9] = (T)(img)(_p4##x,_p3##y,z,c), I[10] = (T)(img)(_p3##x,_p3##y,z,c), I[11] = (T)(img)(_p2##x,_p3##y,z,c), \
+ I[12] = (T)(img)(_p1##x,_p3##y,z,c), I[13] = (T)(img)(x,_p3##y,z,c), I[14] = (T)(img)(_n1##x,_p3##y,z,c), \
+ I[15] = (T)(img)(_n2##x,_p3##y,z,c), I[16] = (T)(img)(_n3##x,_p3##y,z,c), I[17] = (T)(img)(_n4##x,_p3##y,z,c), \
+ I[18] = (T)(img)(_p4##x,_p2##y,z,c), I[19] = (T)(img)(_p3##x,_p2##y,z,c), I[20] = (T)(img)(_p2##x,_p2##y,z,c), \
+ I[21] = (T)(img)(_p1##x,_p2##y,z,c), I[22] = (T)(img)(x,_p2##y,z,c), I[23] = (T)(img)(_n1##x,_p2##y,z,c), \
+ I[24] = (T)(img)(_n2##x,_p2##y,z,c), I[25] = (T)(img)(_n3##x,_p2##y,z,c), I[26] = (T)(img)(_n4##x,_p2##y,z,c), \
+ I[27] = (T)(img)(_p4##x,_p1##y,z,c), I[28] = (T)(img)(_p3##x,_p1##y,z,c), I[29] = (T)(img)(_p2##x,_p1##y,z,c), \
+ I[30] = (T)(img)(_p1##x,_p1##y,z,c), I[31] = (T)(img)(x,_p1##y,z,c), I[32] = (T)(img)(_n1##x,_p1##y,z,c), \
+ I[33] = (T)(img)(_n2##x,_p1##y,z,c), I[34] = (T)(img)(_n3##x,_p1##y,z,c), I[35] = (T)(img)(_n4##x,_p1##y,z,c), \
+ I[36] = (T)(img)(_p4##x,y,z,c), I[37] = (T)(img)(_p3##x,y,z,c), I[38] = (T)(img)(_p2##x,y,z,c), \
+ I[39] = (T)(img)(_p1##x,y,z,c), I[40] = (T)(img)(x,y,z,c), I[41] = (T)(img)(_n1##x,y,z,c), \
+ I[42] = (T)(img)(_n2##x,y,z,c), I[43] = (T)(img)(_n3##x,y,z,c), I[44] = (T)(img)(_n4##x,y,z,c), \
+ I[45] = (T)(img)(_p4##x,_n1##y,z,c), I[46] = (T)(img)(_p3##x,_n1##y,z,c), I[47] = (T)(img)(_p2##x,_n1##y,z,c), \
+ I[48] = (T)(img)(_p1##x,_n1##y,z,c), I[49] = (T)(img)(x,_n1##y,z,c), I[50] = (T)(img)(_n1##x,_n1##y,z,c), \
+ I[51] = (T)(img)(_n2##x,_n1##y,z,c), I[52] = (T)(img)(_n3##x,_n1##y,z,c), I[53] = (T)(img)(_n4##x,_n1##y,z,c), \
+ I[54] = (T)(img)(_p4##x,_n2##y,z,c), I[55] = (T)(img)(_p3##x,_n2##y,z,c), I[56] = (T)(img)(_p2##x,_n2##y,z,c), \
+ I[57] = (T)(img)(_p1##x,_n2##y,z,c), I[58] = (T)(img)(x,_n2##y,z,c), I[59] = (T)(img)(_n1##x,_n2##y,z,c), \
+ I[60] = (T)(img)(_n2##x,_n2##y,z,c), I[61] = (T)(img)(_n3##x,_n2##y,z,c), I[62] = (T)(img)(_n4##x,_n2##y,z,c), \
+ I[63] = (T)(img)(_p4##x,_n3##y,z,c), I[64] = (T)(img)(_p3##x,_n3##y,z,c), I[65] = (T)(img)(_p2##x,_n3##y,z,c), \
+ I[66] = (T)(img)(_p1##x,_n3##y,z,c), I[67] = (T)(img)(x,_n3##y,z,c), I[68] = (T)(img)(_n1##x,_n3##y,z,c), \
+ I[69] = (T)(img)(_n2##x,_n3##y,z,c), I[70] = (T)(img)(_n3##x,_n3##y,z,c), I[71] = (T)(img)(_n4##x,_n3##y,z,c), \
+ I[72] = (T)(img)(_p4##x,_n4##y,z,c), I[73] = (T)(img)(_p3##x,_n4##y,z,c), I[74] = (T)(img)(_p2##x,_n4##y,z,c), \
+ I[75] = (T)(img)(_p1##x,_n4##y,z,c), I[76] = (T)(img)(x,_n4##y,z,c), I[77] = (T)(img)(_n1##x,_n4##y,z,c), \
+ I[78] = (T)(img)(_n2##x,_n4##y,z,c), I[79] = (T)(img)(_n3##x,_n4##y,z,c), I[80] = (T)(img)(_n4##x,_n4##y,z,c)
+
+#define cimg_get2x2x2(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(x,y,z,c), I[1] = (T)(img)(_n1##x,y,z,c), I[2] = (T)(img)(x,_n1##y,z,c), \
+ I[3] = (T)(img)(_n1##x,_n1##y,z,c), I[4] = (T)(img)(x,y,_n1##z,c), I[5] = (T)(img)(_n1##x,y,_n1##z,c), \
+ I[6] = (T)(img)(x,_n1##y,_n1##z,c), I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)
+
+#define cimg_get3x3x3(img,x,y,z,c,I,T) \
+ I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c), I[1] = (T)(img)(x,_p1##y,_p1##z,c), \
+ I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c), I[3] = (T)(img)(_p1##x,y,_p1##z,c), I[4] = (T)(img)(x,y,_p1##z,c), \
+ I[5] = (T)(img)(_n1##x,y,_p1##z,c), I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c), I[7] = (T)(img)(x,_n1##y,_p1##z,c), \
+ I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c), I[9] = (T)(img)(_p1##x,_p1##y,z,c), I[10] = (T)(img)(x,_p1##y,z,c), \
+ I[11] = (T)(img)(_n1##x,_p1##y,z,c), I[12] = (T)(img)(_p1##x,y,z,c), I[13] = (T)(img)(x,y,z,c), \
+ I[14] = (T)(img)(_n1##x,y,z,c), I[15] = (T)(img)(_p1##x,_n1##y,z,c), I[16] = (T)(img)(x,_n1##y,z,c), \
+ I[17] = (T)(img)(_n1##x,_n1##y,z,c), I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c), I[19] = (T)(img)(x,_p1##y,_n1##z,c), \
+ I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c), I[21] = (T)(img)(_p1##x,y,_n1##z,c), I[22] = (T)(img)(x,y,_n1##z,c), \
+ I[23] = (T)(img)(_n1##x,y,_n1##z,c), I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c), I[25] = (T)(img)(x,_n1##y,_n1##z,c), \
+ I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)
+
+// Macros to perform various image loops.
+//
+// These macros are simpler to use than loops with C++ iterators.
+#define cimg_for(img,ptrs,T_ptrs) \
+ for (T_ptrs *ptrs = (img)._data, *_max##ptrs = (img)._data + (img).size(); ptrs<_max##ptrs; ++ptrs)
+#define cimg_rof(img,ptrs,T_ptrs) for (T_ptrs *ptrs = (img)._data + (img).size() - 1; ptrs>=(img)._data; --ptrs)
+#define cimg_foroff(img,off) for (cimg_ulong off = 0, _max##off = (img).size(); off<_max##off; ++off)
+
+#define cimg_for1(bound,i) for (int i = 0; i<(int)(bound); ++i)
+#define cimg_forX(img,x) cimg_for1((img)._width,x)
+#define cimg_forY(img,y) cimg_for1((img)._height,y)
+#define cimg_forZ(img,z) cimg_for1((img)._depth,z)
+#define cimg_forC(img,c) cimg_for1((img)._spectrum,c)
+#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x)
+#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x)
+#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y)
+#define cimg_forXC(img,x,c) cimg_forC(img,c) cimg_forX(img,x)
+#define cimg_forYC(img,y,c) cimg_forC(img,c) cimg_forY(img,y)
+#define cimg_forZC(img,z,c) cimg_forC(img,c) cimg_forZ(img,z)
+#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y)
+#define cimg_forXYC(img,x,y,c) cimg_forC(img,c) cimg_forXY(img,x,y)
+#define cimg_forXZC(img,x,z,c) cimg_forC(img,c) cimg_forXZ(img,x,z)
+#define cimg_forYZC(img,y,z,c) cimg_forC(img,c) cimg_forYZ(img,y,z)
+#define cimg_forXYZC(img,x,y,z,c) cimg_forC(img,c) cimg_forXYZ(img,x,y,z)
+
+#define cimg_rof1(bound,i) for (int i = (int)(bound) - 1; i>=0; --i)
+#define cimg_rofX(img,x) cimg_rof1((img)._width,x)
+#define cimg_rofY(img,y) cimg_rof1((img)._height,y)
+#define cimg_rofZ(img,z) cimg_rof1((img)._depth,z)
+#define cimg_rofC(img,c) cimg_rof1((img)._spectrum,c)
+#define cimg_rofXY(img,x,y) cimg_rofY(img,y) cimg_rofX(img,x)
+#define cimg_rofXZ(img,x,z) cimg_rofZ(img,z) cimg_rofX(img,x)
+#define cimg_rofYZ(img,y,z) cimg_rofZ(img,z) cimg_rofY(img,y)
+#define cimg_rofXC(img,x,c) cimg_rofC(img,c) cimg_rofX(img,x)
+#define cimg_rofYC(img,y,c) cimg_rofC(img,c) cimg_rofY(img,y)
+#define cimg_rofZC(img,z,c) cimg_rofC(img,c) cimg_rofZ(img,z)
+#define cimg_rofXYZ(img,x,y,z) cimg_rofZ(img,z) cimg_rofXY(img,x,y)
+#define cimg_rofXYC(img,x,y,c) cimg_rofC(img,c) cimg_rofXY(img,x,y)
+#define cimg_rofXZC(img,x,z,c) cimg_rofC(img,c) cimg_rofXZ(img,x,z)
+#define cimg_rofYZC(img,y,z,c) cimg_rofC(img,c) cimg_rofYZ(img,y,z)
+#define cimg_rofXYZC(img,x,y,z,c) cimg_rofC(img,c) cimg_rofXYZ(img,x,y,z)
+
+#define cimg_for_in1(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound) - 1; i<=_max##i; ++i)
+#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img)._width,x0,x1,x)
+#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img)._height,y0,y1,y)
+#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img)._depth,z0,z1,z)
+#define cimg_for_inC(img,c0,c1,c) cimg_for_in1((img)._spectrum,c0,c1,c)
+#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inXC(img,x0,c0,x1,c1,x,c) cimg_for_inC(img,c0,c1,c) cimg_for_inX(img,x0,x1,x)
+#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y)
+#define cimg_for_inYC(img,y0,c0,y1,c1,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inY(img,y0,y1,y)
+#define cimg_for_inZC(img,z0,c0,z1,c1,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inZ(img,z0,z1,z)
+#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_inXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_inXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inXZ(img,x0,z0,x1,z1,x,z)
+#define cimg_for_inYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_inC(img,c0,c1,c) cimg_for_inYZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_inXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_inC(img,c0,c1,c) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img)._width - 1 - (n),x)
+#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img)._height - 1 - (n),y)
+#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img)._depth - 1 - (n),z)
+#define cimg_for_insideC(img,c,n) cimg_for_inC(img,n,(img)._spectrum - 1 - (n),c)
+#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y)
+#define cimg_for_insideXYZ(img,x,y,z,n) \
+ cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z)
+#define cimg_for_insideXYZC(img,x,y,z,c,n) \
+ cimg_for_inXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z)
+
+#define cimg_for_out1(boundi,i0,i1,i) \
+ for (int i = (int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1) + 1:i)
+#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \
+ for (int j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \
+ ++i, i = _n1j?i:(i==(int)(i0)?(int)(i1) + 1:i))
+#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \
+ for (int k = 0; k<(int)(boundk); ++k) \
+ for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1) + 1; i<(int)(boundi); \
+ ++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1) + 1:i))
+#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \
+ for (int l = 0; l<(int)(boundl); ++l) \
+ for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \
+ for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
+ for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1) + 1; \
+ i<(int)(boundi); ++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1) + 1:i))
+#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img)._width,x0,x1,x)
+#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img)._height,y0,y1,y)
+#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img)._depth,z0,z1,z)
+#define cimg_for_outC(img,c0,c1,c) cimg_for_out1((img)._spectrum,c0,c1,c)
+#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img)._width,(img)._height,x0,y0,x1,y1,x,y)
+#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img)._width,(img)._depth,x0,z0,x1,z1,x,z)
+#define cimg_for_outXC(img,x0,c0,x1,c1,x,c) cimg_for_out2((img)._width,(img)._spectrum,x0,c0,x1,c1,x,c)
+#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img)._height,(img)._depth,y0,z0,y1,z1,y,z)
+#define cimg_for_outYC(img,y0,c0,y1,c1,y,c) cimg_for_out2((img)._height,(img)._spectrum,y0,c0,y1,c1,y,c)
+#define cimg_for_outZC(img,z0,c0,z1,c1,z,c) cimg_for_out2((img)._depth,(img)._spectrum,z0,c0,z1,c1,z,c)
+#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) \
+ cimg_for_out3((img)._width,(img)._height,(img)._depth,x0,y0,z0,x1,y1,z1,x,y,z)
+#define cimg_for_outXYC(img,x0,y0,c0,x1,y1,c1,x,y,c) \
+ cimg_for_out3((img)._width,(img)._height,(img)._spectrum,x0,y0,c0,x1,y1,c1,x,y,c)
+#define cimg_for_outXZC(img,x0,z0,c0,x1,z1,c1,x,z,c) \
+ cimg_for_out3((img)._width,(img)._depth,(img)._spectrum,x0,z0,c0,x1,z1,c1,x,z,c)
+#define cimg_for_outYZC(img,y0,z0,c0,y1,z1,c1,y,z,c) \
+ cimg_for_out3((img)._height,(img)._depth,(img)._spectrum,y0,z0,c0,y1,z1,c1,y,z,c)
+#define cimg_for_outXYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_out4((img)._width,(img)._height,(img)._depth,(img)._spectrum,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c)
+#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img)._width - 1 - (n),x)
+#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img)._height - 1 - (n),y)
+#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img)._depth - 1 - (n),z)
+#define cimg_for_borderC(img,c,n) cimg_for_outC(img,n,(img)._spectrum - 1 - (n),c)
+#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),x,y)
+#define cimg_for_borderXYZ(img,x,y,z,n) \
+ cimg_for_outXYZ(img,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n),(img)._depth - 1 - (n),x,y,z)
+#define cimg_for_borderXYZC(img,x,y,z,c,n) \
+ cimg_for_outXYZC(img,n,n,n,n,(img)._width - 1 - (n),(img)._height - 1 - (n), \
+ (img)._depth - 1 - (n),(img)._spectrum - 1 - (n),x,y,z,c)
+
+#define cimg_for_spiralXY(img,x,y) \
+ for (int x = 0, y = 0, _n1##x = 1, _n1##y = (img).width()*(img).height(); _n1##y; \
+ --_n1##y, _n1##x+=(_n1##x>>2) - ((!(_n1##x&3)?--y:((_n1##x&3)==1?(img)._width - 1 - ++x:\
+ ((_n1##x&3)==2?(img)._height - 1 - ++y:--x))))?0:1)
+
+#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \
+ for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \
+ _dx=(x1)>(x0)?(int)(x1) - (int)(x0):(_sx=-1,(int)(x0) - (int)(x1)), \
+ _dy=(y1)>(y0)?(int)(y1) - (int)(y0):(_sy=-1,(int)(y0) - (int)(y1)), \
+ _counter = _dx, \
+ _err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \
+ _counter>=0; \
+ --_counter, x+=_steep? \
+ (y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \
+ (y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx))
+
+#define cimg_for2(bound,i) \
+ for (int i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ ++i, ++_n1##i)
+#define cimg_for2X(img,x) cimg_for2((img)._width,x)
+#define cimg_for2Y(img,y) cimg_for2((img)._height,y)
+#define cimg_for2Z(img,z) cimg_for2((img)._depth,z)
+#define cimg_for2C(img,c) cimg_for2((img)._spectrum,c)
+#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x)
+#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x)
+#define cimg_for2XC(img,x,c) cimg_for2C(img,c) cimg_for2X(img,x)
+#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y)
+#define cimg_for2YC(img,y,c) cimg_for2C(img,c) cimg_for2Y(img,y)
+#define cimg_for2ZC(img,z,c) cimg_for2C(img,c) cimg_for2Z(img,z)
+#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y)
+#define cimg_for2XZC(img,x,z,c) cimg_for2C(img,c) cimg_for2XZ(img,x,z)
+#define cimg_for2YZC(img,y,z,c) cimg_for2C(img,c) cimg_for2YZ(img,y,z)
+#define cimg_for2XYZC(img,x,y,z,c) cimg_for2C(img,c) cimg_for2XYZ(img,x,y,z)
+
+#define cimg_for_in2(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \
+ i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
+ ++i, ++_n1##i)
+#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img)._width,x0,x1,x)
+#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img)._height,y0,y1,y)
+#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img)._depth,z0,z1,z)
+#define cimg_for_in2C(img,c0,c1,c) cimg_for_in2((img)._spectrum,c0,c1,c)
+#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2XC(img,x0,c0,x1,c1,x,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2X(img,x0,x1,x)
+#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y)
+#define cimg_for_in2YC(img,y0,c0,y1,c1,y,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Y(img,y0,y1,y)
+#define cimg_for_in2ZC(img,z0,c0,z1,c1,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2Z(img,z0,z1,z)
+#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in2XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in2YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in2C(img,c0,c1,c) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in2XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in2C(img,c0,c1,c) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound) - 1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ _p1##i = i++, ++_n1##i)
+#define cimg_for3X(img,x) cimg_for3((img)._width,x)
+#define cimg_for3Y(img,y) cimg_for3((img)._height,y)
+#define cimg_for3Z(img,z) cimg_for3((img)._depth,z)
+#define cimg_for3C(img,c) cimg_for3((img)._spectrum,c)
+#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x)
+#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x)
+#define cimg_for3XC(img,x,c) cimg_for3C(img,c) cimg_for3X(img,x)
+#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y)
+#define cimg_for3YC(img,y,c) cimg_for3C(img,c) cimg_for3Y(img,y)
+#define cimg_for3ZC(img,z,c) cimg_for3C(img,c) cimg_for3Z(img,z)
+#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y)
+#define cimg_for3XZC(img,x,z,c) cimg_for3C(img,c) cimg_for3XZ(img,x,z)
+#define cimg_for3YZC(img,y,z,c) cimg_for3C(img,c) cimg_for3YZ(img,y,z)
+#define cimg_for3XYZC(img,x,y,z,c) cimg_for3C(img,c) cimg_for3XYZ(img,x,y,z)
+
+#define cimg_for_in3(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p1##i = i - 1<0?0:i - 1, \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1; \
+ i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
+ _p1##i = i++, ++_n1##i)
+#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img)._width,x0,x1,x)
+#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img)._height,y0,y1,y)
+#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img)._depth,z0,z1,z)
+#define cimg_for_in3C(img,c0,c1,c) cimg_for_in3((img)._spectrum,c0,c1,c)
+#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3XC(img,x0,c0,x1,c1,x,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3X(img,x0,x1,x)
+#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y)
+#define cimg_for_in3YC(img,y0,c0,y1,c1,y,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Y(img,y0,y1,y)
+#define cimg_for_in3ZC(img,z0,c0,z1,c1,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3Z(img,z0,z1,z)
+#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in3XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in3YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in3C(img,c0,c1,c) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in3XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in3C(img,c0,c1,c) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for4(bound,i) \
+ for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound) - 1:1, \
+ _n2##i = 2>=(bound)?(int)(bound) - 1:2; \
+ _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
+ _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for4X(img,x) cimg_for4((img)._width,x)
+#define cimg_for4Y(img,y) cimg_for4((img)._height,y)
+#define cimg_for4Z(img,z) cimg_for4((img)._depth,z)
+#define cimg_for4C(img,c) cimg_for4((img)._spectrum,c)
+#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x)
+#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x)
+#define cimg_for4XC(img,x,c) cimg_for4C(img,c) cimg_for4X(img,x)
+#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y)
+#define cimg_for4YC(img,y,c) cimg_for4C(img,c) cimg_for4Y(img,y)
+#define cimg_for4ZC(img,z,c) cimg_for4C(img,c) cimg_for4Z(img,z)
+#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y)
+#define cimg_for4XZC(img,x,z,c) cimg_for4C(img,c) cimg_for4XZ(img,x,z)
+#define cimg_for4YZC(img,y,z,c) cimg_for4C(img,c) cimg_for4YZ(img,y,z)
+#define cimg_for4XYZC(img,x,y,z,c) cimg_for4C(img,c) cimg_for4XYZ(img,x,y,z)
+
+#define cimg_for_in4(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p1##i = i - 1<0?0:i - 1, \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \
+ _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \
+ i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
+ _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img)._width,x0,x1,x)
+#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img)._height,y0,y1,y)
+#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img)._depth,z0,z1,z)
+#define cimg_for_in4C(img,c0,c1,c) cimg_for_in4((img)._spectrum,c0,c1,c)
+#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4XC(img,x0,c0,x1,c1,x,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4X(img,x0,x1,x)
+#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y)
+#define cimg_for_in4YC(img,y0,c0,y1,c1,y,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Y(img,y0,y1,y)
+#define cimg_for_in4ZC(img,z0,c0,z1,c1,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4Z(img,z0,z1,z)
+#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in4XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in4YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in4C(img,c0,c1,c) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in4XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in4C(img,c0,c1,c) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for5(bound,i) \
+ for (int i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound) - 1:1, \
+ _n2##i = 2>=(bound)?(int)(bound) - 1:2; \
+ _n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for5X(img,x) cimg_for5((img)._width,x)
+#define cimg_for5Y(img,y) cimg_for5((img)._height,y)
+#define cimg_for5Z(img,z) cimg_for5((img)._depth,z)
+#define cimg_for5C(img,c) cimg_for5((img)._spectrum,c)
+#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x)
+#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x)
+#define cimg_for5XC(img,x,c) cimg_for5C(img,c) cimg_for5X(img,x)
+#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y)
+#define cimg_for5YC(img,y,c) cimg_for5C(img,c) cimg_for5Y(img,y)
+#define cimg_for5ZC(img,z,c) cimg_for5C(img,c) cimg_for5Z(img,z)
+#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y)
+#define cimg_for5XZC(img,x,z,c) cimg_for5C(img,c) cimg_for5XZ(img,x,z)
+#define cimg_for5YZC(img,y,z,c) cimg_for5C(img,c) cimg_for5YZ(img,y,z)
+#define cimg_for5XYZC(img,x,y,z,c) cimg_for5C(img,c) cimg_for5XYZ(img,x,y,z)
+
+#define cimg_for_in5(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p2##i = i - 2<0?0:i - 2, \
+ _p1##i = i - 1<0?0:i - 1, \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \
+ _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2; \
+ i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
+#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img)._width,x0,x1,x)
+#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img)._height,y0,y1,y)
+#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img)._depth,z0,z1,z)
+#define cimg_for_in5C(img,c0,c1,c) cimg_for_in5((img)._spectrum,c0,c1,c)
+#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5XC(img,x0,c0,x1,c1,x,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5X(img,x0,x1,x)
+#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y)
+#define cimg_for_in5YC(img,y0,c0,y1,c1,y,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Y(img,y0,y1,y)
+#define cimg_for_in5ZC(img,z0,c0,z1,c1,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5Z(img,z0,z1,z)
+#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in5XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in5YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in5C(img,c0,c1,c) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in5XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in5C(img,c0,c1,c) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for6(bound,i) \
+ for (int i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound) - 1:1, \
+ _n2##i = 2>=(bound)?(int)(bound) - 1:2, \
+ _n3##i = 3>=(bound)?(int)(bound) - 1:3; \
+ _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for6X(img,x) cimg_for6((img)._width,x)
+#define cimg_for6Y(img,y) cimg_for6((img)._height,y)
+#define cimg_for6Z(img,z) cimg_for6((img)._depth,z)
+#define cimg_for6C(img,c) cimg_for6((img)._spectrum,c)
+#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x)
+#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x)
+#define cimg_for6XC(img,x,c) cimg_for6C(img,c) cimg_for6X(img,x)
+#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y)
+#define cimg_for6YC(img,y,c) cimg_for6C(img,c) cimg_for6Y(img,y)
+#define cimg_for6ZC(img,z,c) cimg_for6C(img,c) cimg_for6Z(img,z)
+#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y)
+#define cimg_for6XZC(img,x,z,c) cimg_for6C(img,c) cimg_for6XZ(img,x,z)
+#define cimg_for6YZC(img,y,z,c) cimg_for6C(img,c) cimg_for6YZ(img,y,z)
+#define cimg_for6XYZC(img,x,y,z,c) cimg_for6C(img,c) cimg_for6XYZ(img,x,y,z)
+
+#define cimg_for_in6(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p2##i = i - 2<0?0:i - 2, \
+ _p1##i = i - 1<0?0:i - 1, \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \
+ _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \
+ _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \
+ i<=(int)(i1) && \
+ (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
+ _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img)._width,x0,x1,x)
+#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img)._height,y0,y1,y)
+#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img)._depth,z0,z1,z)
+#define cimg_for_in6C(img,c0,c1,c) cimg_for_in6((img)._spectrum,c0,c1,c)
+#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6XC(img,x0,c0,x1,c1,x,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6X(img,x0,x1,x)
+#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y)
+#define cimg_for_in6YC(img,y0,c0,y1,c1,y,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Y(img,y0,y1,y)
+#define cimg_for_in6ZC(img,z0,c0,z1,c1,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6Z(img,z0,z1,z)
+#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in6XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in6YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in6C(img,c0,c1,c) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in6XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in6C(img,c0,c1,c) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for7(bound,i) \
+ for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound) - 1:1, \
+ _n2##i = 2>=(bound)?(int)(bound) - 1:2, \
+ _n3##i = 3>=(bound)?(int)(bound) - 1:3; \
+ _n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for7X(img,x) cimg_for7((img)._width,x)
+#define cimg_for7Y(img,y) cimg_for7((img)._height,y)
+#define cimg_for7Z(img,z) cimg_for7((img)._depth,z)
+#define cimg_for7C(img,c) cimg_for7((img)._spectrum,c)
+#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x)
+#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x)
+#define cimg_for7XC(img,x,c) cimg_for7C(img,c) cimg_for7X(img,x)
+#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y)
+#define cimg_for7YC(img,y,c) cimg_for7C(img,c) cimg_for7Y(img,y)
+#define cimg_for7ZC(img,z,c) cimg_for7C(img,c) cimg_for7Z(img,z)
+#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y)
+#define cimg_for7XZC(img,x,z,c) cimg_for7C(img,c) cimg_for7XZ(img,x,z)
+#define cimg_for7YZC(img,y,z,c) cimg_for7C(img,c) cimg_for7YZ(img,y,z)
+#define cimg_for7XYZC(img,x,y,z,c) cimg_for7C(img,c) cimg_for7XYZ(img,x,y,z)
+
+#define cimg_for_in7(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p3##i = i - 3<0?0:i - 3, \
+ _p2##i = i - 2<0?0:i - 2, \
+ _p1##i = i - 1<0?0:i - 1, \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \
+ _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \
+ _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3; \
+ i<=(int)(i1) && \
+ (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
+#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img)._width,x0,x1,x)
+#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img)._height,y0,y1,y)
+#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img)._depth,z0,z1,z)
+#define cimg_for_in7C(img,c0,c1,c) cimg_for_in7((img)._spectrum,c0,c1,c)
+#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7XC(img,x0,c0,x1,c1,x,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7X(img,x0,x1,x)
+#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y)
+#define cimg_for_in7YC(img,y0,c0,y1,c1,y,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Y(img,y0,y1,y)
+#define cimg_for_in7ZC(img,z0,c0,z1,c1,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7Z(img,z0,z1,z)
+#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in7XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in7YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in7C(img,c0,c1,c) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in7XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in7C(img,c0,c1,c) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for8(bound,i) \
+ for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound) - 1:1, \
+ _n2##i = 2>=(bound)?(int)(bound) - 1:2, \
+ _n3##i = 3>=(bound)?(int)(bound) - 1:3, \
+ _n4##i = 4>=(bound)?(int)(bound) - 1:4; \
+ _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for8X(img,x) cimg_for8((img)._width,x)
+#define cimg_for8Y(img,y) cimg_for8((img)._height,y)
+#define cimg_for8Z(img,z) cimg_for8((img)._depth,z)
+#define cimg_for8C(img,c) cimg_for8((img)._spectrum,c)
+#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x)
+#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x)
+#define cimg_for8XC(img,x,c) cimg_for8C(img,c) cimg_for8X(img,x)
+#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y)
+#define cimg_for8YC(img,y,c) cimg_for8C(img,c) cimg_for8Y(img,y)
+#define cimg_for8ZC(img,z,c) cimg_for8C(img,c) cimg_for8Z(img,z)
+#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y)
+#define cimg_for8XZC(img,x,z,c) cimg_for8C(img,c) cimg_for8XZ(img,x,z)
+#define cimg_for8YZC(img,y,z,c) cimg_for8C(img,c) cimg_for8YZ(img,y,z)
+#define cimg_for8XYZC(img,x,y,z,c) cimg_for8C(img,c) cimg_for8XYZ(img,x,y,z)
+
+#define cimg_for_in8(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p3##i = i - 3<0?0:i - 3, \
+ _p2##i = i - 2<0?0:i - 2, \
+ _p1##i = i - 1<0?0:i - 1, \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \
+ _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \
+ _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \
+ _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \
+ i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
+ _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img)._width,x0,x1,x)
+#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img)._height,y0,y1,y)
+#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img)._depth,z0,z1,z)
+#define cimg_for_in8C(img,c0,c1,c) cimg_for_in8((img)._spectrum,c0,c1,c)
+#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8XC(img,x0,c0,x1,c1,x,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8X(img,x0,x1,x)
+#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y)
+#define cimg_for_in8YC(img,y0,c0,y1,c1,y,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Y(img,y0,y1,y)
+#define cimg_for_in8ZC(img,z0,c0,z1,c1,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8Z(img,z0,z1,z)
+#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in8XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in8YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in8C(img,c0,c1,c) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in8XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in8C(img,c0,c1,c) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for9(bound,i) \
+ for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
+ _n1##i = 1>=(int)(bound)?(int)(bound) - 1:1, \
+ _n2##i = 2>=(int)(bound)?(int)(bound) - 1:2, \
+ _n3##i = 3>=(int)(bound)?(int)(bound) - 1:3, \
+ _n4##i = 4>=(int)(bound)?(int)(bound) - 1:4; \
+ _n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
+ _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for9X(img,x) cimg_for9((img)._width,x)
+#define cimg_for9Y(img,y) cimg_for9((img)._height,y)
+#define cimg_for9Z(img,z) cimg_for9((img)._depth,z)
+#define cimg_for9C(img,c) cimg_for9((img)._spectrum,c)
+#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x)
+#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x)
+#define cimg_for9XC(img,x,c) cimg_for9C(img,c) cimg_for9X(img,x)
+#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y)
+#define cimg_for9YC(img,y,c) cimg_for9C(img,c) cimg_for9Y(img,y)
+#define cimg_for9ZC(img,z,c) cimg_for9C(img,c) cimg_for9Z(img,z)
+#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y)
+#define cimg_for9XZC(img,x,z,c) cimg_for9C(img,c) cimg_for9XZ(img,x,z)
+#define cimg_for9YZC(img,y,z,c) cimg_for9C(img,c) cimg_for9YZ(img,y,z)
+#define cimg_for9XYZC(img,x,y,z,c) cimg_for9C(img,c) cimg_for9XYZ(img,x,y,z)
+
+#define cimg_for_in9(bound,i0,i1,i) \
+ for (int i = (int)(i0)<0?0:(int)(i0), \
+ _p4##i = i - 4<0?0:i - 4, \
+ _p3##i = i - 3<0?0:i - 3, \
+ _p2##i = i - 2<0?0:i - 2, \
+ _p1##i = i - 1<0?0:i - 1, \
+ _n1##i = i + 1>=(int)(bound)?(int)(bound) - 1:i + 1, \
+ _n2##i = i + 2>=(int)(bound)?(int)(bound) - 1:i + 2, \
+ _n3##i = i + 3>=(int)(bound)?(int)(bound) - 1:i + 3, \
+ _n4##i = i + 4>=(int)(bound)?(int)(bound) - 1:i + 4; \
+ i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
+ i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
+ _p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
+#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img)._width,x0,x1,x)
+#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img)._height,y0,y1,y)
+#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img)._depth,z0,z1,z)
+#define cimg_for_in9C(img,c0,c1,c) cimg_for_in9((img)._spectrum,c0,c1,c)
+#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9XC(img,x0,c0,x1,c1,x,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9X(img,x0,x1,x)
+#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y)
+#define cimg_for_in9YC(img,y0,c0,y1,c1,y,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Y(img,y0,y1,y)
+#define cimg_for_in9ZC(img,z0,c0,z1,c1,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9Z(img,z0,z1,z)
+#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y)
+#define cimg_for_in9XZC(img,x0,z0,c0,x1,y1,c1,x,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z)
+#define cimg_for_in9YZC(img,y0,z0,c0,y1,z1,c1,y,z,c) cimg_for_in9C(img,c0,c1,c) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z)
+#define cimg_for_in9XYZC(img,x0,y0,z0,c0,x1,y1,z1,c1,x,y,z,c) \
+ cimg_for_in9C(img,c0,c1,c) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
+
+#define cimg_for2x2(img,x,y,z,c,I,T) \
+ cimg_for2((img)._height,y) for (int x = 0, \
+ _n1##x = (int)( \
+ (I[0] = (T)(img)(0,y,z,c)), \
+ (I[2] = (T)(img)(0,_n1##y,z,c)), \
+ 1>=(img)._width?(img).width() - 1:1); \
+ (_n1##x<(img).width() && ( \
+ (I[1] = (T)(img)(_n1##x,y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], \
+ I[2] = I[3], \
+ ++x, ++_n1##x)
+
+#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _n1##x = (int)( \
+ (I[0] = (T)(img)(x,y,z,c)), \
+ (I[2] = (T)(img)(x,_n1##y,z,c)), \
+ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \
+ x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+ (I[1] = (T)(img)(_n1##x,y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], \
+ I[2] = I[3], \
+ ++x, ++_n1##x)
+
+#define cimg_for3x3(img,x,y,z,c,I,T) \
+ cimg_for3((img)._height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+ (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \
+ 1>=(img)._width?(img).width() - 1:1); \
+ (_n1##x<(img).width() && ( \
+ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,z,c)), \
+ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = (int)( \
+ (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[3] = (T)(img)(_p1##x,y,z,c)), \
+ (I[6] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[1] = (T)(img)(x,_p1##y,z,c)), \
+ (I[4] = (T)(img)(x,y,z,c)), \
+ (I[7] = (T)(img)(x,_n1##y,z,c)), \
+ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \
+ x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,z,c)), \
+ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+#define cimg_for4x4(img,x,y,z,c,I,T) \
+ cimg_for4((img)._height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = 1>=(img)._width?(img).width() - 1:1, \
+ _n2##x = (int)( \
+ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[4] = I[5] = (T)(img)(0,y,z,c)), \
+ (I[8] = I[9] = (T)(img)(0,_n1##y,z,c)), \
+ (I[12] = I[13] = (T)(img)(0,_n2##y,z,c)), \
+ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[6] = (T)(img)(_n1##x,y,z,c)), \
+ (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ 2>=(img)._width?(img).width() - 1:2); \
+ (_n2##x<(img).width() && ( \
+ (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[7] = (T)(img)(_n2##x,y,z,c)), \
+ (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], \
+ I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in4((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \
+ _n2##x = (int)( \
+ (I[0] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[4] = (T)(img)(_p1##x,y,z,c)), \
+ (I[8] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[12] = (T)(img)(_p1##x,_n2##y,z,c)), \
+ (I[1] = (T)(img)(x,_p1##y,z,c)), \
+ (I[5] = (T)(img)(x,y,z,c)), \
+ (I[9] = (T)(img)(x,_n1##y,z,c)), \
+ (I[13] = (T)(img)(x,_n2##y,z,c)), \
+ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[6] = (T)(img)(_n1##x,y,z,c)), \
+ (I[10] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[14] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \
+ x<=(int)(x1) && ((_n2##x<(img).width() && ( \
+ (I[3] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[7] = (T)(img)(_n2##x,y,z,c)), \
+ (I[11] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[15] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], \
+ I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for5x5(img,x,y,z,c,I,T) \
+ cimg_for5((img)._height,y) for (int x = 0, \
+ _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=(img)._width?(img).width() - 1:1, \
+ _n2##x = (int)( \
+ (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \
+ (I[5] = I[6] = I[7] = (T)(img)(0,_p1##y,z,c)), \
+ (I[10] = I[11] = I[12] = (T)(img)(0,y,z,c)), \
+ (I[15] = I[16] = I[17] = (T)(img)(0,_n1##y,z,c)), \
+ (I[20] = I[21] = I[22] = (T)(img)(0,_n2##y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[13] = (T)(img)(_n1##x,y,z,c)), \
+ (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ 2>=(img)._width?(img).width() - 1:2); \
+ (_n2##x<(img).width() && ( \
+ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[14] = (T)(img)(_n2##x,y,z,c)), \
+ (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
+ I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
+ I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
+ I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
+ I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in5((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p2##x = x - 2<0?0:x - 2, \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \
+ _n2##x = (int)( \
+ (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \
+ (I[5] = (T)(img)(_p2##x,_p1##y,z,c)), \
+ (I[10] = (T)(img)(_p2##x,y,z,c)), \
+ (I[15] = (T)(img)(_p2##x,_n1##y,z,c)), \
+ (I[20] = (T)(img)(_p2##x,_n2##y,z,c)), \
+ (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \
+ (I[6] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[11] = (T)(img)(_p1##x,y,z,c)), \
+ (I[16] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[21] = (T)(img)(_p1##x,_n2##y,z,c)), \
+ (I[2] = (T)(img)(x,_p2##y,z,c)), \
+ (I[7] = (T)(img)(x,_p1##y,z,c)), \
+ (I[12] = (T)(img)(x,y,z,c)), \
+ (I[17] = (T)(img)(x,_n1##y,z,c)), \
+ (I[22] = (T)(img)(x,_n2##y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[8] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[13] = (T)(img)(_n1##x,y,z,c)), \
+ (I[18] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[23] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ x + 2>=(int)(img)._width?(img).width() - 1:x + 2); \
+ x<=(int)(x1) && ((_n2##x<(img).width() && ( \
+ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[9] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[14] = (T)(img)(_n2##x,y,z,c)), \
+ (I[19] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[24] = (T)(img)(_n2##x,_n2##y,z,c)),1)) || \
+ _n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
+ I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
+ I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
+ I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
+ I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
+
+#define cimg_for6x6(img,x,y,z,c,I,T) \
+ cimg_for6((img)._height,y) for (int x = 0, \
+ _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=(img)._width?(img).width() - 1:1, \
+ _n2##x = 2>=(img)._width?(img).width() - 1:2, \
+ _n3##x = (int)( \
+ (I[0] = I[1] = I[2] = (T)(img)(_p2##x,_p2##y,z,c)), \
+ (I[6] = I[7] = I[8] = (T)(img)(0,_p1##y,z,c)), \
+ (I[12] = I[13] = I[14] = (T)(img)(0,y,z,c)), \
+ (I[18] = I[19] = I[20] = (T)(img)(0,_n1##y,z,c)), \
+ (I[24] = I[25] = I[26] = (T)(img)(0,_n2##y,z,c)), \
+ (I[30] = I[31] = I[32] = (T)(img)(0,_n3##y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[15] = (T)(img)(_n1##x,y,z,c)), \
+ (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[16] = (T)(img)(_n2##x,y,z,c)), \
+ (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ 3>=(img)._width?(img).width() - 1:3); \
+ (_n3##x<(img).width() && ( \
+ (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[17] = (T)(img)(_n3##x,y,z,c)), \
+ (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
+ I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in6((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \
+ _p2##x = x - 2<0?0:x - 2, \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \
+ _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \
+ _n3##x = (int)( \
+ (I[0] = (T)(img)(_p2##x,_p2##y,z,c)), \
+ (I[6] = (T)(img)(_p2##x,_p1##y,z,c)), \
+ (I[12] = (T)(img)(_p2##x,y,z,c)), \
+ (I[18] = (T)(img)(_p2##x,_n1##y,z,c)), \
+ (I[24] = (T)(img)(_p2##x,_n2##y,z,c)), \
+ (I[30] = (T)(img)(_p2##x,_n3##y,z,c)), \
+ (I[1] = (T)(img)(_p1##x,_p2##y,z,c)), \
+ (I[7] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[13] = (T)(img)(_p1##x,y,z,c)), \
+ (I[19] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[25] = (T)(img)(_p1##x,_n2##y,z,c)), \
+ (I[31] = (T)(img)(_p1##x,_n3##y,z,c)), \
+ (I[2] = (T)(img)(x,_p2##y,z,c)), \
+ (I[8] = (T)(img)(x,_p1##y,z,c)), \
+ (I[14] = (T)(img)(x,y,z,c)), \
+ (I[20] = (T)(img)(x,_n1##y,z,c)), \
+ (I[26] = (T)(img)(x,_n2##y,z,c)), \
+ (I[32] = (T)(img)(x,_n3##y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[9] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[15] = (T)(img)(_n1##x,y,z,c)), \
+ (I[21] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[27] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[33] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[4] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[10] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[16] = (T)(img)(_n2##x,y,z,c)), \
+ (I[22] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[28] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[34] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \
+ x<=(int)(x1) && ((_n3##x<(img).width() && ( \
+ (I[5] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[11] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[17] = (T)(img)(_n3##x,y,z,c)), \
+ (I[23] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[29] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[35] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
+ I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
+ I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
+ _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for7x7(img,x,y,z,c,I,T) \
+ cimg_for7((img)._height,y) for (int x = 0, \
+ _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=(img)._width?(img).width() - 1:1, \
+ _n2##x = 2>=(img)._width?(img).width() - 1:2, \
+ _n3##x = (int)( \
+ (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \
+ (I[7] = I[8] = I[9] = I[10] = (T)(img)(0,_p2##y,z,c)), \
+ (I[14] = I[15] = I[16] = I[17] = (T)(img)(0,_p1##y,z,c)), \
+ (I[21] = I[22] = I[23] = I[24] = (T)(img)(0,y,z,c)), \
+ (I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_n1##y,z,c)), \
+ (I[35] = I[36] = I[37] = I[38] = (T)(img)(0,_n2##y,z,c)), \
+ (I[42] = I[43] = I[44] = I[45] = (T)(img)(0,_n3##y,z,c)), \
+ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+ (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[25] = (T)(img)(_n1##x,y,z,c)), \
+ (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+ (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[26] = (T)(img)(_n2##x,y,z,c)), \
+ (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ 3>=(img)._width?(img).width() - 1:3); \
+ (_n3##x<(img).width() && ( \
+ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+ (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[27] = (T)(img)(_n3##x,y,z,c)), \
+ (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
+ I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
+ I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
+ I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
+ I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
+ I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
+ I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in7((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p3##x = x - 3<0?0:x - 3, \
+ _p2##x = x - 2<0?0:x - 2, \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = x + 1>=(int)(img)._width?(img).width() - 1:x + 1, \
+ _n2##x = x + 2>=(int)(img)._width?(img).width() - 1:x + 2, \
+ _n3##x = (int)( \
+ (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \
+ (I[7] = (T)(img)(_p3##x,_p2##y,z,c)), \
+ (I[14] = (T)(img)(_p3##x,_p1##y,z,c)), \
+ (I[21] = (T)(img)(_p3##x,y,z,c)), \
+ (I[28] = (T)(img)(_p3##x,_n1##y,z,c)), \
+ (I[35] = (T)(img)(_p3##x,_n2##y,z,c)), \
+ (I[42] = (T)(img)(_p3##x,_n3##y,z,c)), \
+ (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \
+ (I[8] = (T)(img)(_p2##x,_p2##y,z,c)), \
+ (I[15] = (T)(img)(_p2##x,_p1##y,z,c)), \
+ (I[22] = (T)(img)(_p2##x,y,z,c)), \
+ (I[29] = (T)(img)(_p2##x,_n1##y,z,c)), \
+ (I[36] = (T)(img)(_p2##x,_n2##y,z,c)), \
+ (I[43] = (T)(img)(_p2##x,_n3##y,z,c)), \
+ (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \
+ (I[9] = (T)(img)(_p1##x,_p2##y,z,c)), \
+ (I[16] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[23] = (T)(img)(_p1##x,y,z,c)), \
+ (I[30] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[37] = (T)(img)(_p1##x,_n2##y,z,c)), \
+ (I[44] = (T)(img)(_p1##x,_n3##y,z,c)), \
+ (I[3] = (T)(img)(x,_p3##y,z,c)), \
+ (I[10] = (T)(img)(x,_p2##y,z,c)), \
+ (I[17] = (T)(img)(x,_p1##y,z,c)), \
+ (I[24] = (T)(img)(x,y,z,c)), \
+ (I[31] = (T)(img)(x,_n1##y,z,c)), \
+ (I[38] = (T)(img)(x,_n2##y,z,c)), \
+ (I[45] = (T)(img)(x,_n3##y,z,c)), \
+ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+ (I[11] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[18] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[25] = (T)(img)(_n1##x,y,z,c)), \
+ (I[32] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[39] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[46] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+ (I[12] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[19] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[26] = (T)(img)(_n2##x,y,z,c)), \
+ (I[33] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[40] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[47] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ x + 3>=(int)(img)._width?(img).width() - 1:x + 3); \
+ x<=(int)(x1) && ((_n3##x<(img).width() && ( \
+ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+ (I[13] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[20] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[27] = (T)(img)(_n3##x,y,z,c)), \
+ (I[34] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[41] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[48] = (T)(img)(_n3##x,_n3##y,z,c)),1)) || \
+ _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
+ I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
+ I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
+ I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
+ I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
+ I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
+ I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
+
+#define cimg_for8x8(img,x,y,z,c,I,T) \
+ cimg_for8((img)._height,y) for (int x = 0, \
+ _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=((img)._width)?(img).width() - 1:1, \
+ _n2##x = 2>=((img)._width)?(img).width() - 1:2, \
+ _n3##x = 3>=((img)._width)?(img).width() - 1:3, \
+ _n4##x = (int)( \
+ (I[0] = I[1] = I[2] = I[3] = (T)(img)(_p3##x,_p3##y,z,c)), \
+ (I[8] = I[9] = I[10] = I[11] = (T)(img)(0,_p2##y,z,c)), \
+ (I[16] = I[17] = I[18] = I[19] = (T)(img)(0,_p1##y,z,c)), \
+ (I[24] = I[25] = I[26] = I[27] = (T)(img)(0,y,z,c)), \
+ (I[32] = I[33] = I[34] = I[35] = (T)(img)(0,_n1##y,z,c)), \
+ (I[40] = I[41] = I[42] = I[43] = (T)(img)(0,_n2##y,z,c)), \
+ (I[48] = I[49] = I[50] = I[51] = (T)(img)(0,_n3##y,z,c)), \
+ (I[56] = I[57] = I[58] = I[59] = (T)(img)(0,_n4##y,z,c)), \
+ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+ (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[28] = (T)(img)(_n1##x,y,z,c)), \
+ (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \
+ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+ (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[29] = (T)(img)(_n2##x,y,z,c)), \
+ (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \
+ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+ (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[30] = (T)(img)(_n3##x,y,z,c)), \
+ (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \
+ (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \
+ 4>=((img)._width)?(img).width() - 1:4); \
+ (_n4##x<(img).width() && ( \
+ (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \
+ (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \
+ (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \
+ (I[31] = (T)(img)(_n4##x,y,z,c)), \
+ (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \
+ (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \
+ (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \
+ (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
+ I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
+ I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
+ I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
+ I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in8((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p3##x = x - 3<0?0:x - 3, \
+ _p2##x = x - 2<0?0:x - 2, \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \
+ _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \
+ _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \
+ _n4##x = (int)( \
+ (I[0] = (T)(img)(_p3##x,_p3##y,z,c)), \
+ (I[8] = (T)(img)(_p3##x,_p2##y,z,c)), \
+ (I[16] = (T)(img)(_p3##x,_p1##y,z,c)), \
+ (I[24] = (T)(img)(_p3##x,y,z,c)), \
+ (I[32] = (T)(img)(_p3##x,_n1##y,z,c)), \
+ (I[40] = (T)(img)(_p3##x,_n2##y,z,c)), \
+ (I[48] = (T)(img)(_p3##x,_n3##y,z,c)), \
+ (I[56] = (T)(img)(_p3##x,_n4##y,z,c)), \
+ (I[1] = (T)(img)(_p2##x,_p3##y,z,c)), \
+ (I[9] = (T)(img)(_p2##x,_p2##y,z,c)), \
+ (I[17] = (T)(img)(_p2##x,_p1##y,z,c)), \
+ (I[25] = (T)(img)(_p2##x,y,z,c)), \
+ (I[33] = (T)(img)(_p2##x,_n1##y,z,c)), \
+ (I[41] = (T)(img)(_p2##x,_n2##y,z,c)), \
+ (I[49] = (T)(img)(_p2##x,_n3##y,z,c)), \
+ (I[57] = (T)(img)(_p2##x,_n4##y,z,c)), \
+ (I[2] = (T)(img)(_p1##x,_p3##y,z,c)), \
+ (I[10] = (T)(img)(_p1##x,_p2##y,z,c)), \
+ (I[18] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[26] = (T)(img)(_p1##x,y,z,c)), \
+ (I[34] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[42] = (T)(img)(_p1##x,_n2##y,z,c)), \
+ (I[50] = (T)(img)(_p1##x,_n3##y,z,c)), \
+ (I[58] = (T)(img)(_p1##x,_n4##y,z,c)), \
+ (I[3] = (T)(img)(x,_p3##y,z,c)), \
+ (I[11] = (T)(img)(x,_p2##y,z,c)), \
+ (I[19] = (T)(img)(x,_p1##y,z,c)), \
+ (I[27] = (T)(img)(x,y,z,c)), \
+ (I[35] = (T)(img)(x,_n1##y,z,c)), \
+ (I[43] = (T)(img)(x,_n2##y,z,c)), \
+ (I[51] = (T)(img)(x,_n3##y,z,c)), \
+ (I[59] = (T)(img)(x,_n4##y,z,c)), \
+ (I[4] = (T)(img)(_n1##x,_p3##y,z,c)), \
+ (I[12] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[20] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[28] = (T)(img)(_n1##x,y,z,c)), \
+ (I[36] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[44] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[52] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[60] = (T)(img)(_n1##x,_n4##y,z,c)), \
+ (I[5] = (T)(img)(_n2##x,_p3##y,z,c)), \
+ (I[13] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[21] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[29] = (T)(img)(_n2##x,y,z,c)), \
+ (I[37] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[45] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[53] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ (I[61] = (T)(img)(_n2##x,_n4##y,z,c)), \
+ (I[6] = (T)(img)(_n3##x,_p3##y,z,c)), \
+ (I[14] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[22] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[30] = (T)(img)(_n3##x,y,z,c)), \
+ (I[38] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[46] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[54] = (T)(img)(_n3##x,_n3##y,z,c)), \
+ (I[62] = (T)(img)(_n3##x,_n4##y,z,c)), \
+ x + 4>=(img).width()?(img).width() - 1:x + 4); \
+ x<=(int)(x1) && ((_n4##x<(img).width() && ( \
+ (I[7] = (T)(img)(_n4##x,_p3##y,z,c)), \
+ (I[15] = (T)(img)(_n4##x,_p2##y,z,c)), \
+ (I[23] = (T)(img)(_n4##x,_p1##y,z,c)), \
+ (I[31] = (T)(img)(_n4##x,y,z,c)), \
+ (I[39] = (T)(img)(_n4##x,_n1##y,z,c)), \
+ (I[47] = (T)(img)(_n4##x,_n2##y,z,c)), \
+ (I[55] = (T)(img)(_n4##x,_n3##y,z,c)), \
+ (I[63] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
+ I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
+ I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
+ I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
+ I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
+ I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
+ I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
+ I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
+ _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for9x9(img,x,y,z,c,I,T) \
+ cimg_for9((img)._height,y) for (int x = 0, \
+ _p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \
+ _n1##x = 1>=((img)._width)?(img).width() - 1:1, \
+ _n2##x = 2>=((img)._width)?(img).width() - 1:2, \
+ _n3##x = 3>=((img)._width)?(img).width() - 1:3, \
+ _n4##x = (int)( \
+ (I[0] = I[1] = I[2] = I[3] = I[4] = (T)(img)(_p4##x,_p4##y,z,c)), \
+ (I[9] = I[10] = I[11] = I[12] = I[13] = (T)(img)(0,_p3##y,z,c)), \
+ (I[18] = I[19] = I[20] = I[21] = I[22] = (T)(img)(0,_p2##y,z,c)), \
+ (I[27] = I[28] = I[29] = I[30] = I[31] = (T)(img)(0,_p1##y,z,c)), \
+ (I[36] = I[37] = I[38] = I[39] = I[40] = (T)(img)(0,y,z,c)), \
+ (I[45] = I[46] = I[47] = I[48] = I[49] = (T)(img)(0,_n1##y,z,c)), \
+ (I[54] = I[55] = I[56] = I[57] = I[58] = (T)(img)(0,_n2##y,z,c)), \
+ (I[63] = I[64] = I[65] = I[66] = I[67] = (T)(img)(0,_n3##y,z,c)), \
+ (I[72] = I[73] = I[74] = I[75] = I[76] = (T)(img)(0,_n4##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \
+ (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \
+ (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[41] = (T)(img)(_n1##x,y,z,c)), \
+ (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \
+ (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \
+ (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \
+ (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[42] = (T)(img)(_n2##x,y,z,c)), \
+ (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \
+ (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \
+ (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \
+ (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[43] = (T)(img)(_n3##x,y,z,c)), \
+ (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \
+ (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \
+ 4>=((img)._width)?(img).width() - 1:4); \
+ (_n4##x<(img).width() && ( \
+ (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \
+ (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \
+ (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \
+ (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \
+ (I[44] = (T)(img)(_n4##x,y,z,c)), \
+ (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \
+ (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \
+ (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \
+ (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \
+ I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+ I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \
+ I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \
+ I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+ I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \
+ I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \
+ I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
+ I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \
+ I[79] = I[80], \
+ _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,c,I,T) \
+ cimg_for_in9((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p4##x = x - 4<0?0:x - 4, \
+ _p3##x = x - 3<0?0:x - 3, \
+ _p2##x = x - 2<0?0:x - 2, \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = x + 1>=(img).width()?(img).width() - 1:x + 1, \
+ _n2##x = x + 2>=(img).width()?(img).width() - 1:x + 2, \
+ _n3##x = x + 3>=(img).width()?(img).width() - 1:x + 3, \
+ _n4##x = (int)( \
+ (I[0] = (T)(img)(_p4##x,_p4##y,z,c)), \
+ (I[9] = (T)(img)(_p4##x,_p3##y,z,c)), \
+ (I[18] = (T)(img)(_p4##x,_p2##y,z,c)), \
+ (I[27] = (T)(img)(_p4##x,_p1##y,z,c)), \
+ (I[36] = (T)(img)(_p4##x,y,z,c)), \
+ (I[45] = (T)(img)(_p4##x,_n1##y,z,c)), \
+ (I[54] = (T)(img)(_p4##x,_n2##y,z,c)), \
+ (I[63] = (T)(img)(_p4##x,_n3##y,z,c)), \
+ (I[72] = (T)(img)(_p4##x,_n4##y,z,c)), \
+ (I[1] = (T)(img)(_p3##x,_p4##y,z,c)), \
+ (I[10] = (T)(img)(_p3##x,_p3##y,z,c)), \
+ (I[19] = (T)(img)(_p3##x,_p2##y,z,c)), \
+ (I[28] = (T)(img)(_p3##x,_p1##y,z,c)), \
+ (I[37] = (T)(img)(_p3##x,y,z,c)), \
+ (I[46] = (T)(img)(_p3##x,_n1##y,z,c)), \
+ (I[55] = (T)(img)(_p3##x,_n2##y,z,c)), \
+ (I[64] = (T)(img)(_p3##x,_n3##y,z,c)), \
+ (I[73] = (T)(img)(_p3##x,_n4##y,z,c)), \
+ (I[2] = (T)(img)(_p2##x,_p4##y,z,c)), \
+ (I[11] = (T)(img)(_p2##x,_p3##y,z,c)), \
+ (I[20] = (T)(img)(_p2##x,_p2##y,z,c)), \
+ (I[29] = (T)(img)(_p2##x,_p1##y,z,c)), \
+ (I[38] = (T)(img)(_p2##x,y,z,c)), \
+ (I[47] = (T)(img)(_p2##x,_n1##y,z,c)), \
+ (I[56] = (T)(img)(_p2##x,_n2##y,z,c)), \
+ (I[65] = (T)(img)(_p2##x,_n3##y,z,c)), \
+ (I[74] = (T)(img)(_p2##x,_n4##y,z,c)), \
+ (I[3] = (T)(img)(_p1##x,_p4##y,z,c)), \
+ (I[12] = (T)(img)(_p1##x,_p3##y,z,c)), \
+ (I[21] = (T)(img)(_p1##x,_p2##y,z,c)), \
+ (I[30] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[39] = (T)(img)(_p1##x,y,z,c)), \
+ (I[48] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[57] = (T)(img)(_p1##x,_n2##y,z,c)), \
+ (I[66] = (T)(img)(_p1##x,_n3##y,z,c)), \
+ (I[75] = (T)(img)(_p1##x,_n4##y,z,c)), \
+ (I[4] = (T)(img)(x,_p4##y,z,c)), \
+ (I[13] = (T)(img)(x,_p3##y,z,c)), \
+ (I[22] = (T)(img)(x,_p2##y,z,c)), \
+ (I[31] = (T)(img)(x,_p1##y,z,c)), \
+ (I[40] = (T)(img)(x,y,z,c)), \
+ (I[49] = (T)(img)(x,_n1##y,z,c)), \
+ (I[58] = (T)(img)(x,_n2##y,z,c)), \
+ (I[67] = (T)(img)(x,_n3##y,z,c)), \
+ (I[76] = (T)(img)(x,_n4##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,_p4##y,z,c)), \
+ (I[14] = (T)(img)(_n1##x,_p3##y,z,c)), \
+ (I[23] = (T)(img)(_n1##x,_p2##y,z,c)), \
+ (I[32] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[41] = (T)(img)(_n1##x,y,z,c)), \
+ (I[50] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[59] = (T)(img)(_n1##x,_n2##y,z,c)), \
+ (I[68] = (T)(img)(_n1##x,_n3##y,z,c)), \
+ (I[77] = (T)(img)(_n1##x,_n4##y,z,c)), \
+ (I[6] = (T)(img)(_n2##x,_p4##y,z,c)), \
+ (I[15] = (T)(img)(_n2##x,_p3##y,z,c)), \
+ (I[24] = (T)(img)(_n2##x,_p2##y,z,c)), \
+ (I[33] = (T)(img)(_n2##x,_p1##y,z,c)), \
+ (I[42] = (T)(img)(_n2##x,y,z,c)), \
+ (I[51] = (T)(img)(_n2##x,_n1##y,z,c)), \
+ (I[60] = (T)(img)(_n2##x,_n2##y,z,c)), \
+ (I[69] = (T)(img)(_n2##x,_n3##y,z,c)), \
+ (I[78] = (T)(img)(_n2##x,_n4##y,z,c)), \
+ (I[7] = (T)(img)(_n3##x,_p4##y,z,c)), \
+ (I[16] = (T)(img)(_n3##x,_p3##y,z,c)), \
+ (I[25] = (T)(img)(_n3##x,_p2##y,z,c)), \
+ (I[34] = (T)(img)(_n3##x,_p1##y,z,c)), \
+ (I[43] = (T)(img)(_n3##x,y,z,c)), \
+ (I[52] = (T)(img)(_n3##x,_n1##y,z,c)), \
+ (I[61] = (T)(img)(_n3##x,_n2##y,z,c)), \
+ (I[70] = (T)(img)(_n3##x,_n3##y,z,c)), \
+ (I[79] = (T)(img)(_n3##x,_n4##y,z,c)), \
+ x + 4>=(img).width()?(img).width() - 1:x + 4); \
+ x<=(int)(x1) && ((_n4##x<(img).width() && ( \
+ (I[8] = (T)(img)(_n4##x,_p4##y,z,c)), \
+ (I[17] = (T)(img)(_n4##x,_p3##y,z,c)), \
+ (I[26] = (T)(img)(_n4##x,_p2##y,z,c)), \
+ (I[35] = (T)(img)(_n4##x,_p1##y,z,c)), \
+ (I[44] = (T)(img)(_n4##x,y,z,c)), \
+ (I[53] = (T)(img)(_n4##x,_n1##y,z,c)), \
+ (I[62] = (T)(img)(_n4##x,_n2##y,z,c)), \
+ (I[71] = (T)(img)(_n4##x,_n3##y,z,c)), \
+ (I[80] = (T)(img)(_n4##x,_n4##y,z,c)),1)) || \
+ _n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
+ I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], \
+ I[16] = I[17], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
+ I[24] = I[25], I[25] = I[26], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], \
+ I[32] = I[33], I[33] = I[34], I[34] = I[35], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], \
+ I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
+ I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[54] = I[55], I[55] = I[56], \
+ I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[63] = I[64], \
+ I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
+ I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], \
+ I[79] = I[80], \
+ _p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
+
+#define cimg_for2x2x2(img,x,y,z,c,I,T) \
+ cimg_for2((img)._depth,z) cimg_for2((img)._height,y) for (int x = 0, \
+ _n1##x = (int)( \
+ (I[0] = (T)(img)(0,y,z,c)), \
+ (I[2] = (T)(img)(0,_n1##y,z,c)), \
+ (I[4] = (T)(img)(0,y,_n1##z,c)), \
+ (I[6] = (T)(img)(0,_n1##y,_n1##z,c)), \
+ 1>=(img)._width?(img).width() - 1:1); \
+ (_n1##x<(img).width() && ( \
+ (I[1] = (T)(img)(_n1##x,y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \
+ (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
+ ++x, ++_n1##x)
+
+#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \
+ cimg_for_in2((img)._depth,z0,z1,z) cimg_for_in2((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _n1##x = (int)( \
+ (I[0] = (T)(img)(x,y,z,c)), \
+ (I[2] = (T)(img)(x,_n1##y,z,c)), \
+ (I[4] = (T)(img)(x,y,_n1##z,c)), \
+ (I[6] = (T)(img)(x,_n1##y,_n1##z,c)), \
+ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \
+ x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+ (I[1] = (T)(img)(_n1##x,y,z,c)), \
+ (I[3] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,_n1##z,c)), \
+ (I[7] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
+ ++x, ++_n1##x)
+
+#define cimg_for3x3x3(img,x,y,z,c,I,T) \
+ cimg_for3((img)._depth,z) cimg_for3((img)._height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \
+ (I[3] = I[4] = (T)(img)(0,y,_p1##z,c)), \
+ (I[6] = I[7] = (T)(img)(0,_n1##y,_p1##z,c)), \
+ (I[9] = I[10] = (T)(img)(0,_p1##y,z,c)), \
+ (I[12] = I[13] = (T)(img)(0,y,z,c)), \
+ (I[15] = I[16] = (T)(img)(0,_n1##y,z,c)), \
+ (I[18] = I[19] = (T)(img)(0,_p1##y,_n1##z,c)), \
+ (I[21] = I[22] = (T)(img)(0,y,_n1##z,c)), \
+ (I[24] = I[25] = (T)(img)(0,_n1##y,_n1##z,c)), \
+ 1>=(img)._width?(img).width() - 1:1); \
+ (_n1##x<(img).width() && ( \
+ (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \
+ (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \
+ (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[14] = (T)(img)(_n1##x,y,z,c)), \
+ (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \
+ (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \
+ (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
+ _p1##x = x++, ++_n1##x)
+
+#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,c,I,T) \
+ cimg_for_in3((img)._depth,z0,z1,z) cimg_for_in3((img)._height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
+ _p1##x = x - 1<0?0:x - 1, \
+ _n1##x = (int)( \
+ (I[0] = (T)(img)(_p1##x,_p1##y,_p1##z,c)), \
+ (I[3] = (T)(img)(_p1##x,y,_p1##z,c)), \
+ (I[6] = (T)(img)(_p1##x,_n1##y,_p1##z,c)), \
+ (I[9] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[12] = (T)(img)(_p1##x,y,z,c)), \
+ (I[15] = (T)(img)(_p1##x,_n1##y,z,c)), \
+ (I[18] = (T)(img)(_p1##x,_p1##y,_n1##z,c)), \
+ (I[21] = (T)(img)(_p1##x,y,_n1##z,c)), \
+ (I[24] = (T)(img)(_p1##x,_n1##y,_n1##z,c)), \
+ (I[1] = (T)(img)(x,_p1##y,_p1##z,c)), \
+ (I[4] = (T)(img)(x,y,_p1##z,c)), \
+ (I[7] = (T)(img)(x,_n1##y,_p1##z,c)), \
+ (I[10] = (T)(img)(x,_p1##y,z,c)), \
+ (I[13] = (T)(img)(x,y,z,c)), \
+ (I[16] = (T)(img)(x,_n1##y,z,c)), \
+ (I[19] = (T)(img)(x,_p1##y,_n1##z,c)), \
+ (I[22] = (T)(img)(x,y,_n1##z,c)), \
+ (I[25] = (T)(img)(x,_n1##y,_n1##z,c)), \
+ x + 1>=(int)(img)._width?(img).width() - 1:x + 1); \
+ x<=(int)(x1) && ((_n1##x<(img).width() && ( \
+ (I[2] = (T)(img)(_n1##x,_p1##y,_p1##z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,_p1##z,c)), \
+ (I[8] = (T)(img)(_n1##x,_n1##y,_p1##z,c)), \
+ (I[11] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[14] = (T)(img)(_n1##x,y,z,c)), \
+ (I[17] = (T)(img)(_n1##x,_n1##y,z,c)), \
+ (I[20] = (T)(img)(_n1##x,_p1##y,_n1##z,c)), \
+ (I[23] = (T)(img)(_n1##x,y,_n1##z,c)), \
+ (I[26] = (T)(img)(_n1##x,_n1##y,_n1##z,c)),1)) || \
+ x==--_n1##x); \
+ I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
+ I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
+ I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
+ _p1##x = x++, ++_n1##x)
+
+#define cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l)
+#define cimglist_for_in(list,l0,l1,l) \
+ for (int l = (int)(l0)<0?0:(int)(l0), _max##l = (unsigned int)l1<(list)._width?(int)(l1):(int)(list)._width - 1; \
+ l<=_max##l; ++l)
+
+#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn
+
+// Macros used to display error messages when exceptions are thrown.
+// You should not use these macros is your own code.
+#define _cimgdisplay_instance "[instance(%u,%u,%u,%c%s%c)] CImgDisplay::"
+#define cimgdisplay_instance _width,_height,_normalization,_title?'\"':'[',_title?_title:"untitled",_title?'\"':']'
+#define _cimg_instance "[instance(%u,%u,%u,%u,%p,%sshared)] CImg<%s>::"
+#define cimg_instance _width,_height,_depth,_spectrum,_data,_is_shared?"":"non-",pixel_type()
+#define _cimglist_instance "[instance(%u,%u,%p)] CImgList<%s>::"
+#define cimglist_instance _width,_allocated_width,_data,pixel_type()
+
+/*------------------------------------------------
+ #
+ #
+ # Define cimg_library:: namespace
+ #
+ #
+ -------------------------------------------------*/
+//! Contains <i>all classes and functions</i> of the \CImg library.
+/**
+ This namespace is defined to avoid functions and class names collisions
+ that could happen with the inclusion 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 objects afterwards.
+**/
+namespace cimg_library_suffixed {
+
+ // Declare the four classes of the CImg Library.
+ template<typename T=float> struct CImg;
+ template<typename T=float> struct CImgList;
+ struct CImgDisplay;
+ struct CImgException;
+
+ // Declare cimg:: namespace.
+ // This is an uncomplete namespace definition here. It only contains some
+ // necessary stuff to ensure a correct declaration order of the classes and functions
+ // defined afterwards.
+ namespace cimg {
+
+ // Define ascii sequences for colored terminal output.
+#ifdef cimg_use_vt100
+ static const char t_normal[] = { 0x1b, '[', '0', ';', '0', ';', '0', 'm', 0 };
+ static const char t_black[] = { 0x1b, '[', '0', ';', '3', '0', ';', '5', '9', 'm', 0 };
+ static const char t_red[] = { 0x1b, '[', '0', ';', '3', '1', ';', '5', '9', 'm', 0 };
+ static const char t_green[] = { 0x1b, '[', '0', ';', '3', '2', ';', '5', '9', 'm', 0 };
+ static const char t_yellow[] = { 0x1b, '[', '0', ';', '3', '3', ';', '5', '9', 'm', 0 };
+ static const char t_blue[] = { 0x1b, '[', '0', ';', '3', '4', ';', '5', '9', 'm', 0 };
+ static const char t_magenta[] = { 0x1b, '[', '0', ';', '3', '5', ';', '5', '9', 'm', 0 };
+ static const char t_cyan[] = { 0x1b, '[', '0', ';', '3', '6', ';', '5', '9', 'm', 0 };
+ static const char t_white[] = { 0x1b, '[', '0', ';', '3', '7', ';', '5', '9', 'm', 0 };
+ static const char t_bold[] = { 0x1b, '[', '1', 'm', 0 };
+ static const char t_underscore[] = { 0x1b, '[', '4', 'm', 0 };
+#else
+ static const char t_normal[] = { 0 };
+ static const char *const t_black = cimg::t_normal,
+ *const t_red = cimg::t_normal,
+ *const t_green = cimg::t_normal,
+ *const t_yellow = cimg::t_normal,
+ *const t_blue = cimg::t_normal,
+ *const t_magenta = cimg::t_normal,
+ *const t_cyan = cimg::t_normal,
+ *const t_white = cimg::t_normal,
+ *const t_bold = cimg::t_normal,
+ *const t_underscore = cimg::t_normal;
+#endif
+
+ inline std::FILE* output(std::FILE *file=0);
+ inline void info();
+
+ //! Avoid warning messages due to unused parameters. Do nothing actually.
+ template<typename T>
+ inline void unused(const T&, ...) {}
+
+ // [internal] Lock/unlock a mutex for managing concurrent threads.
+ // 'lock_mode' can be { 0=unlock | 1=lock | 2=trylock }.
+ // 'n' can be in [0,31] but mutex range [0,15] is reserved by CImg.
+ inline int mutex(const unsigned int n, const int lock_mode=1);
+
+ inline unsigned int& _exception_mode(const unsigned int value, const bool is_set) {
+ static unsigned int mode = cimg_verbosity;
+ if (is_set) { cimg::mutex(0); mode = value<4?value:4; cimg::mutex(0,0); }
+ return mode;
+ }
+
+ // Functions to return standard streams 'stdin', 'stdout' and 'stderr'.
+ inline FILE* _stdin(const bool throw_exception=true);
+ inline FILE* _stdout(const bool throw_exception=true);
+ inline FILE* _stderr(const bool throw_exception=true);
+
+ // Mandatory because Microsoft's _snprintf() and _vsnprintf() do not add the '\0' character
+ // at the end of the string.
+#if cimg_OS==2 && defined(_MSC_VER)
+ inline int _snprintf(char *const s, const size_t size, const char *const format, ...) {
+ va_list ap;
+ va_start(ap,format);
+ const int result = _vsnprintf(s,size,format,ap);
+ va_end(ap);
+ return result;
+ }
+
+ inline int _vsnprintf(char *const s, const size_t size, const char *const format, va_list ap) {
+ int result = -1;
+ cimg::mutex(6);
+ if (size) result = _vsnprintf_s(s,size,_TRUNCATE,format,ap);
+ if (result==-1) result = _vscprintf(format,ap);
+ cimg::mutex(6,0);
+ return result;
+ }
+
+ // Mutex-protected version of sscanf, sprintf and snprintf.
+ // Used only MacOSX, as it seems those functions are not re-entrant on MacOSX.
+#elif defined(__MACOSX__) || defined(__APPLE__)
+ inline int _sscanf(const char *const s, const char *const format, ...) {
+ cimg::mutex(6);
+ va_list args;
+ va_start(args,format);
+ const int result = std::vsscanf(s,format,args);
+ va_end(args);
+ cimg::mutex(6,0);
+ return result;
+ }
+
+ inline int _sprintf(char *const s, const char *const format, ...) {
+ cimg::mutex(6);
+ va_list args;
+ va_start(args,format);
+ const int result = std::vsprintf(s,format,args);
+ va_end(args);
+ cimg::mutex(6,0);
+ return result;
+ }
+
+ inline int _snprintf(char *const s, const size_t n, const char *const format, ...) {
+ cimg::mutex(6);
+ va_list args;
+ va_start(args,format);
+ const int result = std::vsnprintf(s,n,format,args);
+ va_end(args);
+ cimg::mutex(6,0);
+ return result;
+ }
+
+ inline int _vsnprintf(char *const s, const size_t size, const char* format, va_list ap) {
+ cimg::mutex(6);
+ const int result = std::vsnprintf(s,size,format,ap);
+ cimg::mutex(6,0);
+ return result;
+ }
+#endif
+
+ //! Set current \CImg exception mode.
+ /**
+ The way error messages are handled by \CImg can be changed dynamically, using this function.
+ \param mode Desired exception mode. Possible values are:
+ - \c 0: Hide library messages (quiet mode).
+ - \c 1: Print library messages on the console.
+ - \c 2: Display library messages on a dialog window.
+ - \c 3: Do as \c 1 + add extra debug warnings (slow down the code!).
+ - \c 4: Do as \c 2 + add extra debug warnings (slow down the code!).
+ **/
+ inline unsigned int& exception_mode(const unsigned int mode) {
+ return _exception_mode(mode,true);
+ }
+
+ //! Return current \CImg exception mode.
+ /**
+ \note By default, return the value of configuration macro \c cimg_verbosity
+ **/
+ inline unsigned int& exception_mode() {
+ return _exception_mode(0,false);
+ }
+
+ //! Set current \CImg openmp mode.
+ /**
+ The way openmp-based methods are handled by \CImg can be changed dynamically, using this function.
+ \param mode Desired openmp mode. Possible values are:
+ - \c 0: Never parallelize.
+ - \c 1: Always parallelize.
+ - \c 2: Adaptive parallelization mode (default behavior).
+ **/
+ inline unsigned int& _openmp_mode(const unsigned int value, const bool is_set) {
+ static unsigned int mode = 2;
+ if (is_set) { cimg::mutex(0); mode = value<2?value:2; cimg::mutex(0,0); }
+ return mode;
+ }
+
+ inline unsigned int& openmp_mode(const unsigned int mode) {
+ return _openmp_mode(mode,true);
+ }
+
+ //! Return current \CImg openmp mode.
+ inline unsigned int& openmp_mode() {
+ return _openmp_mode(0,false);
+ }
+
+#define cimg_openmp_if(cond) if (cimg::openmp_mode()==1 || (cimg::openmp_mode()>1 && (cond)))
+
+ // Display a simple dialog box, and wait for the user's response.
+ 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 c=0);
+
+ }
+
+ /*---------------------------------------
+ #
+ # Define the CImgException structures
+ #
+ --------------------------------------*/
+ //! Instances of \c CImgException are thrown when errors are encountered in a \CImg function call.
+ /**
+ \par Overview
+
+ CImgException is the base class of all exceptions thrown by \CImg (except \b CImgAbortException).
+ CImgException is never thrown itself. Derived classes that specify the type of errord are thrown instead.
+ These classes can be:
+
+ - \b CImgAbortException: Thrown when a computationally-intensive function is aborted by an external signal.
+ This is the only \c non-derived exception class.
+
+ - \b CImgArgumentException: Thrown when one argument of a called \CImg function is invalid.
+ This is probably one of the most thrown exception by \CImg.
+ For instance, the following example throws a \c CImgArgumentException:
+ \code
+ CImg<float> img(100,100,1,3); // Define a 100x100 color image with float-valued pixels.
+ img.mirror('e'); // Try to mirror image along the (non-existing) 'e'-axis.
+ \endcode
+
+ - \b CImgDisplayException: Thrown when something went wrong during the display of images in CImgDisplay instances.
+
+ - \b CImgInstanceException: Thrown when an instance associated to a called \CImg method does not fit
+ the function requirements. For instance, the following example throws a \c CImgInstanceException:
+ \code
+ const CImg<float> img; // Define an empty image.
+ const float value = img.at(0); // Try to read first pixel value (does not exist).
+ \endcode
+
+ - \b CImgIOException: Thrown when an error occured when trying to load or save image files.
+ This happens when trying to read files that do not exist or with invalid formats.
+ For instance, the following example throws a \c CImgIOException:
+ \code
+ const CImg<float> img("missing_file.jpg"); // Try to load a file that does not exist.
+ \endcode
+
+ - \b CImgWarningException: Thrown only if configuration macro \c cimg_strict_warnings is set, and
+ when a \CImg function has to display a warning message (see cimg::warn()).
+
+ It is not recommended to throw CImgException instances by yourself,
+ since they are expected to be thrown only by \CImg.
+ When an error occurs in a library function call, \CImg may display error messages on the screen or on the
+ standard output, depending on the current \CImg exception mode.
+ The \CImg exception mode can be get and set by functions cimg::exception_mode() and
+ cimg::exception_mode(unsigned int).
+
+ \par Exceptions handling
+
+ In all cases, when an error occurs in \CImg, an instance of the corresponding exception class is thrown.
+ This may lead the program to break (this is the default behavior), but you can bypass this behavior by
+ handling the exceptions by yourself,
+ using a usual <tt>try { ... } catch () { ... }</tt> bloc, as in the following example:
+ \code
+ #define "CImg.h"
+ using namespace cimg_library;
+ int main() {
+ cimg::exception_mode(0); // Enable quiet exception mode.
+ try {
+ ... // Here, do what you want to stress CImg.
+ } catch (CImgException& e) { // You succeeded: something went wrong!
+ std::fprintf(stderr,"CImg Library Error: %s",e.what()); // Display your custom error message.
+ ... // Do what you want now to save the ship!
+ }
+ }
+ \endcode
+ **/
+ struct CImgException : public std::exception {
+#define _cimg_exception_err(etype,disp_flag) \
+ std::va_list ap, ap2; \
+ va_start(ap,format); va_start(ap2,format); \
+ int size = cimg_vsnprintf(0,0,format,ap2); \
+ if (size++>=0) { \
+ delete[] _message; \
+ _message = new char[size]; \
+ cimg_vsnprintf(_message,size,format,ap); \
+ if (cimg::exception_mode()) { \
+ std::fprintf(cimg::output(),"\n%s[CImg] *** %s ***%s %s\n",cimg::t_red,etype,cimg::t_normal,_message); \
+ if (cimg_display && disp_flag && !(cimg::exception_mode()%2)) try { cimg::dialog(etype,_message,"Abort"); } \
+ catch (CImgException&) {} \
+ if (cimg::exception_mode()>=3) cimg_library_suffixed::cimg::info(); \
+ } \
+ } \
+ va_end(ap); va_end(ap2); \
+
+ char *_message;
+ CImgException() { _message = new char[1]; *_message = 0; }
+ CImgException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgException",true); }
+ CImgException(const CImgException& e):std::exception(e) {
+ const size_t size = std::strlen(e._message);
+ _message = new char[size + 1];
+ std::strncpy(_message,e._message,size);
+ _message[size] = 0;
+ }
+ ~CImgException() throw() { delete[] _message; }
+ CImgException& operator=(const CImgException& e) {
+ const size_t size = std::strlen(e._message);
+ _message = new char[size + 1];
+ std::strncpy(_message,e._message,size);
+ _message[size] = 0;
+ return *this;
+ }
+ //! Return a C-string containing the error message associated to the thrown exception.
+ const char *what() const throw() { return _message; }
+ };
+
+ // The CImgAbortException class is used to throw an exception when
+ // a computationally-intensive function has been aborted by an external signal.
+ struct CImgAbortException : public std::exception {
+ char *_message;
+ CImgAbortException() { _message = new char[1]; *_message = 0; }
+ CImgAbortException(const char *const format, ...):_message(0) { _cimg_exception_err("CImgAbortException",true); }
+ CImgAbortException(const CImgAbortException& e):std::exception(e) {
+ const size_t size = std::strlen(e._message);
+ _message = new char[size + 1];
+ std::strncpy(_message,e._message,size);
+ _message[size] = 0;
+ }
+ ~CImgAbortException() throw() { delete[] _message; }
+ CImgAbortException& operator=(const CImgAbortException& e) {
+ const size_t size = std::strlen(e._message);
+ _message = new char[size + 1];
+ std::strncpy(_message,e._message,size);
+ _message[size] = 0;
+ return *this;
+ }
+ //! Return a C-string containing the error message associated to the thrown exception.
+ const char *what() const throw() { return _message; }
+ };
+
+ // The CImgArgumentException class is used to throw an exception related
+ // to invalid arguments encountered in a library function call.
+ struct CImgArgumentException : public CImgException {
+ CImgArgumentException(const char *const format, ...) { _cimg_exception_err("CImgArgumentException",true); }
+ };
+
+ // The CImgDisplayException class is used to throw an exception related
+ // to display problems encountered in a library function call.
+ struct CImgDisplayException : public CImgException {
+ CImgDisplayException(const char *const format, ...) { _cimg_exception_err("CImgDisplayException",false); }
+ };
+
+ // The CImgInstanceException class is used to throw an exception related
+ // to an invalid instance encountered in a library function call.
+ struct CImgInstanceException : public CImgException {
+ CImgInstanceException(const char *const format, ...) { _cimg_exception_err("CImgInstanceException",true); }
+ };
+
+ // The 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 CImgWarningException class is used to throw an exception for warnings
+ // encountered in a library function call.
+ struct CImgWarningException : public CImgException {
+ CImgWarningException(const char *const format, ...) { _cimg_exception_err("CImgWarningException",false); }
+ };
+
+ /*-------------------------------------
+ #
+ # Define cimg:: namespace
+ #
+ -----------------------------------*/
+ //! Contains \a low-level functions and variables of the \CImg Library.
+ /**
+ Most of the functions and variables within this namespace are used by the \CImg library for low-level operations.
+ You may use them to access specific const values or environment variables internally used by \CImg.
+ \warning Never write <tt>using namespace cimg_library::cimg;</tt> in your source code. Lot of functions in the
+ <tt>cimg:: namespace</tt> have the same names as standard C functions that may be defined in the global
+ namespace <tt>::</tt>.
+ **/
+ namespace cimg {
+
+ // Define traits that will be used to determine the best data type to work in CImg functions.
+ //
+ template<typename T> struct type {
+ static const char* string() {
+ static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24",
+ "unknown32", "unknown40", "unknown48", "unknown56",
+ "unknown64", "unknown72", "unknown80", "unknown88",
+ "unknown96", "unknown104", "unknown112", "unknown120",
+ "unknown128" };
+ return s[(sizeof(T)<17)?sizeof(T):0];
+ }
+ static bool is_float() { return false; }
+ static bool is_inf(const T) { return false; }
+ static bool is_nan(const T) { return false; }
+ static T min() { return ~max(); }
+ static T max() { return (T)1<<(8*sizeof(T) - 1); }
+ static T inf() { return max(); }
+ static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; }
+ static const char* format() { return "%s"; }
+ static const char* format_s() { return "%s"; }
+ static const char* format(const T& val) { static const char *const s = "unknown"; cimg::unused(val); return s; }
+ };
+
+ template<> struct type<bool> {
+ static const char* string() { static const char *const s = "bool"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const bool) { return false; }
+ static bool is_nan(const bool) { return false; }
+ static bool min() { return false; }
+ static bool max() { return true; }
+ static bool inf() { return max(); }
+ static bool is_inf() { return false; }
+ static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; }
+ static const char* format() { return "%s"; }
+ static const char* format_s() { return "%s"; }
+ static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; }
+ };
+
+ template<> struct type<unsigned char> {
+ static const char* string() { static const char *const s = "unsigned char"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const unsigned char) { return false; }
+ static bool is_nan(const unsigned char) { return false; }
+ static unsigned char min() { return 0; }
+ static unsigned char max() { return (unsigned char)-1; }
+ static unsigned char inf() { return max(); }
+ static unsigned char cut(const double val) {
+ return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; }
+ static const char* format() { return "%u"; }
+ static const char* format_s() { return "%u"; }
+ static unsigned int format(const unsigned char val) { return (unsigned int)val; }
+ };
+
+#if defined(CHAR_MAX) && CHAR_MAX==255
+ template<> struct type<char> {
+ static const char* string() { static const char *const s = "char"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const char) { return false; }
+ static bool is_nan(const char) { return false; }
+ static char min() { return 0; }
+ static char max() { return (char)-1; }
+ static char inf() { return max(); }
+ static char cut(const double val) {
+ return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; }
+ static const char* format() { return "%u"; }
+ static const char* format_s() { return "%u"; }
+ static unsigned int format(const char val) { return (unsigned int)val; }
+ };
+#else
+ template<> struct type<char> {
+ static const char* string() { static const char *const s = "char"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const char) { return false; }
+ static bool is_nan(const char) { return false; }
+ static char min() { return ~max(); }
+ static char max() { return (char)((unsigned char)-1>>1); }
+ static char inf() { return max(); }
+ static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; }
+ static const char* format() { return "%d"; }
+ static const char* format_s() { return "%d"; }
+ static int format(const char val) { return (int)val; }
+ };
+#endif
+
+ template<> struct type<signed char> {
+ static const char* string() { static const char *const s = "signed char"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const signed char) { return false; }
+ static bool is_nan(const signed char) { return false; }
+ static signed char min() { return ~max(); }
+ static signed char max() { return (signed char)((unsigned char)-1>>1); }
+ static signed char inf() { return max(); }
+ static signed char cut(const double val) {
+ return val<(double)min()?min():val>(double)max()?max():(signed char)val; }
+ static const char* format() { return "%d"; }
+ static const char* format_s() { return "%d"; }
+ static int format(const signed char val) { return (int)val; }
+ };
+
+ template<> struct type<unsigned short> {
+ static const char* string() { static const char *const s = "unsigned short"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const unsigned short) { return false; }
+ static bool is_nan(const unsigned short) { return false; }
+ static unsigned short min() { return 0; }
+ static unsigned short max() { return (unsigned short)-1; }
+ static unsigned short inf() { return max(); }
+ static unsigned short cut(const double val) {
+ return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; }
+ static const char* format() { return "%u"; }
+ static const char* format_s() { return "%u"; }
+ static unsigned int format(const unsigned short val) { return (unsigned int)val; }
+ };
+
+ template<> struct type<short> {
+ static const char* string() { static const char *const s = "short"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const short) { return false; }
+ static bool is_nan(const short) { return false; }
+ static short min() { return ~max(); }
+ static short max() { return (short)((unsigned short)-1>>1); }
+ static short inf() { return max(); }
+ static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; }
+ static const char* format() { return "%d"; }
+ static const char* format_s() { return "%d"; }
+ static int format(const short val) { return (int)val; }
+ };
+
+ template<> struct type<unsigned int> {
+ static const char* string() { static const char *const s = "unsigned int"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const unsigned int) { return false; }
+ static bool is_nan(const unsigned int) { return false; }
+ static unsigned int min() { return 0; }
+ static unsigned int max() { return (unsigned int)-1; }
+ static unsigned int inf() { return max(); }
+ static unsigned int cut(const double val) {
+ return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; }
+ static const char* format() { return "%u"; }
+ static const char* format_s() { return "%u"; }
+ static unsigned int format(const unsigned int val) { return val; }
+ };
+
+ template<> struct type<int> {
+ static const char* string() { static const char *const s = "int"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const int) { return false; }
+ static bool is_nan(const int) { return false; }
+ static int min() { return ~max(); }
+ static int max() { return (int)((unsigned int)-1>>1); }
+ static int inf() { return max(); }
+ static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; }
+ static const char* format() { return "%d"; }
+ static const char* format_s() { return "%d"; }
+ static int format(const int val) { return val; }
+ };
+
+ template<> struct type<cimg_uint64> {
+ static const char* string() { static const char *const s = "unsigned int64"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const cimg_uint64) { return false; }
+ static bool is_nan(const cimg_uint64) { return false; }
+ static cimg_uint64 min() { return 0; }
+ static cimg_uint64 max() { return (cimg_uint64)-1; }
+ static cimg_uint64 inf() { return max(); }
+ static cimg_uint64 cut(const double val) {
+ return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; }
+ static const char* format() { return cimg_fuint64; }
+ static const char* format_s() { return cimg_fuint64; }
+ static unsigned long format(const cimg_uint64 val) { return (unsigned long)val; }
+ };
+
+ template<> struct type<cimg_int64> {
+ static const char* string() { static const char *const s = "int64"; return s; }
+ static bool is_float() { return false; }
+ static bool is_inf(const cimg_int64) { return false; }
+ static bool is_nan(const cimg_int64) { return false; }
+ static cimg_int64 min() { return ~max(); }
+ static cimg_int64 max() { return (cimg_int64)((cimg_uint64)-1>>1); }
+ static cimg_int64 inf() { return max(); }
+ static cimg_int64 cut(const double val) {
+ return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val;
+ }
+ static const char* format() { return cimg_fint64; }
+ static const char* format_s() { return cimg_fint64; }
+ static long format(const long val) { return (long)val; }
+ };
+
+ template<> struct type<double> {
+ static const char* string() { static const char *const s = "double"; return s; }
+ static bool is_float() { return true; }
+ static bool is_inf(const double val) {
+#ifdef isinf
+ return (bool)isinf(val);
+#else
+ return !is_nan(val) && (val<cimg::type<double>::min() || val>cimg::type<double>::max());
+#endif
+ }
+ static bool is_nan(const double val) {
+#ifdef isnan
+ return (bool)isnan(val);
+#else
+ return !(val==val);
+#endif
+ }
+ static double min() { return -DBL_MAX; }
+ static double max() { return DBL_MAX; }
+ static double inf() {
+#ifdef INFINITY
+ return (double)INFINITY;
+#else
+ return max()*max();
+#endif
+ }
+ static double nan() {
+#ifdef NAN
+ return (double)NAN;
+#else
+ const double val_nan = -std::sqrt(-1.0); return val_nan;
+#endif
+ }
+ static double cut(const double val) { return val; }
+ static const char* format() { return "%.17g"; }
+ static const char* format_s() { return "%g"; }
+ static double format(const double val) { return val; }
+ };
+
+ template<> struct type<float> {
+ static const char* string() { static const char *const s = "float"; return s; }
+ static bool is_float() { return true; }
+ static bool is_inf(const float val) {
+#ifdef isinf
+ return (bool)isinf(val);
+#else
+ return !is_nan(val) && (val<cimg::type<float>::min() || val>cimg::type<float>::max());
+#endif
+ }
+ static bool is_nan(const float val) {
+#ifdef isnan
+ return (bool)isnan(val);
+#else
+ return !(val==val);
+#endif
+ }
+ static float min() { return -FLT_MAX; }
+ static float max() { return FLT_MAX; }
+ static float inf() { return (float)cimg::type<double>::inf(); }
+ static float nan() { return (float)cimg::type<double>::nan(); }
+ static float cut(const double val) { return (float)val; }
+ static float cut(const float val) { return (float)val; }
+ static const char* format() { return "%.9g"; }
+ static const char* format_s() { return "%g"; }
+ static double format(const float val) { return (double)val; }
+ };
+
+ template<> struct type<long double> {
+ static const char* string() { static const char *const s = "long double"; return s; }
+ static bool is_float() { return true; }
+ static bool is_inf(const long double val) {
+#ifdef isinf
+ return (bool)isinf(val);
+#else
+ return !is_nan(val) && (val<cimg::type<long double>::min() || val>cimg::type<long double>::max());
+#endif
+ }
+ static bool is_nan(const long double val) {
+#ifdef isnan
+ return (bool)isnan(val);
+#else
+ return !(val==val);
+#endif
+ }
+ static long double min() { return -LDBL_MAX; }
+ static long double max() { return LDBL_MAX; }
+ static long double inf() { return max()*max(); }
+ static long double nan() { const long double val_nan = -std::sqrt(-1.0L); return val_nan; }
+ static long double cut(const long double val) { return val; }
+ static const char* format() { return "%.17g"; }
+ static const char* format_s() { return "%g"; }
+ static double format(const long double val) { return (double)val; }
+ };
+
+#ifdef cimg_use_half
+ template<> struct type<half> {
+ static const char* string() { static const char *const s = "half"; return s; }
+ static bool is_float() { return true; }
+ static bool is_inf(const long double val) {
+#ifdef isinf
+ return (bool)isinf(val);
+#else
+ return !is_nan(val) && (val<cimg::type<half>::min() || val>cimg::type<half>::max());
+#endif
+ }
+ static bool is_nan(const long double val) {
+#ifdef isnan
+ return (bool)isnan(val);
+#else
+ return !(val==val);
+#endif
+ }
+ static half min() { return (half)-65504; }
+ static half max() { return (half)65504; }
+ static half inf() { return max()*max(); }
+ static half nan() { const half val_nan = (half)-std::sqrt(-1.0); return val_nan; }
+ static half cut(const double val) { return (half)val; }
+ static const char* format() { return "%.9g"; }
+ static const char* format_s() { return "%g"; }
+ static double format(const half val) { return (double)val; }
+ };
+#endif
+
+ 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,cimg_uint64> { typedef cimg_uint64 type; };
+ template<> struct superset<bool,cimg_int64> { typedef cimg_int64 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,cimg_uint64> { typedef cimg_uint64 type; };
+ template<> struct superset<unsigned char,cimg_int64> { typedef cimg_int64 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 cimg_int64 type; };
+ template<> struct superset<signed char,int> { typedef int type; };
+ template<> struct superset<signed char,cimg_uint64> { typedef cimg_int64 type; };
+ template<> struct superset<signed char,cimg_int64> { typedef cimg_int64 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 cimg_int64 type; };
+ template<> struct superset<char,int> { typedef int type; };
+ template<> struct superset<char,cimg_uint64> { typedef cimg_int64 type; };
+ template<> struct superset<char,cimg_int64> { typedef cimg_int64 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,cimg_uint64> { typedef cimg_uint64 type; };
+ template<> struct superset<unsigned short,cimg_int64> { typedef cimg_int64 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 cimg_int64 type; };
+ template<> struct superset<short,int> { typedef int type; };
+ template<> struct superset<short,cimg_uint64> { typedef cimg_int64 type; };
+ template<> struct superset<short,cimg_int64> { typedef cimg_int64 type; };
+ template<> struct superset<short,float> { typedef float type; };
+ template<> struct superset<short,double> { typedef double type; };
+ template<> struct superset<unsigned int,char> { typedef cimg_int64 type; };
+ template<> struct superset<unsigned int,signed char> { typedef cimg_int64 type; };
+ template<> struct superset<unsigned int,short> { typedef cimg_int64 type; };
+ template<> struct superset<unsigned int,int> { typedef cimg_int64 type; };
+ template<> struct superset<unsigned int,cimg_uint64> { typedef cimg_uint64 type; };
+ template<> struct superset<unsigned int,cimg_int64> { typedef cimg_int64 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 cimg_int64 type; };
+ template<> struct superset<int,cimg_uint64> { typedef cimg_int64 type; };
+ template<> struct superset<int,cimg_int64> { typedef cimg_int64 type; };
+ template<> struct superset<int,float> { typedef float type; };
+ template<> struct superset<int,double> { typedef double type; };
+ template<> struct superset<cimg_uint64,char> { typedef cimg_int64 type; };
+ template<> struct superset<cimg_uint64,signed char> { typedef cimg_int64 type; };
+ template<> struct superset<cimg_uint64,short> { typedef cimg_int64 type; };
+ template<> struct superset<cimg_uint64,int> { typedef cimg_int64 type; };
+ template<> struct superset<cimg_uint64,cimg_int64> { typedef cimg_int64 type; };
+ template<> struct superset<cimg_uint64,float> { typedef double type; };
+ template<> struct superset<cimg_uint64,double> { typedef double type; };
+ template<> struct superset<cimg_int64,float> { typedef double type; };
+ template<> struct superset<cimg_int64,double> { typedef double type; };
+ template<> struct superset<float,double> { typedef double type; };
+#ifdef cimg_use_half
+ template<> struct superset<half,unsigned short> { typedef float type; };
+ template<> struct superset<half,short> { typedef float type; };
+ template<> struct superset<half,unsigned int> { typedef float type; };
+ template<> struct superset<half,int> { typedef float type; };
+ template<> struct superset<half,cimg_uint64> { typedef float type; };
+ template<> struct superset<half,cimg_int64> { typedef float type; };
+ template<> struct superset<half,float> { typedef float type; };
+ template<> struct superset<half,double> { typedef double type; };
+#endif
+
+ 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 _cimg_Ttdouble typename cimg::superset2<T,t,double>::type
+
+ // Define variables used internally by CImg.
+#if cimg_display==1
+ struct X11_info {
+ unsigned int nb_wins;
+ pthread_t *events_thread;
+ pthread_cond_t wait_event;
+ pthread_mutex_t wait_event_mutex;
+ CImgDisplay **wins;
+ Display *display;
+ unsigned int nb_bits;
+ bool is_blue_first;
+ bool is_shm_enabled;
+ bool byte_order;
+#ifdef cimg_use_xrandr
+ XRRScreenSize *resolutions;
+ Rotation curr_rotation;
+ unsigned int curr_resolution;
+ unsigned int nb_resolutions;
+#endif
+ X11_info():nb_wins(0),events_thread(0),display(0),
+ nb_bits(0),is_blue_first(false),is_shm_enabled(false),byte_order(false) {
+#ifdef __FreeBSD__
+ XInitThreads();
+#endif
+ wins = new CImgDisplay*[1024];
+ pthread_mutex_init(&wait_event_mutex,0);
+ pthread_cond_init(&wait_event,0);
+#ifdef cimg_use_xrandr
+ resolutions = 0;
+ curr_rotation = 0;
+ curr_resolution = nb_resolutions = 0;
+#endif
+ }
+
+ ~X11_info() {
+ delete[] wins;
+ /*
+ if (events_thread) {
+ pthread_cancel(*events_thread);
+ delete events_thread;
+ }
+ if (display) { } // XCloseDisplay(display); }
+ pthread_cond_destroy(&wait_event);
+ pthread_mutex_unlock(&wait_event_mutex);
+ pthread_mutex_destroy(&wait_event_mutex);
+ */
+ }
+ };
+#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
+#define cimg_lock_display() cimg::mutex(15)
+#define cimg_unlock_display() cimg::mutex(15,0)
+
+#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
+
+ struct Mutex_info {
+#if cimg_OS==2
+ HANDLE mutex[32];
+ Mutex_info() { for (unsigned int i = 0; i<32; ++i) mutex[i] = CreateMutex(0,FALSE,0); }
+ void lock(const unsigned int n) { WaitForSingleObject(mutex[n],INFINITE); }
+ void unlock(const unsigned int n) { ReleaseMutex(mutex[n]); }
+ int trylock(const unsigned int) { return 0; }
+#elif defined(_PTHREAD_H)
+ pthread_mutex_t mutex[32];
+ Mutex_info() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); }
+ void lock(const unsigned int n) { pthread_mutex_lock(&mutex[n]); }
+ void unlock(const unsigned int n) { pthread_mutex_unlock(&mutex[n]); }
+ int trylock(const unsigned int n) { return pthread_mutex_trylock(&mutex[n]); }
+#else
+ Mutex_info() {}
+ void lock(const unsigned int) {}
+ void unlock(const unsigned int) {}
+ int trylock(const unsigned int) { return 0; }
+#endif
+ };
+#if defined(cimg_module)
+ Mutex_info& Mutex_attr();
+#elif defined(cimg_main)
+ Mutex_info& Mutex_attr() { static Mutex_info val; return val; }
+#else
+ inline Mutex_info& Mutex_attr() { static Mutex_info val; return val; }
+#endif
+
+#if defined(cimg_use_magick)
+ static struct Magick_info {
+ Magick_info() {
+ Magick::InitializeMagick("");
+ }
+ } _Magick_info;
+#endif
+
+#if cimg_display==1
+ // Define keycodes for X11-based graphical systems.
+ const unsigned int keyESC = XK_Escape;
+ const unsigned int keyF1 = XK_F1;
+ const unsigned int keyF2 = XK_F2;
+ const unsigned int keyF3 = XK_F3;
+ const unsigned int keyF4 = XK_F4;
+ const unsigned int keyF5 = XK_F5;
+ const unsigned int keyF6 = XK_F6;
+ const unsigned int keyF7 = XK_F7;
+ const unsigned int keyF8 = XK_F8;
+ const unsigned int keyF9 = XK_F9;
+ const unsigned int keyF10 = XK_F10;
+ const unsigned int keyF11 = XK_F11;
+ const unsigned int keyF12 = XK_F12;
+ const unsigned int keyPAUSE = XK_Pause;
+ const unsigned int key1 = XK_1;
+ const unsigned int key2 = XK_2;
+ const unsigned int key3 = XK_3;
+ const unsigned int key4 = XK_4;
+ const unsigned int key5 = XK_5;
+ const unsigned int key6 = XK_6;
+ const unsigned int key7 = XK_7;
+ const unsigned int key8 = XK_8;
+ const unsigned int key9 = XK_9;
+ const unsigned int key0 = XK_0;
+ const unsigned int keyBACKSPACE = XK_BackSpace;
+ const unsigned int keyINSERT = XK_Insert;
+ const unsigned int keyHOME = XK_Home;
+ const unsigned int keyPAGEUP = XK_Page_Up;
+ const unsigned int keyTAB = XK_Tab;
+ const unsigned int keyQ = XK_q;
+ const unsigned int keyW = XK_w;
+ const unsigned int keyE = XK_e;
+ const unsigned int keyR = XK_r;
+ const unsigned int keyT = XK_t;
+ const unsigned int keyY = XK_y;
+ const unsigned int keyU = XK_u;
+ const unsigned int keyI = XK_i;
+ const unsigned int keyO = XK_o;
+ const unsigned int keyP = XK_p;
+ const unsigned int keyDELETE = XK_Delete;
+ const unsigned int keyEND = XK_End;
+ const unsigned int keyPAGEDOWN = XK_Page_Down;
+ const unsigned int keyCAPSLOCK = XK_Caps_Lock;
+ const unsigned int keyA = XK_a;
+ const unsigned int keyS = XK_s;
+ const unsigned int keyD = XK_d;
+ const unsigned int keyF = XK_f;
+ const unsigned int keyG = XK_g;
+ const unsigned int keyH = XK_h;
+ const unsigned int keyJ = XK_j;
+ const unsigned int keyK = XK_k;
+ const unsigned int keyL = XK_l;
+ const unsigned int keyENTER = XK_Return;
+ const unsigned int keySHIFTLEFT = XK_Shift_L;
+ const unsigned int keyZ = XK_z;
+ const unsigned int keyX = XK_x;
+ const unsigned int keyC = XK_c;
+ const unsigned int keyV = XK_v;
+ const unsigned int keyB = XK_b;
+ const unsigned int keyN = XK_n;
+ const unsigned int keyM = XK_m;
+ const unsigned int keySHIFTRIGHT = XK_Shift_R;
+ const unsigned int keyARROWUP = XK_Up;
+ const unsigned int keyCTRLLEFT = XK_Control_L;
+ const unsigned int keyAPPLEFT = XK_Super_L;
+ const unsigned int keyALT = XK_Alt_L;
+ const unsigned int keySPACE = XK_space;
+ const unsigned int keyALTGR = XK_Alt_R;
+ const unsigned int keyAPPRIGHT = XK_Super_R;
+ const unsigned int keyMENU = XK_Menu;
+ const unsigned int keyCTRLRIGHT = XK_Control_R;
+ const unsigned int keyARROWLEFT = XK_Left;
+ const unsigned int keyARROWDOWN = XK_Down;
+ const unsigned int keyARROWRIGHT = XK_Right;
+ const unsigned int keyPAD0 = XK_KP_0;
+ const unsigned int keyPAD1 = XK_KP_1;
+ const unsigned int keyPAD2 = XK_KP_2;
+ const unsigned int keyPAD3 = XK_KP_3;
+ const unsigned int keyPAD4 = XK_KP_4;
+ const unsigned int keyPAD5 = XK_KP_5;
+ const unsigned int keyPAD6 = XK_KP_6;
+ const unsigned int keyPAD7 = XK_KP_7;
+ const unsigned int keyPAD8 = XK_KP_8;
+ const unsigned int keyPAD9 = XK_KP_9;
+ const unsigned int keyPADADD = XK_KP_Add;
+ const unsigned int keyPADSUB = XK_KP_Subtract;
+ const unsigned int keyPADMUL = XK_KP_Multiply;
+ const unsigned int keyPADDIV = XK_KP_Divide;
+
+#elif cimg_display==2
+ // Define keycodes for Windows.
+ const unsigned int keyESC = VK_ESCAPE;
+ const unsigned int keyF1 = VK_F1;
+ const unsigned int keyF2 = VK_F2;
+ const unsigned int keyF3 = VK_F3;
+ const unsigned int keyF4 = VK_F4;
+ const unsigned int keyF5 = VK_F5;
+ const unsigned int keyF6 = VK_F6;
+ const unsigned int keyF7 = VK_F7;
+ const unsigned int keyF8 = VK_F8;
+ const unsigned int keyF9 = VK_F9;
+ const unsigned int keyF10 = VK_F10;
+ const unsigned int keyF11 = VK_F11;
+ const unsigned int keyF12 = VK_F12;
+ const unsigned int keyPAUSE = VK_PAUSE;
+ const unsigned int key1 = '1';
+ const unsigned int key2 = '2';
+ const unsigned int key3 = '3';
+ const unsigned int key4 = '4';
+ const unsigned int key5 = '5';
+ const unsigned int key6 = '6';
+ const unsigned int key7 = '7';
+ const unsigned int key8 = '8';
+ const unsigned int key9 = '9';
+ const unsigned int key0 = '0';
+ const unsigned int keyBACKSPACE = VK_BACK;
+ const unsigned int keyINSERT = VK_INSERT;
+ const unsigned int keyHOME = VK_HOME;
+ const unsigned int keyPAGEUP = VK_PRIOR;
+ const unsigned int keyTAB = VK_TAB;
+ const unsigned int keyQ = 'Q';
+ const unsigned int keyW = 'W';
+ const unsigned int keyE = 'E';
+ const unsigned int keyR = 'R';
+ const unsigned int keyT = 'T';
+ const unsigned int keyY = 'Y';
+ const unsigned int keyU = 'U';
+ const unsigned int keyI = 'I';
+ const unsigned int keyO = 'O';
+ const unsigned int keyP = 'P';
+ const unsigned int keyDELETE = VK_DELETE;
+ const unsigned int keyEND = VK_END;
+ const unsigned int keyPAGEDOWN = VK_NEXT;
+ const unsigned int keyCAPSLOCK = VK_CAPITAL;
+ const unsigned int keyA = 'A';
+ const unsigned int keyS = 'S';
+ const unsigned int keyD = 'D';
+ const unsigned int keyF = 'F';
+ const unsigned int keyG = 'G';
+ const unsigned int keyH = 'H';
+ const unsigned int keyJ = 'J';
+ const unsigned int keyK = 'K';
+ const unsigned int keyL = 'L';
+ const unsigned int keyENTER = VK_RETURN;
+ const unsigned int keySHIFTLEFT = VK_SHIFT;
+ const unsigned int keyZ = 'Z';
+ const unsigned int keyX = 'X';
+ const unsigned int keyC = 'C';
+ const unsigned int keyV = 'V';
+ const unsigned int keyB = 'B';
+ const unsigned int keyN = 'N';
+ const unsigned int keyM = 'M';
+ const unsigned int keySHIFTRIGHT = VK_SHIFT;
+ const unsigned int keyARROWUP = VK_UP;
+ const unsigned int keyCTRLLEFT = VK_CONTROL;
+ const unsigned int keyAPPLEFT = VK_LWIN;
+ const unsigned int keyALT = VK_LMENU;
+ const unsigned int keySPACE = VK_SPACE;
+ const unsigned int keyALTGR = VK_CONTROL;
+ const unsigned int keyAPPRIGHT = VK_RWIN;
+ const unsigned int keyMENU = VK_APPS;
+ const unsigned int keyCTRLRIGHT = VK_CONTROL;
+ const unsigned int keyARROWLEFT = VK_LEFT;
+ const unsigned int keyARROWDOWN = VK_DOWN;
+ const unsigned int keyARROWRIGHT = VK_RIGHT;
+ const unsigned int keyPAD0 = 0x60;
+ const unsigned int keyPAD1 = 0x61;
+ const unsigned int keyPAD2 = 0x62;
+ const unsigned int keyPAD3 = 0x63;
+ const unsigned int keyPAD4 = 0x64;
+ const unsigned int keyPAD5 = 0x65;
+ const unsigned int keyPAD6 = 0x66;
+ const unsigned int keyPAD7 = 0x67;
+ const unsigned int keyPAD8 = 0x68;
+ const unsigned int keyPAD9 = 0x69;
+ const unsigned int keyPADADD = VK_ADD;
+ const unsigned int keyPADSUB = VK_SUBTRACT;
+ const unsigned int keyPADMUL = VK_MULTIPLY;
+ const unsigned int keyPADDIV = VK_DIVIDE;
+
+#else
+ // Define random keycodes when no display is available.
+ // (should rarely be used then!).
+ const unsigned int keyESC = 1U; //!< Keycode for the \c ESC key (architecture-dependent).
+ const unsigned int keyF1 = 2U; //!< Keycode for the \c F1 key (architecture-dependent).
+ const unsigned int keyF2 = 3U; //!< Keycode for the \c F2 key (architecture-dependent).
+ const unsigned int keyF3 = 4U; //!< Keycode for the \c F3 key (architecture-dependent).
+ const unsigned int keyF4 = 5U; //!< Keycode for the \c F4 key (architecture-dependent).
+ const unsigned int keyF5 = 6U; //!< Keycode for the \c F5 key (architecture-dependent).
+ const unsigned int keyF6 = 7U; //!< Keycode for the \c F6 key (architecture-dependent).
+ const unsigned int keyF7 = 8U; //!< Keycode for the \c F7 key (architecture-dependent).
+ const unsigned int keyF8 = 9U; //!< Keycode for the \c F8 key (architecture-dependent).
+ const unsigned int keyF9 = 10U; //!< Keycode for the \c F9 key (architecture-dependent).
+ const unsigned int keyF10 = 11U; //!< Keycode for the \c F10 key (architecture-dependent).
+ const unsigned int keyF11 = 12U; //!< Keycode for the \c F11 key (architecture-dependent).
+ const unsigned int keyF12 = 13U; //!< Keycode for the \c F12 key (architecture-dependent).
+ const unsigned int keyPAUSE = 14U; //!< Keycode for the \c PAUSE key (architecture-dependent).
+ const unsigned int key1 = 15U; //!< Keycode for the \c 1 key (architecture-dependent).
+ const unsigned int key2 = 16U; //!< Keycode for the \c 2 key (architecture-dependent).
+ const unsigned int key3 = 17U; //!< Keycode for the \c 3 key (architecture-dependent).
+ const unsigned int key4 = 18U; //!< Keycode for the \c 4 key (architecture-dependent).
+ const unsigned int key5 = 19U; //!< Keycode for the \c 5 key (architecture-dependent).
+ const unsigned int key6 = 20U; //!< Keycode for the \c 6 key (architecture-dependent).
+ const unsigned int key7 = 21U; //!< Keycode for the \c 7 key (architecture-dependent).
+ const unsigned int key8 = 22U; //!< Keycode for the \c 8 key (architecture-dependent).
+ const unsigned int key9 = 23U; //!< Keycode for the \c 9 key (architecture-dependent).
+ const unsigned int key0 = 24U; //!< Keycode for the \c 0 key (architecture-dependent).
+ const unsigned int keyBACKSPACE = 25U; //!< Keycode for the \c BACKSPACE key (architecture-dependent).
+ const unsigned int keyINSERT = 26U; //!< Keycode for the \c INSERT key (architecture-dependent).
+ const unsigned int keyHOME = 27U; //!< Keycode for the \c HOME key (architecture-dependent).
+ const unsigned int keyPAGEUP = 28U; //!< Keycode for the \c PAGEUP key (architecture-dependent).
+ const unsigned int keyTAB = 29U; //!< Keycode for the \c TAB key (architecture-dependent).
+ const unsigned int keyQ = 30U; //!< Keycode for the \c Q key (architecture-dependent).
+ const unsigned int keyW = 31U; //!< Keycode for the \c W key (architecture-dependent).
+ const unsigned int keyE = 32U; //!< Keycode for the \c E key (architecture-dependent).
+ const unsigned int keyR = 33U; //!< Keycode for the \c R key (architecture-dependent).
+ const unsigned int keyT = 34U; //!< Keycode for the \c T key (architecture-dependent).
+ const unsigned int keyY = 35U; //!< Keycode for the \c Y key (architecture-dependent).
+ const unsigned int keyU = 36U; //!< Keycode for the \c U key (architecture-dependent).
+ const unsigned int keyI = 37U; //!< Keycode for the \c I key (architecture-dependent).
+ const unsigned int keyO = 38U; //!< Keycode for the \c O key (architecture-dependent).
+ const unsigned int keyP = 39U; //!< Keycode for the \c P key (architecture-dependent).
+ const unsigned int keyDELETE = 40U; //!< Keycode for the \c DELETE key (architecture-dependent).
+ const unsigned int keyEND = 41U; //!< Keycode for the \c END key (architecture-dependent).
+ const unsigned int keyPAGEDOWN = 42U; //!< Keycode for the \c PAGEDOWN key (architecture-dependent).
+ const unsigned int keyCAPSLOCK = 43U; //!< Keycode for the \c CAPSLOCK key (architecture-dependent).
+ const unsigned int keyA = 44U; //!< Keycode for the \c A key (architecture-dependent).
+ const unsigned int keyS = 45U; //!< Keycode for the \c S key (architecture-dependent).
+ const unsigned int keyD = 46U; //!< Keycode for the \c D key (architecture-dependent).
+ const unsigned int keyF = 47U; //!< Keycode for the \c F key (architecture-dependent).
+ const unsigned int keyG = 48U; //!< Keycode for the \c G key (architecture-dependent).
+ const unsigned int keyH = 49U; //!< Keycode for the \c H key (architecture-dependent).
+ const unsigned int keyJ = 50U; //!< Keycode for the \c J key (architecture-dependent).
+ const unsigned int keyK = 51U; //!< Keycode for the \c K key (architecture-dependent).
+ const unsigned int keyL = 52U; //!< Keycode for the \c L key (architecture-dependent).
+ const unsigned int keyENTER = 53U; //!< Keycode for the \c ENTER key (architecture-dependent).
+ const unsigned int keySHIFTLEFT = 54U; //!< Keycode for the \c SHIFTLEFT key (architecture-dependent).
+ const unsigned int keyZ = 55U; //!< Keycode for the \c Z key (architecture-dependent).
+ const unsigned int keyX = 56U; //!< Keycode for the \c X key (architecture-dependent).
+ const unsigned int keyC = 57U; //!< Keycode for the \c C key (architecture-dependent).
+ const unsigned int keyV = 58U; //!< Keycode for the \c V key (architecture-dependent).
+ const unsigned int keyB = 59U; //!< Keycode for the \c B key (architecture-dependent).
+ const unsigned int keyN = 60U; //!< Keycode for the \c N key (architecture-dependent).
+ const unsigned int keyM = 61U; //!< Keycode for the \c M key (architecture-dependent).
+ const unsigned int keySHIFTRIGHT = 62U; //!< Keycode for the \c SHIFTRIGHT key (architecture-dependent).
+ const unsigned int keyARROWUP = 63U; //!< Keycode for the \c ARROWUP key (architecture-dependent).
+ const unsigned int keyCTRLLEFT = 64U; //!< Keycode for the \c CTRLLEFT key (architecture-dependent).
+ const unsigned int keyAPPLEFT = 65U; //!< Keycode for the \c APPLEFT key (architecture-dependent).
+ const unsigned int keyALT = 66U; //!< Keycode for the \c ALT key (architecture-dependent).
+ const unsigned int keySPACE = 67U; //!< Keycode for the \c SPACE key (architecture-dependent).
+ const unsigned int keyALTGR = 68U; //!< Keycode for the \c ALTGR key (architecture-dependent).
+ const unsigned int keyAPPRIGHT = 69U; //!< Keycode for the \c APPRIGHT key (architecture-dependent).
+ const unsigned int keyMENU = 70U; //!< Keycode for the \c MENU key (architecture-dependent).
+ const unsigned int keyCTRLRIGHT = 71U; //!< Keycode for the \c CTRLRIGHT key (architecture-dependent).
+ const unsigned int keyARROWLEFT = 72U; //!< Keycode for the \c ARROWLEFT key (architecture-dependent).
+ const unsigned int keyARROWDOWN = 73U; //!< Keycode for the \c ARROWDOWN key (architecture-dependent).
+ const unsigned int keyARROWRIGHT = 74U; //!< Keycode for the \c ARROWRIGHT key (architecture-dependent).
+ const unsigned int keyPAD0 = 75U; //!< Keycode for the \c PAD0 key (architecture-dependent).
+ const unsigned int keyPAD1 = 76U; //!< Keycode for the \c PAD1 key (architecture-dependent).
+ const unsigned int keyPAD2 = 77U; //!< Keycode for the \c PAD2 key (architecture-dependent).
+ const unsigned int keyPAD3 = 78U; //!< Keycode for the \c PAD3 key (architecture-dependent).
+ const unsigned int keyPAD4 = 79U; //!< Keycode for the \c PAD4 key (architecture-dependent).
+ const unsigned int keyPAD5 = 80U; //!< Keycode for the \c PAD5 key (architecture-dependent).
+ const unsigned int keyPAD6 = 81U; //!< Keycode for the \c PAD6 key (architecture-dependent).
+ const unsigned int keyPAD7 = 82U; //!< Keycode for the \c PAD7 key (architecture-dependent).
+ const unsigned int keyPAD8 = 83U; //!< Keycode for the \c PAD8 key (architecture-dependent).
+ const unsigned int keyPAD9 = 84U; //!< Keycode for the \c PAD9 key (architecture-dependent).
+ const unsigned int keyPADADD = 85U; //!< Keycode for the \c PADADD key (architecture-dependent).
+ const unsigned int keyPADSUB = 86U; //!< Keycode for the \c PADSUB key (architecture-dependent).
+ const unsigned int keyPADMUL = 87U; //!< Keycode for the \c PADMUL key (architecture-dependent).
+ const unsigned int keyPADDIV = 88U; //!< Keycode for the \c PADDDIV key (architecture-dependent).
+#endif
+
+ const double PI = 3.14159265358979323846; //!< Value of the mathematical constant PI
+
+ // Define a 12x13 font (small size).
+ static const char *const data_font12x13 =
+ " .wjwlwmyuw>wjwkwbwjwkwRxuwmwjwkwmyuwJwjwlx`w Fw "
+ " mwlwlwuwnwuynwuwmyTwlwkwuwmwuwnwlwkwuwmwuw_wuxlwlwkwuwnwuynwuwTwlwlwtwnwtwnw my Qw +wlw b"
+ "{ \\w Wx`xTw_w[wbxawSwkw nynwky<x1w `y ,w Xwuw CxlwiwlwmyuwbwuwUwiwlwbwiwrwqw^wuwmxuwnwiwlwmy"
+ "uwJwiwlw^wnwEymymymymy1w^wkxnxtxnw<| gybwkwuwjwtwowmxswnxnwkxlxkw:wlymxlymykwn{myo{nymy2ykwqwqwm{myo"
+ "zn{o{mzpwrwpwkwkwswowkwqwqxswnyozlyozmzp}pwrwqwqwqwswswsxsxqwqwp}qwlwiwjybw`w[wcw_wkwkwkwkw mw\"wlwiw"
+ "=wtw`xIw awuwlwm{o{mylwn|pwtwtwoy`w_w_wbwiwkxcwqwpwkznwuwjzpyGzqymyaxlylw_zWxkxaxrwqxrwqyswowkwkwkwk"
+ "wkwkwk}qyo{o{o{o{owkwkwkwkznxswnymymymymyayuwqwrwpwrwpwrwpwrwqwqwpwkwtwlwkwlwuwnwuynwuwmyTwkwlwuwmwu"
+ "wnwkwlwuwmwuwkxlwuxmwkwlwuwnwuynwuwTwkwlwuwmwuwlwmwkwtwUwuwuwowswowswowswowsw;wqwtw_ymzp~py>w bwswcw"
+ "kwuwjwuwozpwtwuwnwtwowkwjwmwuwuwkwIxmxuxowuwmwswowswmxnwjwhwowswowsw0wmwowswuwnwrwowswpwswowkwjwrwqw"
+ "rwpwkwkwtwnwkxsxqxswowswpwswnwswpwswowrwnwmwrwqwqwqwswswrwswowswjwpwlxjwkxuxLw[wcw_wSwkw mw\"wlwiw=wt"
+ "wmxlwFw cwswnwuwnwkwjwswo{pwrwpwtwtwpwswby`w`yUwlwtwpwqwpwswowlw\\wrwrxuwHwrwfwuwjwlwlwTyuwVwlwtwawsw"
+ "owswowswcwuwmwuwmwuwmwuwmwuwlwkwuwnwswpwkwkwkwkwkwkwkwkwswoxswowswowswowswowswowswowrwpwswpwrwpwrwpw"
+ "rwpwrwpwswoznwtw Ww (wGwtwtwqwqwqwuwuwuwqwswuwqwqw=wqxtw`{nzp~q{ozowrwnxmwtwow bzawkwuwl}rwuwnwtwuw"
+ "nwtwowkwjwlyjwIwlwswmwiwkwnwuwnwkwhwnwswowswowkwewewixnwsytwswuwnwrwpwkwrwpwkwkwkwrwpwkwkwuwmwkxsxqw"
+ "uwtwpwqwqwswowqwqwswowiwmwrwpwswowtwtwpwuwmwuwjwowkwjwlxsxXynzmymznyozlzoznwkwkwtwnwkzuyrzmynzmzowux"
+ "myozmwswpwrwowtwtwrwrwpwrwp{mwlwiwHyuwpwtwkwmxlynzoxswmwmwswnwswowtxq|owtwtwpym{p{owswnwuwmwlwkwqwqx"
+ "uwuxqwrwpwtwtwqwqwowlwuwuwkwmwlwtwowuwuwdwjznwl{nwuwnwkx_wtxtwswtwlwtwWwuytwgyjwmwjwawswoyuwVwlwtwnw"
+ "twmwtwnwtwmwuwmwlwuwmwuwmwuwmwuwmwuwmwuwmxuwowkwkwkwkwkwkwkwkwkwrwpwuwtwpwqwqwqwqwqwqwqwqwqwowtwpwsw"
+ "uwqwrwpwrwpwrwpwrwowuwnwswowuwlymymymymymymyuyqymymymymynwkwkwkwjynzmymymymymykwmzowswowswowswowswpw"
+ "rwozowrwW}q}qwtwtwqwtwtwqwtwtwA}rwuw_{p~r~r}pwtwowrwnxmwtwow aw_w]wtwpwuwmxuwmybwjwlyjwIwlwswmwiwnyn"
+ "wtwnznzkwmynwswTyp}pylwmwtwtwtwswuwn{owkwrwp{o{owk|pwkwkxlwkwuwuwuwqwuwtwpwqwqwswowqwqwswoykwmwrwpws"
+ "wowuwuwuwowkwjwnwkwjwDwowswowkwswowswowkwswowswowkwkwuwmwkwswswswswowswowswowswoxlwswowkwswpwrwowtwt"
+ "wqwtwowrwlwoxkwhxVxuxpwtypwuwjwnwtwnwkwswowtxnxmwswowqwqwtwuxqwtwnwtwtwqwswowswmwm{nwuwlxnwkwqwqwtwt"
+ "wqwrwpwtwtwqwuyuwpwiwhwnwmwrwnwbwkwuwlwlwswoxuxowlwtw`wuwrwszmwtwo}dwuwtwuw[}qymx`wswoyuwow_ylxlwtwo"
+ "yuwoyuwoyuwmwlwuwmwuwmwuwmwuwmwuwmwuwmwt{swk{o{o{o{owkwkwkwlztwpwuwtwpwqwqwqwqwqwqwqwqwqwnxowtwtwqwr"
+ "wpwrwpwrwpwrwnwmwswowuwiwkwkwkwkwkwkwswswkwswowswowswowswowkwkwkwkwswowswowswowswowswowswowswcwtxows"
+ "wowswowswowswpwrwowswpwrwWwtwtwqwqwqwuwuwuwqwuwswqwqw>wowuw`}q~q|q}qwrwpwrwowtwnwtwo~ izaw]wtwoykwux"
+ "qwtwswfwjwmwuwuwn}eyaxlwswmwjwjwpwswjwowswmwmwswnzWy]ypwlwtwtwuwswswowrwpwkwrwpwkwkwsyqwrwpwkwkwuwmw"
+ "kwuwuwuwqwtwuwpwqwqznwqwqzkynwmwrwowuwnwuwuwuwowkwjwnwkxkwGzowswowkwswo{owkwswowswowkwkxlwkwswswswsw"
+ "owswowswowswowjxmwkwswowtwnwuwuwuwpxmwtwlwlwlwiwlytwewtwtwqwswowtxoznwswnxmwswnwuwmwuwnwswowtwtwqwtw"
+ "twqwtwnwtwtwqwswowswmwmwswowswmwmwkwqwqwtwtwqwrwowuwuwpwuyuwq~own~own~owbwkwuwmznwswmwbwswawuwrwgwtw"
+ "hwdwuytwXwJwswnxuw=wtwmwswowtxowswqxmwswowswowswowswowswowswnwtwowkwkwkwkwkwkwkwkwkwrwpwtwuwpwqwqwqw"
+ "qwqwqwqwqwqwnxowtwtwqwrwpwrwpwrwpwrwnwmwswowtwmznznznznznzn~swk{o{o{o{owkwkwkwkwswowswowswowswowswow"
+ "swowswo}qwuwuwowswowswowswowswowtwnwswowtwUwuwuwowswowswowswowsw@}qx`}q~pzo{pwrwpwrwowtwnwtwow aw_w_"
+ "}owuwmwuwtwrwswuwewjwkwiwJwkwswmwkwiwp|kwowswmwmwswkwWym}mypwlwszr{owrwpwkwrwpwkwkwqwqwrwpwkwkwtwnwk"
+ "wtwtwqwtwuwpwqwqwkwqwqwtwiwnwmwrwowuwnwuwuwuwpwuwlwkwmwjwkwHwswowswowkwswowkwkwswowswowkwkwuwmwkwsws"
+ "wswswowswowswowswowhwnwkwswowtwnwuwuwuwpxmwtwmwkwlwiwmwtydwtwtwqwswowswowtwnwswowkwswnwuwnwtwnwswowt"
+ "wtwqwtwtwqwtwnwtwtwqwswowswmwmwswowswnwlwkwqwqxuwuxqwrwnyowqwpwiwhwpwuwuwowrwpwuwuwdwkwuwlwlwswo{owk"
+ "xuwawtxtwszmwtwiwdwuwtwuwXwJwswmwuwKzmwtwlwtxowrwpwtxrxl{o{o{o{o{o{o{owkwkwkwkwkwkwkwkwkwrwpwtwuwpwq"
+ "wqwqwqwqwqwqwqwqwowtwpwuwswqwrwpwrwpwrwpwrwnwmznwswowswowswowswowswowswowswowswowkwkwkwkwkwkwkwkwkws"
+ "wowswowswowswowswowswowswcwuwuwowswowswowswowswowtwnwswowtwTymymymymy=wmw^wuwuwmxlxmyowrwowtwnwtwmxm"
+ "w bwswIwuwmwuwmwuwtwrxswdwjw]wJwkxuxmwlwlwswlwjwowswmwmwswlwSycyawlwswowrwowswpwswowkwjwrwqwrwpwkwkw"
+ "swowkwqwqwsxowswpwjwswpwswowrwnwmxtxnwlwswpwswmwlwlwjwkwHwswowswowkwswowswowkwswowswowkwkwtwnwkwswsw"
+ "swswowswowswowswowkwswowkwswnxlwswpwtwmxmwjwlwiwTxuxpwtxowswowtwnwswowkwswnynwtwnwswowtwtwqxuwuxqwtw"
+ "nwtwtwqwswowswmwlwuwnwswowkwjwswo{pwrwmwmwswnwjwiwnymwtwnycwkwuwlwl{mwmwiw_wrwdwtwVwrw*wswmwuw?wtwlw"
+ "tzqwrwpwtzswkwswowswowswowswowswowswowswnwswpwkwkwkwkwkwkwkwkwswowsxowswowswowswowswowswowrwpwswpxtx"
+ "pxtxpxtxpxtxnwmwkwswowswowswowswowswowswowswowtxowkwswowswowswowswowkwkwkwkwswowswowswowswowswowswow"
+ "swlwnxtwowswowswowswowswnxmwswnx >wlw\\wkx`wnwrwoznwtwmxl| gybw^wtwozmwsxpzuxfxlx]wnw_wlxjyn{o{nykwnz"
+ "mymwkynymwkwewewjwjwrwswqwp{myozn{owizpwrwpwkwkwrwp{owqwqwsxnyowiyowrwozmwlzmwlwswqxsxnwm}qwjxlwGzoz"
+ "mymznynwjzowswowkwkwswowkwswswswswnynzmzowjymxlznxlwswqwrwnwm{mwlwiwHxuxpzmxlymynwswmwnwrwozmxuxo{pw"
+ "txn{pzmykwmyo}p{owkyuynwnwrwmwly`w_w_wbwjzo{pwqwnwmwhw_z>zY}M|nwuw2wqwqwryrwqwqyowqwqwqwqwqwqwqwqwqw"
+ "qwqwqwr{qyo{o{o{o{owkwkwkwkznwsxnymymymymycwuynznznznzmwmwkwuynznznznznznznyuzrymymymymynwkwkwkwjynw"
+ "swnymymymymybzmznznznznwlzmw hwHwlwSwTw <w8z ]x tx Zxjwmx RwWw/wgw pw_ynwky=wCwmwaw\\w_wnw 1wIwl"
+ "z 'wiwuwaw mw Pw swlwjw hw f| pyWx/wgw rxSw/wCwmwaw\\w_wnw 1w AwRx nw Pw txk"
+ "wlxm";
+
+ // Define a 20x23 font (normal size).
+ static const char *const data_font20x23 =
+ " 9q\\q^r_rnp`qnq`plp7q\\q^q_qmqbq\\q^q_qmqHqmp_q\\q^r_rnp"
+ "`qnq7q\\q^q_qmq_q \"r Mq^q^qnq`pnr`qnq`plp6q^q^p"
+ "mp`qmqaq^q^pmp`qmqIpmq]q^q^qnq`pnr`qnq6q^q^pmp`qmq`q \"plp 'q 5qmq Vq "
+ " Xq [plp 3qYq_p^rnpLplp8qYq_qNqYq_q4rmpaqYq_q_rmp%qYq^pGq Irc|!pKp]raqjq`p "
+ "HtNq_qmq\\plqbp_shpdscq[q^q[p [q]s_r`uau]rbv`tcxbuat LsZucrav_udwcxdw`udqiqeq]q]qjreq]sksgrjqbtcv_tcv"
+ "aud{eqiqgqfqgqjsjqlrjrhrirfzfs`q[sZqMqJqCqNsLq]q]q]q]q .scq]s \\sKt%r [s^raxdxat_qazgqlqlqctJqIqIq"
+ "LqHsOqiqOtaqmq\\uft nufu`sLs`t\\qKv<r\\rLrepirepitgpeq]r^r^r^r^r^r^{gudxdxdxdxdq]q]q]q]wcrjqbt`t`t`t`tL"
+ "tlpgqiqeqiqeqiqeqiqgrireq[s_q[q_pnp_pnr`qnq`plp7q[q_s`qmqcq[q_s`qmq]pkpbpmr`q[q_s`pmraqmq8q[q^pnp_qn"
+ "q^qaq\\qnq !pnqd{!pJp^tdunucr _y dvOq_qmq\\plpap_pmpipdudq[p\\p_plplp _q^ubtawcw^rbvavdxcwcw Ou]yerawb"
+ "xeyexdwbxeqiqeq]q]qkrdq]sksgrjqdxewbxewcwe{eqiqfqhqfqjsjqkqjqfqiqezfs`q[s[sMpJqCqOtLq]q]q]q]q q 1tc"
+ "q]t ^vaq_w&r \\u_raxdxcxcuczgqlqlqexMsJqJsMq[p^uPqiqdq]uaqmq]qkqcq!qkqguaqmqNpkp\\p]pKtmp:p]plpKpfpfp"
+ "fpcpipdq]r^r^r^r^r^r^{ixexdxdxdxdq]q]q]q]yerjqdxdxdxdxdxPwnpfqiqeqiqeqiqeqiqfqiqdq\\u_p[p^pnpKqnq_r5p"
+ "[p^pmp`qmqbp[p^pmp`qmq]tKp[p^pmpLqmq7p[p]pnp_qnq^p`q\\qnq5uauauauaucq`qhq4p]pKr_ueunucr `q \\rkpOq_qm"
+ "q\\plpctbqmqkqerlpdq\\q\\q_qnpnq\\q%q^qkqcqnqapjrdpjr`sbq]rkp^qcrkrerkq Oplr`sirgtbqkrdripeqjsfq]q]ripeq"
+ "iqeq]q]qlrcq]sksgskqerjrfqkrdrjrfqkrerjp`q`qiqfqhqeqkskqiqlqdqkq\\qeq]qZq\\qmqNqKqCqOqIq5q]q q 1q`qZq"
+ " _rlqbtaqjp$q ^qkqatbr^q]rjrewdqhqgqlqlqfrjrOuKqKu8p_rlpOqkqcq]qFpgpcp\"pgpTpkp\\q^p\\p^qLump:p^pjpLpg"
+ "pepgpbpjpPt`t`t`t`t`qnq_qnqcripeq]q]q]q]q]q]q]q]qjsfskqerjrfrjrfrjrfrjrfrjrRrjrfqiqeqiqeqiqeqiqeqkqc"
+ "vbrlq`q]q_plp Iq]q_qmqNq]q_qmqKtIq]q_qmq ^q]q^plpKq`q mqkqcqkqcqkqcqkqcqkqdq`qhq5q^qLt`ueunudtasbqip"
+ "`q`pipcq [qIq_qmq`{gvcqmqkpdq_q\\q\\q]rZq%q_rkraqZq]qaqnqbq]qXqcqiqeqiq1pSpXq`qfrhqnqbqjqdq]qhqfq]q]q"
+ "]qiqeq]q]qmrbq]qnqmqnqgskqeqhqfqjqdqhqfqjqeqYq`qiqfrjreqkskqirnrdrmr]qdq]qZq]qkq)qCqOqIq5q]q q 1q`q"
+ "Zq _qkq_qaq mq ^qkqaqnqar_q]qhqfrnqnreqhqgqlqlqfqhqPwLqLw9p_q_phqdqkqcq]qGplslpiu#pmtlpUpkp\\q_q_r8u"
+ "mp:p^pjpLpgpepgperipcq^qnq`qnq`qnq`qnq`qnq`qnq`qmqcq]q]q]q]q]q]q]q]q]qhqfskqeqhqfqhqfqhqfqhqfqhqdphp"
+ "fqirfqiqeqiqeqiqeqiqermrcwcqkq [q 3qZp Oq nqmqmqeqiqeqiqeqiqeqiqeq_piq4q^pLvatd|evdvcqipasaqkqdq "
+ " [qHq_qmq`{hrnpmpcqmqlpcq_q\\pZp]rZq%q_qiqaqZq]qapmqbq^qWqcqiqeqiqdq]qUsSs[qaqdqhqnqbqjqeq\\qgqgq]q^q\\"
+ "qiqeq]q]qnraq]qnqmqnqgqnqlqfqfqgqjqeqfqgqjqeqYq`qiqeqjqdqlqmqlqhqnqbqmq]rdq]qZq^pgp=taqns`s`snqatdv_"
+ "snqcqnsbq]q]qkqcq]qnsmshqns`saqnsasnqcqnr`tbvaqjqeqiqdqkqkqjrkreqiqdw`q`qZq#tnreqkq^qatauaqnsdqiq`ra"
+ "qjqdqiqdpmrcxdqmqmqatbxfyeqiqbqnq`r`q^qfqhrmqmrfqhqgqlqlqgqfqep[pnqnp[p`q`pipbpnqnpNq]taq^qnqnqbqmqb"
+ "q\\qIqmpkpmqkqkp$qmpkpmqVqmq\\q`q[pLqjqeump:p^pjpLphpdphpapkpbq^qnq`qnq`qnq`qnq`qnq`qnq`qmqdq\\q]q]q]q]"
+ "q]q]q]q]qgqgqnqlqfqfqhqfqhqfqhqfqhqfqfrjrhqiqnqgqiqeqiqeqiqeqiqdqmqbqkrdqmsbt`t`t`t`t`t`tlsfs_t`t`t`"
+ "tbq]q]q]q[tbqns`s_s_s_s_s\\q`smpdqjqdqjqdqjqdqjqeqiqdqnscqiq;qlqlqgqgqgqnqmqnqgqjqnqgqgqfq_qjq<{fpjpL"
+ "vatd|fxeqkqdqipasaqkqdp \\yNqGplqeqmp`qmqmqcrLqZq`qnpnq\\q%q_qiqaqZq^rbqmqbubqms^qaqkqdqiqdq]qXuf{fu_"
+ "q`qlrnqlqjqlqcqkreq\\qgqgq]q^q\\qiqeq]q]t`q]qnqmqnqgqnqlqfqfqgqkreqfqgqkres[q`qiqeqjqdqlqmqlqhs`s]rcq]"
+ "qZq#vbwcvbwcwev`wcwcq]q]qlqbq]vnthwcwcwcwcubwcvaqjqdqkqcqkqkqiqkqdqiqdw`q`qZq7smsfxdqlr^qavdvawdqkq_"
+ "raqjqdpgpeqntdxdqmqmqcwdyfyeqiqcqlq`raq^qfqhqlqlqfqhqgqlqlqgqfqfrZqZraqarkraqLq^vbq^wbqmqbq]tKpmpfpk"
+ "pjp_plp9plpkplpUs[qaqZpLqjqeump:p^pjpaplp_piqdpiqaplqbq_qlqbqlqbqlqbqlqbqlqbqlqbrmqdq\\q]q]q]q]q]q]q]"
+ "q]qgqgqnqlqfqfqhqfqhqfqhqfqhqfqerlrgqjqmqgqiqeqiqeqiqeqiqcsaqjqdqnq`vbvbvbvbvbvbvnuivbwcwcwcwcq]q]q]"
+ "q]wcwcwcwcwcwcwOwcqjqdqjqdqjqdqjqeqiqdwdqiq;pkqkpgpepgpmumpgpjrmpgpepfq_qkq;{hrkpLxdxf|fxepipdqipas`"
+ "pkpcp ZqHqGplpdt_pmplpmshsMqZqaplplp]q&q^qiqaq[qat`plqbvcx_q`ucrkr:uc{cucq`qlvlqjqlqcwdq\\qgqgxdvcqj"
+ "tfyeq]q]s_q]qmsmqgqmqmqfqfqgwdqfqgwcv_q`qiqdqlqbqmqmqmqfr`s]qbq\\q[q#pjqcrlrdqkpcrlrcqkrdq^rlrcrlrdq]"
+ "q]qmqaq]rlrlqirlrdqkqcrlrerlrcr_qjpbq]qjqdqkqcqlslqhqmqbqkq^q_q`qZq_tjpSqmsmpgrlsdqnsaqmqbqkqdq\\rlrd"
+ "qlq_raqjqeqgqgrnqnrdqlqcqmqmqcqkqerkq`qaycqlq_rbq^qfqhqlqlqfqhqgqlqlqgqnvnqgrYqYrbqbrirbqLq_rnpmpdwa"
+ "qmqcydq^qlqLpmpfpkpkq`plpa{RpltkpB{gpXpLqjqdtmpcqHp]plp_plp`pipjpipipmsfplpjphr_qlqbqlqbqlqbqlqbqlqb"
+ "qlqbqlxkq\\xdxdxdxdq]q]q]q_vjqgqmqmqfqfqhqfqhqfqhqfqhqfqdrnrfqkqlqgqiqeqiqeqiqeqiqcsaqjqdqnq`pjqcpjqc"
+ "pjqcpjqcpjqcpjqcpjrlrjqkpbqkrdqkrdqkrdqkrdq]q]q]q]qkrdrlrdqkqcqkqcqkqcqkqcqkqOqkqcqjqdqjqdqjqdqjqdqk"
+ "qcrlrdqkq:pnwnpgpnwnpgplslpgpkrlpgpkqkpfq^qlq6qaqlpMzfzfzfzgqipdqipbqmp`qmqc| fqHqHqlpcuasmplpmpiul"
+ "qSqZq]p^{+q^qiqaq\\q`ubqlqbpkrdrkrarawcx<tEteq`qlqlqlqjqlqcwdq\\qgqgxdvcqjtfyeq]q]t`q]qmsmqgqmqmqfqfqg"
+ "vcqfqgv_t`q`qiqdqlqbqmqmqmqgs_q]qaq\\q[q\"vcqjqeq]qjqdqiqdq^qjqcqjqdq]q]qnq`q]qkqkqiqjqeqiqdqjqeqjqcq^"
+ "s^q]qjqdqkqbqmsmqgqmqbqkq_qas_qYsc{Spkqkphqkrcqntcvcqiqeq\\qjqdqmr`tbqjqeqgqgqmqmqdqlqcqmqmqdqiqfqiqa"
+ "qaycqlq_qaq^qfqhqlqlqfqhqfqmqmqfqnvnqh}cqc}cqc}cqLq_qmpawbqkqasaq^qkqMpmpfpjsnpaplp`{RplpmqkpB{huatK"
+ "qjqbrmpcqJt^r]plpctlpjqktlpmpkpltlpjqhq^qlqbqlqbqlqbqlqbqlqcrlrcqlxkq\\xdxdxdxdq]q]q]q_vjqgqmqmqfqfqh"
+ "qfqhqfqhqfqhqfqcteqlqkqgqiqeqiqeqiqeqiqbq`qkrdqmravbvbvbvbvbvbvjqkq]qiqeqiqeqiqeqiqdq]q]q]q^qiqdqjqe"
+ "qiqeqiqeqiqeqiqeqiqd{hqkpnqdqjqdqjqdqjqdqjqdqkqcqjqdqkq:pnwnpgpnwnpgplslpgplrkpgpkqkpfq^qlq6qaqmqMzg"
+ "|fxdxfqipdqipbqmqaqmqcp \\wLqK{dt]qmqmqkrmrnrSqZqK{TtKq^qiqaq]r\\rdqkq\\qdqiqaqarkrcsmq<tEtfq_qlqlqlqk"
+ "qjqdqjqeq\\qgqgq]q^qgqfqiqeq]q]qnraq]qmsmqgqlqnqfqfqgq^qfqgqkq]raq`qiqdqlqbqnqkqnqgt`q^raq\\q[q#wcqjqe"
+ "q]qjqdydq^qjqcqjqdq]q]s_q]qkqkqiqjqeqiqdqjqeqjqcq]uaq]qjqcqmqaqmpmpmqfs`qmq_ras_qYscpjtRpkqkphqkrcqk"
+ "reqlrcqiqcr_qjqdqmq_qnqbqjqeqlqlqgqmqmqdqlqcqmqmqdqiqfqiqaqaqiqdqjqaq`q^qfqhqlqlqfqhqfrnqnrfqfqh}cqc"
+ "}cqc}cqLq_qmp_q^qkq`qMrlqMpmpfpWplpUqRplplqlp=q&qjq`pmp _plp]qkpnpdqhpeqkpnpiq^qjqdqjqdqjqdqjqdqjqdq"
+ "jqdqkqdq\\q]q]q]q]q]q]q]q]qgqgqlqnqfqfqhqfqhqfqhqfqhqfqbrdqmqjqgqiqeqiqeqiqeqiqbq`wcqlrcwcwcwcwcwcwc~"
+ "kq]yeyeyeydq]q]q]q^qiqdqjqeqiqeqiqeqiqeqiqeqiqd{hqlpmqdqjqdqjqdqjqdqjqcqmqbqjqcqmq9pkqkpgpepgpmumpgp"
+ "mrjpgpepfq]pmq:{epmpLzg|evbveqipdqipbqmqaqmpbq [qHqK{cpmq^plqmqkqktRqZqFqOtKq^qiqaq^rZqdy^qdqiqaqaq"
+ "iq]q:uc{cudq_qlqlqmqjxdqiqfq\\qgqgq]q^qgqfqiqeq]q]qmrbq]qlqlqgqlqnqfqfqgq^qfqgqkr]qaq`qiqcqnqaqnqkqnq"
+ "hrnq`q_r`q\\q[q$qjqcqjqeq]qjqdydq^qjqcqjqdq]q]s_q]qkqkqiqjqeqiqdqjqeqjqcqZsbq]qjqcqmqaqnqmqnqfs`qmq`r"
+ "^r`qZr9pkqkphqkrcqjqeqkqcqiqet_qjqcqnq`rnqbqjqeqlqlqgqmqmqdqlqcqmqmqdqiqfqiqaqaqiqdqjqbr`q]qhqgrmqmr"
+ "fqhqeweqfqgrYqYrdpnqnpdrirdpnqnpNq_qmp_q]qmqcyPrmqMqmpkpmqkvaplpVqRqmpkpmq=q&qjq`pmp(v_plp\\pkpmpdphq"
+ "epkpmpjq]xdxdxdxdxdxdwdq\\q]q]q]q]q]q]q]q]qgqgqlqnqfqfqhqfqhqfqhqfqhqfqcteqnqiqgqiqeqiqeqiqeqiqbq`vbq"
+ "jqeqjqdqjqdqjqdqjqdqjqdqjqdqjxkq]yeyeyeydq]q]q]q^qiqdqjqeqiqeqiqeqiqeqiqeqiqQqmplqdqjqdqjqdqjqdqjqcq"
+ "mqbqjqcqmq9qlqlqgqgqgqnqmqnqgqnqjqgqgqfq]qnq:{eqnpLzg|dt`tdqipcpipbpkp`sbq Zq plq`pmq_pkqmqkqjrQqZq"
+ "Fq'q]rkraq_rYqdy^qdqiqbq`qiq^q6uf{fuaq_qlyjzeqiqeq]qhqfq]q]qhqfqiqeq]q]qlrcq]qlqlqgqkseqhqfq]qhqfqjq"
+ "]qaq`qiqcqnq`skshrmraq_q_q[q\\q$qjqcqjqeq]qjqdq\\q^qjqcqjqdq]q]qnq`q]qkqkqiqjqeqiqdqjqeqjqcqXqbq]qjqcq"
+ "mqaqnqmqnqgqmq`s_q\\q`qZq7pmpnqmpgqkrcqjqeqkpbqiqeq\\qjqcs_qlqcqjqeqlqlqgqmqmqdqlqcqmqmqdqiqfqiqaq`qkq"
+ "drjrdr_q]riqfrnqnreqhqducqhqerZqZrdwdrkrdwOq_qmp_q^w`q`q[sKplslpTplpWqQpmpkqnp<q&qjq`pmp aplp\\pkplpe"
+ "phqepkplpjq^zfzfzfzfzfzfxcq]q]q]q]q]q]q]q]q]qhqfqkseqhqfqhqfqhqfqhqfqhqcrnreriqfqiqeqiqeqiqeqiqbq`q]"
+ "qjqeqjqdqjqdqjqdqjqdqjqdqjqdqjqdq]q]q]q]q\\q]q]q]q^qiqdqjqeqiqeqiqeqiqeqiqeqiqQqnpkqdqjqdqjqdqjqdqjqb"
+ "saqjqbs7qmqmqeqiqeqiqeqiqeqiqeq]qnp7q]rJrnpnresnpnsct_rcqipcqkqcqkqasaq [rkp&plpcplpnr`qkqmqkrltRqZ"
+ "qFq'q\\qkq`q`r_pjr^qcpjrcqkrbq`rkrdpkr3sSsLrlrnrhqhqeqjreripeqjsfq]q]riqfqiqeq]q]qkrdq]qgqgqkserjrfq]"
+ "rjrfqjrfpiraq_qkqbt`skshqkqaq`q^q[q\\q$qkrcrlrdqkpcrlrcqipdq^rlrcqjqdq]q]qmqaq]qkqkqiqjqdqkqcrlrerlrc"
+ "q^pjqbq]rlrbs_rkrfqmq`s`r\\q`qZq6qlrfrmscrlrepkqbrkqdqkpaqjqcs`rlqcrlrernsnrgrnqnrdqlqcrnqnrdrkqdqkra"
+ "q`qkqdqhqer^q\\rkqdwdqhqbqarjrdpYqYpbubpipbuNq_rnpmpbq^qnqnq`q`qZqIpgpRplp7pgp;q&rlr`pmp bplp[pkufpiq"
+ "dpkukrlpcqhqfqhqfqhqfqhqfqhqfqhqfqjqcripeq]q]q]q]q]q]q]q]qjsfqkserjrfrjrfrjrfrjrfrjrdrlrfrjreqkqcqkq"
+ "cqkqcqkqaq`q]qnplqeqkrdqkrdqkrdqkrdqkrdqkrdqksjpjqkpbqipdqipdqipdqipdq]q]q]q]qkqcqjqdqkqcqkqcqkqcqkq"
+ "cqkq^qbqkqcrlrdrlrdrlrdrlrbsarlrbs6qkqcqkqcqkqcqkqcqkqdq\\r7q\\qFp\\p]r^rcqipcvbqkqas`r \\vOqIqlpcw_pip"
+ "mpivnrRpZpEqbqIq^q[ubwdxdw]qcwbwaq_wcvbq]qRpSp[q^q^qhqexcxeyexdq\\xeqiqeq]q]qjrexdqgqgqjrdxeq\\xeqiqfx"
+ "`q_war_ririqiqbqazfq[q\\q$xcwcvbwcxdq]wcqjqdq]q]qlqbq]qkqkqiqjqdwcwcwcq^wbu`wbs_rkrgqkq`q`w`q`qZq$yew"
+ "dqmq`wdvaqjqbr`qkqcyeyewcqlsdwcxdw`sauczexdq^umteucqhqbq`xLqJsKsMq^vdxdpgpaq`qYqIqkq bqkq?{+yapmp Jp"
+ "fpfpipcpfpiucqhqfqhqfqhqfqhqfqhqfqhqfqjxixexdxdxdxdq]q]q]q]yeqjrdxdxdxdxdxdrjrgpnwdwcwcwcwaq`q]qnuex"
+ "dxdxdxdxdxdvnwjvbxdxdxdxdq]q]q]q]wcqjqdwcwcwcwcw^qbwbwcwcwcwaq`w`q4uauauauaucq\\r7p[qFp\\p\\p\\pbqipasap"
+ "ip`q^y ctNqIqmqbu_phsgslrSq\\qEqbqIq^qZsawdxcu\\qbt^taq]uataq]q q]qgpiqfqfw`udwcxdqZudqiqeq]q]qirfxdq"
+ "gqgqjrbtcqZtcqirfv_q]s_r_rirjrircqazfq[q\\q#tnqcqns`s`snqaucq\\snqcqjqdq]q]qkqcq]qkqkqiqjqbsaqnsasnqcq"
+ "]t_t_snqaq^rkrhrkraq`w`q`qZq#smrevbs^t`s`qjqbq`qiqdqnrmqdrmrcubqkrcubqntat^r`sc|fxdq^umtcqaqhqbq^tJq"
+ "IqIqLq]tcxLq`qYqHu `u>{+qnrmqapmp Kpepgpiuhpephscqfqhqfqhqfqhqfqhqfqhqfqhqixgudxdxdxdxdq]q]q]q]wcqjr"
+ "bt`t`t`t`taphpgplt`s_s_s_s_q`q]qmsctnqctnqctnqctnqctnqctnqbsktgs_uauauaucq]q]q]q[saqjqbs_s_s_s_sNpms"
+ "_snqbsnqbsnqbsnqaq`qns_q !p Zp jp#q\\q6q7q lq [sjq Qq -q OqZq]q Cq;q HqWq $rIq`qZq _q iqbqK"
+ "qFqIq`q hp$q]u JqYpmpLp .p jp ]p Xr`q[r !p Tp\"p\\p6q6q mq Yx Qr -r Ps\\q_s"
+ " Ipkq:q HqWq $qHq`qZq _q iqbqKqFqIq`q hp$q]t IqYpmpLq /q kq Fq_q[q #s Tp\"q"
+ "^q6p 1p Vu Rs YsJsMy &v<s HqWq &sHtcq]t _q iqbqKqFqIq`q hp$q 2q2q /q kq Hs_"
+ "q]s \"q (r Xy %t;r GqWq &rFscq]s ^q iqbqKqFqIq`q ,q4r 0r lr G"
+ "r^q *q "
+ " kr i";
+
+ // Define a 47x53 font (extra-large size).
+ static const char *const data_font47x53 =
+ " "
+ " 9])]2_2]T\\8^U^3] E])]"
+ "2`4^U^>])]2_4^U^ 6^T\\5])]1_2]T\\8^U^ K])]2`4^V^3] "
+ " "
+ " U]*\\2a4`V\\8^U^5a F]*\\1\\X\\4^U^=]*\\"
+ "2a5^U^ 7aV\\4]*\\1a4`V\\8^U^ J]*\\1\\X\\4^V^3\\ "
+ " "
+ " S],\\1\\W\\5g8^U^6c F],\\1\\V\\5^U^<],\\2]W]6^U^"
+ " 8h3],\\0\\W\\5g8^U^ I],\\1\\V\\5^V^4\\ ;] "
+ " "
+ " :\\-]2\\U\\6\\V`7^U^7]U] F\\-]2\\T\\6^U^;\\-]3]U]7^U^ 8\\"
+ "Va1\\-]1\\U\\6\\V`7^U^ H\\-]2\\T\\6^V^5] =a J] "
+ " "
+ " N\\/]2\\S\\7\\T]6^U^7\\S\\ E\\/]2\\R\\7^U^:\\/]3]S]8^U^"
+ " 8\\T^/\\/]1\\S\\7\\T]6^U^ G\\/]2\\R\\7^V^6] =c L^ "
+ " *^ U` "
+ " O^ )\\S\\ !^$^3\\ E]"
+ "U\\ K^$^4^ G^$^4] J^$^3\\ #^$^3\\ 4^ B[ "
+ " &^ Xe "
+ " S^ (\\S\\ )Z Q^&^3^2]S\\ A\\S\\ K^&^3^ F^&^4_ >]S"
+ "\\9^&^3^2]S\\ W^&^3^ 6^ Q] M[ ?` ![1^H]?` =]4](\\ %` >b4c Bb "
+ "?`2a .a Ib Pb Aa <a @b Fb =b F^ :] '] Da A].].].].] <_:]._ "
+ " Xh ?c W^ @` La Pa Sa Va5^U^ @` \"f4_ >`0`*^ $^.` <^F]F^F]G`G] "
+ " F\\S\\ ;b %a2a2a2a2a <bR\\ D`4^(^3`4`U\\8^V^6\\S\\ J^(^3`4^U^@^(^3_4^U^/^/`U\\8^(^3`"
+ "4`U\\8^V^ K^(^3`4^V^1^9]+^V^ ?` O\\ D\\6]M] We D]1]T] 9[3bJ\\@e<])]2])\\ "
+ " T]0d3_7h9i/_;k5f?n:f7e 3g :_8i3h@h9n?l5iB]H]C].].]J^B].`I`H_J]<g?g1g?g4hAuB]H]G]C]F]K"
+ "_K]S^J^F^G^CrBb7]*b'_ D] :] '] Fc A].].].].] >a:].a !^T_ Bg ` Dd2_8n?"
+ "m7g3]:rD]P]P]@g <] 8] 8] B] 3e J^K^ If7^U^+b@d Fb@f5a Ad4e-] :f Ra0d AaF\\HaF\\HeJ\\?]._0_"
+ "0_0_0_2\\U\\0tHh@n?n?n?n?].].].]-h:_J]<g8g8g8g8g BhV]G]H]C]H]C]H]C]H]G^G^B]*d5](]2\\X\\4aW]8^V"
+ "^6\\S\\ I](]3]X]5^U^?](]3\\W\\5^U^.^R[9aW]7](]2\\X\\4aW]8^V^ J](]2\\X\\4^V^1]8]+^V^ ?a>w "
+ "P[ 9[/a:aQa7[ Wl \"h E]1]T]+\\R\\;[4dL]Ag=])]2])\\ U^1f8c8k;j1`;k7h?n;h9g 5i*b:_"
+ "8k6kBl=n?l7mD]H]C].].]L_A].`I`H`K]>kAj6kAj9kBuB]H]F]E]E^L_L^R^L^D^I^BrBb7^+b(a D] ;] '] Gd"
+ " A].].].].] ;] (b:].b #^Q] Dj !a Ff3_8n?m8i4]:rD]P]P]Bk ?_ 9] 9_ C]&[0f "
+ "I]K]=]0g7^U^-fC\\S] IfBf6c B[S]5[S].] <i R\\W\\1]T] B\\W\\G]H\\W\\G]H[S]K]?]._0_0_0_0_2c1uIkBn"
+ "?n?n?n?].].].]-l>`K]>k<k<k<k<k EoF]H]C]H]C]H]C]H]F^I^A],h6]*]2\\V\\6]Wa7^V^6\\S\\ H]*]2\\V]6^U"
+ "^>]*]3]W]6^U^._V_;]Wa5]*]2\\V\\6]Wa7^V^ I]*]2\\V\\5^V^2]7]+^V^ @]W\\=v P[ 9\\1c<cSd:] "
+ "\"o #_S^ F]1]T],]S];[5^V^N]A_T]=]*]0]*\\ U]1^T^;e8`S_<^R_2`;k8^R]?n<_T_;^S^ 6^S_."
+ "i>_8m:`R`Cn?n?l9`QaE]H]C].].]M_@].aKaH`K]?`S`Bk8`S`Bk;_R_BuB]H]F]E]D]MaM]P]L]B^K^ArB]1]&])"
+ "c D] <] '] G] :].].].].] ;] (^6]*^ #]P^ E^P\\ V^ H^T^4_8n?m:`S`6]:rD]P]P"
+ "]C`S` Aa :] :a D]&[1^S\\ I^M^=]0^R[7^U^/^R^EZO\\ L^R^ N]U] :],\\0] <j M\\2]R] >\\H]B\\H]=\\M]>"
+ "]._0_0_0_0_0_/uK`R`Cn?n?n?n?].].].]-n@`K]?`S`>`S`>`S`>`S`>`S` H`ScE]H]C]H]C]H]C]H]E^K^@],^"
+ "T^5],]1\\V\\6\\U`7^V^6]U\\ F],]2\\T\\6^U^=],]2\\U\\6^U^-e9\\U`4],]1\\V\\6\\U`7^V^ H],]1\\V\\5^V^3]6]+^"
+ "V^ B`1`1`1`1`6]W]>u P[ 9]2e>eUf;^ %q $^O\\ F]1]T],]S];[5]T]N\\@]P[=]*]0]2ZR\\RZ $"
+ "]2]P]<_W]8]N]<ZL^4a;]+]MZ/]<^P^=^Q^ 7\\O]1nAa9]N_<_M]C]NaA].]+_L^E]H]C].].]N_?].aKaHaL]@"
+ "^M^C]P_:^M^C]P_=^M\\6]6]H]F^G^D]MaM]P^N^B^K^-^B]1]&]*e D] =] '] H] 9].].].].] ;] )"
+ "^5])^ %^O]8^3]LZ U] I^R^6a9_0]+^M^7]:]H]D]P]P]D^M^ Cc ;] ;c E]&[2^PZ H]M]<]1^-^U"
+ "^1]L];[ N]L] Q]S] :\\,\\1] <dU\\ M\\2\\P\\ >\\H\\A\\H\\<\\M\\=]/a2a2a2a2a1_/]V];_M]C].].].].].].].]"
+ "-]ObBaL]@^M^@^M^@^M^@^M^@^M^ J^N`D]H]C]H]C]H]C]H]E^K^@]-^Q]5].]1\\T\\7\\S]6^V^5c E].]2]S\\7^U"
+ "^<].]2\\S\\7^U^,a6\\S]2].]1\\T\\7\\S]6^V^ G].]1\\T\\6^V^4]5]+^V^ De6e6e6e6e9\\U\\>u P[ :_3f@gVf<"
+ "_ &r $]M[ F]1]T],\\R]>d<^T^P]A^OZ=]+].]4]T\\T] &^3^P^=[S]8[K].]4\\X];],]!]<]N]>^O^ "
+ " 8ZM^3`P`Ba9]M^=^J\\C]K_B].],^H\\E]H]C].].]O_>].aKaHaL]A^K^D]N^<^K^D]N^>]JZ6]6]H]E]G]C]MaM]"
+ "O^P^@^M^-^A]1]&]+_W_ D] >] '] H] 9] B].] ;] )]4](] %]N]:c6] G] J^P^7a8"
+ "_1],^K^;c=]H]D]P]P]E^K^ Ee <] <e F]&[2] =^O^<]1] 0\\H\\<\\ P\\H\\ R\\Q\\+]3\\,\\2] <eU\\ M\\3]P\\ >"
+ "\\I]A\\I]<\\N]=]/a2a2a2a2a2a1]U]<^J\\C].].].].].].].]-]K_CaL]A^K^B^K^B^K^B^K^B^K^ K]K^D]H]C]H]"
+ "C]H]C]H]D^M^?]-]P]4]0]1\\R\\ Ha C]0]2]R] E]0]2\\Q\\ 9c 9]0]1\\R\\ !]0]1\\R\\ ?]4] Di:i:i:i:i"
+ ";\\6]G] P\\ :`5g@gWh>a (_ J]KZ F]1]T],\\R\\?h>]R]P\\@]1]+].]3^V\\V^.] T]2]N]5]8ZJ]-]"
+ "6]X];]-]!^=]L]?]M] *]5_J_Ec:]L^>]H[C]I^C].],]F[E]H]C].].]P_=].]X]M]X]HbM]A]I]D]M]<]I]D]"
+ "M]?]%]6]H]E]G]C^NaN^N]Q^>^O^-^@]0]'],_U_ &] '] H] 9] B].] ;] )]4](] %]N]:d7] "
+ " F] K]N]8c8^1],]I]>i@]H]D]P]P]E]I] Fg =] =g G]&[2] <]O];]1] 1\\F\\=\\ Q\\F\\ S\\Q\\+]3\\."
+ "] IeU\\ M\\3\\N\\ ?\\I\\@\\I\\=]M\\<]0c4c4c4c4c3a1]U]<]H[C].].].].].].].]-]J_DbM]A]I]B]I]B]I]B]I]"
+ "B]I] L]J_E]H]C]H]C]H]C]H]C^O^>].]N] .] '`X_ I] FbWa=bWa=bWa=bWa=bWa<"
+ "\\6^I^ ?Z2[ :a5gAiXh?c *^ H] 7]1]T]-]S]Aj>]R]Q]@]1],],\\1^X\\X^,] T]3]L]6]'].]7]W]"
+ ";]-]!]<]L]?]M^ +]6^F^F]W]:]K]?]FZC]H^D].]-]DZE]H]C].].]Q_<].]X]M]X]H]X]M]B]G]E]M^>]G]E]"
+ "M^@]%]6]H]E^I^B]O^X]O]M^R^=]O^-^@]0]']-_S_ '] '] H] 9] B].] ;] )]4](] %]N]:e8"
+ "_ H] L]M]8]W]7^2]-]G]AmB]H]D]P]P]F]G] Hi >] >i J[3] ;^Q^;]1] 2\\RbT\\Ge R\\VdR\\ T\\"
+ "Q\\+]4\\2a IfU\\ M\\3\\N\\ ?\\J\\?\\J\\AaM\\ G]W]4]W]4]W]4]W]4]W]4c3^U]=]FZC].].].].].].].]-]H]D]X]"
+ "M]B]G]D]G]D]G]D]G]D]G]A[H[B]J`E]H]C]H]C]H]C]H]B]O^>g8]N] 1]T_ 3[ 9] "
+ "G_O^?_O^?_O^?_O^?_O^=\\5]I^ @\\3[ ;c6gAy?d7`8]L]7^7]L]>^ H] 6]1]T]-]S]B_W[U]>]R]R]?]1"
+ "],],]0d*] T]3]L]6]'].]7\\V];].] ]<]L]@]K] 7Z PZ X]7^D^G]W]:]K]?]/]G]D].]-]/]H]C].].]R_;]"
+ ".]X^O^X]H]X^N]B]G]E]L]>]G]E]L]@]%]6]H]D]I]A]O]W]O]L^T^<^Q^-^?]0]'].^O^ Sb7]U`2b4`U]8a8])`"
+ "7]T_ M].]%_O_@_2`0`3`/_3c9] )]4](] N_6]N]3^7a/c0_ <^ D[U^ Ga N]L]9]W]6^3]-]G]B`W"
+ "]W`C]H]D]P]P]F]G] I_X]X_ ?] ?_X]X_ Nb7]2ZFZ=]Q]:]0] 3[SfU[Ig R[UfS[ T\\Q\\+]5]2a IfU\\ M"
+ "\\3\\N\\ ?\\K]?\\K]AaN] G]W]4]W]4]W]4]W]4]W]4]W]3]T]=]/].].].].].].].]-]G]E]X^N]B]G]D]G]D]G]D]G"
+ "]D]G]B]J]C]KbF]H]C]H]C]H]C]H]B^Q^=j;]P_9b3b3b3b3b3b3bN`Bb3a2a2a2a V_2_2`1`1`1`1` ;aU] "
+ " :]U` S^T]U^A^L^A^L^A^L^A^L^?]5]I] @^5\\ <e7gAy@f;e:]L]8`8^N^?^ G] 6]1]T]-\\R\\A]U["
+ "RZ>]R]R\\>]1],],].`(] U^3]L]6]'].]8]V];].]!^<]L]@]K] :] P]#^8^A]I^W^;]K]@].]G^E].].].]H]"
+ "C].].]S_:].]W]O]W]H]W]N]C]E]F]L]?]E]F]L]@]%]6]H]D]J^A]O]W]O]L^U^:^S^-^>]0^(]/^M^ Wh:]Wd6f"
+ "8dW]:e>h2dW]?]Vd<].].]O_>].]WdScK]Vd8f;]Wd7dW]?]Wa6h>h6]L]B]I]A]P`P]K^L^B^K^@l4]4](] PdU"
+ "]A]N]2^8e5g;]Vd?^J^8]6]L] E]V`>pA]S]S]:e6kDo>]L]:^W^6^4].]E]D_U]U_D]H]D]P]P]G]E] K_W]W_ @]"
+ " @_W]W_ Qf9]3\\H\\>^S^:]0_ 6[ThT[K]Q\\ S[T\\R]S[ U]S]+]6],] ?]L]@fU\\ M\\3\\N\\ ?\\K\\>\\K\\;]O\\ G"
+ "^W^6^W^6^W^6^W^6^W^5]W]4^T]>].].].].].].].].]-]G^F]W]N]C]E]F]E]F]E]F]E]F]E]D_L_E]K]W]F]H]C"
+ "]H]C]H]C]H]A^S^<k<]Ra<h9h9h9h9h9h9hTeFf7e6e6e6e;].].].]\"^;]Vd8f7f7f7f7f/^6eX]@]L]?]L]?]L]?"
+ "]L]B^K^?]Wd>^K^ O]S]S]B]I]B]I]B]I]B]I]@]5^K^ @]4[ ;f8gAyAg<h<]L]8`7]N]>] F] 6]1]T]"
+ "-\\R\\B]T[6]R]S]>^2]-]*\\.`(] U]2]L]6]'].]9]U];].]!];]L]@]K] =` P`'^7]?\\I]U];]K]@].]F]E].]"
+ ".].]H]C].].]T_9].]W]O]W]H]W^O]C]E]F]L]?]E]F]L]@]%]6]H]C]K]@^P]W]P^K^V^9]S]-^=]/](]0^K^ Xi"
+ ";]Xf9h9fX]<h?h3fX]?]Xg=].].]P_=].]XfVfL]Xg:h<]Xf9fX]?]Xb7i>h6]L]A]K]@^Q`Q^J^N^@]K]?l4]4](]"
+ " QfW^A]O^1]6f9h;]Xg@_K]7]6]L]=]G]C^Wc@pA]S]S]<h9mDo>]L]:]U]5^5].]E]E^S]S^E]H]D]P]P]G]E]@"
+ "Z+]V]V^-Z4]5ZKZ:]V]V^ Sh9]4^J^>]S]9]._ 8[U_Q[T[L]P\\ S[T\\Q]T[ T]U]*]7]*] @]L]@fU\\ M\\3\\N"
+ "\\ ?\\L]>\\L]:]Q]:]1]U]6]U]6]U]6]U]6]U]6^W^5]S]>].].].].].].].].]-]F]F]W^O]C]E]F]E]F]E]F]E]F]"
+ "E]C_N_D]L^W]F]H]C]H]C]H]C]H]@]S];]P_=]S^8i:i:i:i:i:i:iVgIh9h9h9h9h<].].].]'d<]Xg:h9h9h9h9h"
+ "0^8k?]L]?]L]?]L]?]L]A]K]>]Xf>]K] O]R]R]D]G]D]VZOZV]D]KZV]D]G]A]4]K] @]3[ <g7fAyBi>j=]L]8"
+ "`7]N]?] F^ 6]1]T]5uI]T[6]R]S\\<^3]-]*]1d*] U]3]J]7]'].]9\\T];].\\Ua-^;]L]@]K^?].] Uc "
+ "Pc+_8]>]J]U];]K]@].]F]E].].].]H]C].].]U_8].]W^Q^W]H]V]O]C]E]F]L]?]E]F]L]@^&]6]H]C]K]?]Q^V]"
+ "Q]I^X^8^U^.^<]/](]1^I^ ]R_<aT_;_R\\:^Tb=_S^@h4_Ub?bT^=].].]Q_<].aT_X]T^LbT^;_T_=aT_;^Tb?aT"
+ "Z8_R]>h6]L]A]K]?]Q`Q]H^P^?]K]?l4]4](] R^U^W]@]O]0^7g;_S];bT^@`L]8_7]L]>]E]E^W]V]@pA]S]S]"
+ "=_T_<oDo?]K^;]U]5_6].\\D]E]R]R]E]H]D]P]P]G]E]A\\+[U]U\\,\\6]6\\L\\;[U]U\\ S_W[V\\9]3^V`V^=^U^9]/a"
+ " :[T]G[M\\O\\1ZQZ M[S\\P\\S[ Ud)]8](\\ @]L]@fU\\ M\\3\\N\\9ZQZ0\\L\\=\\L\\8\\Q\\9]1]U]6]U]6]U]6]U]6]U]6"
+ "]U]5]S]>].].].].].].].].]-]F]F]V]O]C]E]F]E]F]E]F]E]F]E]B_P_C]L]V^G]H]C]H]C]H]C]H]@^U^;]N^>"
+ "]T]6]R_;]R_;]R_;]R_;]R_;]R_;]R_X_T^K_R\\:_S^;_S^;_S^;_S^=].].].]*h=bT^;_T_;_T_;_T_;_T_;_T_1"
+ "^9_T`>]L]?]L]?]L]?]L]A]K]>aT_?]K] P]Q]R]E]F]E]V\\Q\\W]E]K\\W]E]F]A]4^L] A^@ZN\\ =i8e@yCk?^R^"
+ "=]L]9b8]O^?] Im B]1]T]5uI]T[6]S^T]<^3]-]*]3^X\\X^,] V^3]J]7](^/]9]T];e7]We/]9]N]?]K"
+ "^?].] Wd Nd._8]O`U\\T\\K]S]<]L^A]-]F^F].]/]-]H]C].].]V_7].]V]Q]V]H]V^P]D]C]G]L]@]C]G]L]?^']6"
+ "]H]C^M^?]Q]U]Q]Ic6^W^._<]/^)]2^G^ !ZM^=`Q^=^NZ;^Q`>^P^=].^Q`?`Q^>].].]R_;].`R^X\\R^M`Q^=^P^"
+ ">`Q^=^Q`?`1]MZ;].]L]A^M^?]Q`Q]G^R^>^M^1^4]4](] D]P^A]R^X]@]P^/]9^Vb=^NZ;`Q^AaN^8_7]L]>]E]"
+ "F^V]U]>]P]>]S]S]>^P^>`T`7]6]J]<]S]5^6]/]C]G]Q]Q]F]H]D]P]P]H]C]C^&]TZ,^7]7^N^6]TZ H]/^U[TZ9"
+ "]2n;]U]8]0d <[U]F[M\\P]2[R[ M[S\\P\\S[ Tb(]9]'\\ @]L]@fU\\ M\\3]P]9[R[1\\M\\<\\M\\7\\R\\8]2]S]8]S]8]"
+ "S]8]S]8]S]7]U]6]R]?]-].].].].].].].]-]F]F]V^P]D]C]H]C]H]C]H]C]H]C]B_R_C]L]T]G]H]C]H]C]H]C]"
+ "H]?^W^:]M]>]U^6ZM^<ZM^<ZM^<ZM^<ZM^<ZM^<ZMbP]M^NZ;^P^=^P^=^P^=^P^>].].].]+i=`Q^=^P^=^P^=^P^"
+ "=^P^=^P^2^:^P^>]L]?]L]?]L]?]L]A^M^>`Q^@^M^ P]Q]Q]F]E]F]W^S^W]F]L^W]F]E]B]3]M^ B^B^O[ =k8"
+ "d?xClA^P^>]L]9]X]8^P]>\\ Hl A] 9uI]T[5]T]T]:^ =]*]5^V\\V^.] V]2]J]7](]/^:]S];h:]Xg0]"
+ "9^P^?]K^?].]!e Je2_7\\PdW\\S\\L]S]<]M^@]-]E]F].]/]-]H]C].].]X_5].]V]Q]V]H]U^Q]D]C]G]L]@]C]G]M"
+ "^?`)]6]H]B]M]>]Q]U]Q]Hb5c-^;].])] B]=_O]=].]O_>]N^>].]O_?_O]>].].]S_:]._P`P]M_O]=]N]>_O]"
+ "=]O_?_1]-].]L]@]M]>]RbR]G^R^=]M]1^3]4](] FaSaD^Qa?]R_.]9]R`>]._O]>^N]8`7]L]>]E]G^U]U^?]P]"
+ ">]S]S]>]N]>^P^7]6]J]<]S]4^7]/]C]G]Q]Q]F]H]D]P]P]H]C]D_&]&_8]8_N_7] B]/]T[3]1l:^W^8]1]W` >\\"
+ "U\\E\\N\\P]3\\S\\ N\\S\\P\\S\\ S_']:]&\\ @]L]@fU\\ M\\2\\P\\8\\S\\2\\N]<\\N]7\\S]8]2]S]8]S]8]S]8]S]8]S]8]S]"
+ "7]R]?]-].].].].].].].]-]E]G]U^Q]D]C]H]C]H]C]H]C]H]C]A_T_B]M]S]G]H]C]H]C]H]C]H]>c9]M^?]U]']"
+ ".].].].].].`O^N].]N^>]N^>]N^>]N^?].].].],_R^>_O]=]N]=]N]=]N]=]N]=]N]2^:]O_?]L]?]L]?]L]?]L]"
+ "@]M]=_O]?]M] O\\P]Q]F\\D]F\\U^U^V]F\\L^V]F\\D]B]3]M] RuJ`O[ >m9c>wCmA]N]>]L]9]X]7]P]?] "
+ "Im A] 2\\R\\A]T[5^V^T\\:` ?](\\6]T\\T]/] V]2]J]7])^1_9]S];i;bS^2^8^S_>]K^?].]$e@u@e6_7]QfX\\S\\"
+ "M^S^=]N^?]-]E]F].]/]-]H]C].].c4].]U]S]U]H]T]Q]D]C]G]M^@]C]G]M]=c-]6]H]B]M]>^R]U]R^G`4c.^:]"
+ ".])] B]=^M]?^/]M^?]L]>]/]M^?^N^?].].]T_9].^O_O^N^N^?]M^?^M]?]M^?^0]-].]L]@]M]>^S]X]S^F^T"
+ "^<^O^2_3]4](] GcUcE]Pa?]Vb-]:]O_?].^N^>]O^8a8]L]?]C]H]T]T]?]P]>]S]S]?]L]@^N^8]6]J]=^S^4^8"
+ "]/]C]H^Q]Q^G]H]D]P]P]H]C]E_%]%_9]9_L_8] B]0^T[3]0_T_>cWc=]1]U_ ?[U\\C[N]R^4]T] N[R\\Q]R[ 'u"
+ "G]&] @]L]?eU\\ M\\2]R]8]T]3\\N\\;\\N\\7]S\\7]3^S^:^S^:^S^:^S^:^S^9]S]8^R]?]-].].].].].].].]-]E]G"
+ "]T]Q]D]C]H]C]H]C]H]C]H]C]@_V_A]N]R]G]H]C]H]C]H]C]H]>c9]L]?]U]'].].].].].]._M]O^/]L]?]L]?]L"
+ "]?]L]?].].].]-^O]>^N^?]M^?]M^?]M^?]M^?]M^ I]O`?]L]?]L]?]L]?]L]@^O^=^M]@^O^ P]P]P\\G]C\\G]T^"
+ "W^T\\G]M^T\\G]C\\B]3^O^ RuJ[X]P[ >o=\\XaX]BwDoC]L\\>]L]:^X^8]P]?] E] 5] 3]S]A^U[4dT];b @"
+ "](]6ZR\\RZ.] V]2]J]7]*^7d8]R];]R_<aQ^3]5f<^M_?].]'e=u=e:_6\\Q^S`S]N]Q]=l>]-]E]Fm>k=]-rC].]"
+ ".b3].]U]S]U]H]T^R]D]C]G]M]?]C]G]N^<f1]6]H]B^O^=]S^U^S]F_2a.^9].])] A]>^M]?].]M^?]L]>]/]M"
+ "^?^M]?].].]U_8].^N^N]N^M]?]L]?^M]?]M^?^0]-].]L]@^O^=]S]X]S]D^V^:]O]2_2]4](] H\\U^W]U\\E]Pa?"
+ "]Vb-];]M^?].^M]>^P]7a8]L]?]C]H]T]T]?]P]>]S]S]?]L]@]L]8]6p=]Q]3^9]/]C]H]P]P]G]H]C]Q]Q]G]ViV"
+ "]F_$]$_:]:_J_9] B]0]S[3]0]P]>o=]2]S_ @[U\\C[M]T_5^U^;u O[R\\R]Q[ 'uH]/ZQ] ?]L]?eU\\ M\\1]T]7^"
+ "U^4\\O]O]I\\O]T`MZQ]S]O]E]3]Q]:]Q]:]Q]:]Q]:]Q]:^S^9]QmO]-m>m>m>m>].].].]1hL]G]T^R]D]C]H]C]H]"
+ "C]H]C]H]C]?_X_@]O]Q]G]H]C]H]C]H]C]H]=a8]L]?]U]&].].].].].].^M]O].]L]?]L]?]L]?]L]?].].].].^"
+ "M]?^M]?]L]?]L]?]L]?]L]?]L] I]Pa?]L]?]L]?]L]?]L]?]O]<^M]?]O] O]P]P\\G]C\\G]ScS\\G]N^S\\G]P]P\\B"
+ "]2]O] QuF]Q[ >oAqDuDqD]L]?]L]:^X^8^R^?\\ D] 5] 3]S]@`X[3bS\\R^G]W^N] P](].\\&] W]1]J"
+ "]7]*^7c8]Q];ZM^=`O^4]4d:]M_?].])d:u:d=_5\\R]O^R\\N]Q]=j<]-]E]Fm>k=]-rC].].a2].]U^U^U]H]S]R]D"
+ "]C]G]N^?]C]G]P_:g3]6]H]A]O]<]S]S]S]E^1_.^8]-]*] A]>^M]?]/^M^?]K]?]0^M^?]L]?].].]V_7].]M]"
+ "M]N]L]@^L]?^M]@^M^?]/]-].]L]?]O]<]S]X]S]C^X^9]O]2^1]4](]0_IZ O[R\\X]S\\G^O_>]Vd9_U];]L]?].]L"
+ "]=]P]8]X^9]L]?]C]I^T]S]@]P]>]S]S]?]L]@]L^9]6p=]Q]3^9]/]C]H]P]P]G]H]C]Q]Q]G]ViV]G_#]#_;];_H"
+ "_:] B]0]S[3]0\\N\\>o=]2]Q^ A[U\\C[LcX\\6]T]9u O[RfP[ 'uIf7e >]L]>dU\\<] :f5d4]T]:fT\\O^NfT\\UdOeR"
+ "\\O^F^3]Q]:]Q]:]Q]:]Q]:]Q]:]Q]:^QmO]-m>m>m>m>].].].]1hL]G]S]R]D]C]H]C]H]C]H]C]H]C]>d?]P^Q]G"
+ "]H]C]H]C]H]C]H]<_7]L]?]U^'].].].].].].^L]P].]K]@]K]@]K]@]K]@].].].].]L]?]L]@^L]@^L]@^L]@^L"
+ "]@^L] I]Q]X^@]L]?]L]?]L]?]L]?]O]<^M]?]O] O\\WmX]H\\WmX]H\\QaR]H\\N^R]H\\O]P]C]2]O] QuF]R\\ ?qC"
+ "sDtDrE]L]?]L]:]V]7]R]>x '] 5] 3\\R\\?e3^R\\SbJ^V^O] P](].\\&] W]1]J]7]+^6e:]Q]-^>_M]5^6"
+ "h<^O` Qe8u8e@^5]R\\M]R\\O^Q^>m?]-]E]Fm>k=]KdFrC].].b3].]T]U]T]H]S^S]D]C]G]P_>]C]Gk6f5]6]H]A"
+ "^Q^<]S]S]S]F_1_/_8]-]*] A]>]K]A].]K]@]J]?]0]K]?]L]?].].]W_6].]M]M]N]L]@]J]@]K]A]K]?]/^.]"
+ ".]L]?]O]<]T^W]T]C^X^9^Q^3^1]3]']3dN\\ P\\R`Q[G]N_>]Q`;bW];\\K^?]/]L]=]Q^8]W]9]L]?]C]I]S]S]@]P"
+ "]>]S]S]@]J]B^L^9]6p>^Q^4^9]/]C]H]P]P]G]H]C]Q]Q]G]ViV]H_\"]\"_<]<_F_;] B]1]R[3]1]N]8a6]2]P^ B"
+ "[U\\C[K`V\\7]T]8u O[RdN[ 'uIf5a <]L]=cU\\<] :f3`1]T];fU\\N^NfU\\T[S]NaQ\\N^G^3^Q^<^Q^<^Q^<^Q^<^Q"
+ "^;]Q]:]PmO]-m>m>m>m>].].].]1hL]G]S^S]D]C]H]C]H]C]H]C]H]C]=b>]P]P]G]H]C]H]C]H]C]H]<_7]L]?]U"
+ "_(].].].].].].]K]Q].]J]A]J]A]J]A]J]@].].].].]L]?]L]@]J]A]J]A]J]A]J]A]J] K]P\\V]@]L]?]L]?]L]"
+ "?]L]?^Q^<]K]@^Q^ O\\WmX]H\\WmX]H\\P_Q]H\\O^Q]H\\O]P]C]2^Q^ D^<]R[ >qDuEsCqD]L]?]L]:]V]7]R]>x "
+ " '] 5] 3\\R\\=f+]TdL^T^P] P](].\\2u *]1]J]7],^-_=]P],]>_M]5]7_R^<^Qa Sd .dC^4\\R]M]R\\O]O"
+ "]>]N_@]-]E]F].]/]KdF]H]C].].]X^4].]T]U]T]H]R]S]D]C]Gk=]C]Gj1c6]6]H]@]Q];^T]S]T^Ga1].^7]-]*"
+ "] Lh>]K]A].]K]@]J]?]0]K]?]L]?].].]X_5].]M]M]N]L]@]J]@]K]A]K]?]._0].]L]>]Q];^U]V]U^Bb7]Q]"
+ "3^1^3]'^6iS^ P[P^P[G]N_>]N^=dX]<]J]>^1]L]=^R]8^W]9]L]@]A]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]>]O"
+ "]5^8]/]C]H]P]P]G]H]B]R]R]F]C]Iz<]<z=]=z<] B]1]R[7j:\\L\\7_5]2]P^ B[U\\C[ V]T]7u O[R\\U^O[ T] "
+ " ]L];aU\\<] I]T],]O[X\\>]K]@]O[X\\I`3]O]<]O]<]O]<]O]<]O]<]O];]P]?]-].].].].].].].]-]E]G]R]"
+ "S]D]C]H]C]H]C]H]C]H]C]<`=]Q]O]G]H]C]H]C]H]C]H];]6]L]?]T_4h9h9h9h9h9h9hK]Q].]J]A]J]A]J]A]J]"
+ "@].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J]?tG]Q\\U]@]L]?]L]?]L]?]L]>]Q];]K]?]Q] N\\WmX]H\\WmX]H\\P_"
+ "Q]H\\P^P]H\\O]P]C]1]Q] C]:]S[ ?sEvEqAoC]L]?]L];^V^8^T^>x '] 5] 4]S]<g-\\T^V^M]S_Q\\ O](]"
+ ".\\2u Se =^1]J]7]-^*^?]O],^?^K]7^7]N]<^Sb Sa (aC]3\\R\\K\\R\\P^O^?]L^A]-]E]F].]/]KdF]H]C].].]W"
+ "^5].]T^W^T]H]R^T]D]C]Gj<]C]Gj-`7]6]H]@]Q]:]U^S^U]Fb2]/^6]-^+] Nj>]K]A].]K]@p?]0]K]?]L]?]"
+ ".].b3].]M]M]N]L]@]J]@]K]A]K]?].c4].]L]>]Q]:]U]V]U]@`6^S^4^5b2]&b<u P[O]P\\H]N^=]M]>^Ua<]J]="
+ "c7]L]<]S^8]V^:]L]@]A]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]?^O^7^7]/]C]H]P]P]G]H]B]R]R]F]C]Iz<]<z="
+ "]=z<] B]1]R[7j:\\L\\7_ C^P] B[U\\C[ W]T] W] O[R\\T^P[ T] ]L]7]U\\<] H]T]-\\O\\X\\>\\I\\@\\O\\X\\J`"
+ "3^O^>^O^>^O^>^O^>^O^=]O]<^P]?]-].].].].].].].]-]E]G]R^T]D]C]H]C]H]C]H]C]H]C];^<]R]N]G]H]C]"
+ "H]C]H]C]H];]6]L]?]S`8j;j;j;j;j;j;|Q].pApApAp@].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J]?tG]R]U]@]"
+ "L]?]L]?]L]?]L]>^S^;]K]?^S^ N\\WmX]H\\WmX]H\\QaR]H\\Q^O]H\\O]P]C]1^S^ D]9]T\\ ?sFwDo?nC]L]?]L];"
+ "]T]7]T]=] Hj ?] 4]S]8d/]T]T]N^R_R\\ O](] =u Se =]0]J]7].^(]?]O]+]?^K]7]7]L]<gX] Sa ("
+ "aC]3\\R\\K\\R\\P]M]?]K]A]-]E]F].]/]D]F]H]C].].]V^6].]S]W]S]H]Q]T]D]C]Gg9]C]G]Q_,^7]6]H]@^S^:]U"
+ "]Q]U]G^X]2]0^5],]+] Pl>]K]A].]K]@p?]0]K]?]L]?].].a2].]M]M]N]L]@]J]@]K]A]K]?]-f8].]L]>^S^"
+ ":]U]V]U]?^4]S]4^4`0]$`<^Si O[O\\O\\H]N^=]M^@^S`<]J]=c7]L]<]S]8^U]:]L]@]O]O]J]S]S]@]P]>]S]S]@"
+ "]J]B]J]9]6]J]?]M]7]6]/^E^H]P]P]G]H]A]S]S]E]C]Iz<]<z=]=z<] B]1]R[7j:\\L\\6] A^Q] B[U\\C[Ni:]T]"
+ " V] O[R\\S]P[ T] ]L]6\\U\\<] Dh2]T]/]P\\W\\?]I\\A]P\\W\\K`2]M]>]M]>]M]>]M]>]M]>^O^=]O]?]-].].]"
+ ".].].].].]-]E]G]Q]T]D]C]H]C]H]C]H]C]H]C]<`=]S]M]G]H]C]H]C]H]C]H];]6]M^?]R`;l=l=l=l=l=l=~Q]"
+ ".pApApAp@].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J]?tG]S]T]@]L]?]L]?]L]?]L]=]S]:]K]>]S] M]P]P\\G]"
+ "C\\G]ScS\\G]S^N\\G]P]P\\B]0]S] D]7\\T[ >sFwCn?mB]L]?]L];]T]7]T]=] Hi >] 4]S]7[Xa1]T^T^O]"
+ "P_T] O](] =u Se =]0]J]7]/^'^A]N]+]?^K]7]8^L^<eW] Sd .dC]3\\R\\K\\R\\P]M]?]K]A]-]E]F].]/]D]F]H"
+ "]C].].]U^7].]ScS]H]Q^U]D]C]G]/]C]G]O^,^8]6]H]?]S]9]U]Q]U]H^W^3]1^4],]+] Q`P]>]K]A].]K]@p"
+ "?]0]K]?]L]?].].b3].]M]M]N]L]@]J]@]K]A]K]?]+e9].]L]=]S]9]V]T]V]@_4]S]5_4b2]&b<\\Nd M[O]P\\H]N"
+ "^=]L]@]Q_<]J]?e7]L];]T]8]T]:]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]?]M]8^6].]E]G]P]Q^G]H]A"
+ "^T]T^E]C]Iz<]<z=]=z<] B]1]R[3]1\\L\\6] A_R] B\\U\\E\\Ni:]T] V] O\\S\\R]R\\ T] ]L]6\\U\\<] Dh2]T]"
+ "/\\O[V\\?\\H\\A\\O[V\\L`1]M]>]M]>]M]>]M]>]M]>]M]>^O]?]-].].].].].].].]-]E]G]Q^U]D]C]H]C]H]C]H]C]"
+ "H]C]=b>]T]L]G]H]C]H]C]H]C]H];]6]M]>]Qa>`P]>`P]>`P]>`P]>`P]>`P]>`PoQ].pApApAp@].].].]/]J]@]"
+ "L]@]J]A]J]A]J]A]J]A]J]?tG]T]S]@]L]?]L]?]L]?]L]=]S]:]K]>]S] L\\P]P\\F\\C\\F\\T^W^T\\F\\T^M\\F\\C\\B]"
+ "0]S] E^7]U[ >sFwBl=kA]L]?]L]<^T^8^V^=] Ij >] <u=[U^1\\S]R]O]O_U\\ N](] 1] Ge =]0]J]7]"
+ "0_&]A]N]+]?^K]8^8]J]:aU\\ Pe 4eA]3\\R\\K\\R\\Qo@]J]A].]F^F].].]E]F]H]C].].]T^8].]RaR]H]P]U]C]E"
+ "]F].]E]F]N^,]8]6]H]?]S]9^V]Q]V^H^V^4]2_4],]+] Q]M]>]K]A].]K]@],]0]K]?]L]?].].c4].]M]M]N]"
+ "L]@]J]@]K]A]K]?](d;].]L]=]S]9^W]T]W^@`5^U^5^/_3]'_8ZJ` K[O]P\\H]N^=]L]@]P];]J]@_0]L];]U^9^T"
+ "^;]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]6]J]@^M^:^5].]E]F]Q]Q]F]H]@^U]U^C]E]G_\"]\"_BZT]TZB_F_;"
+ "] B]1]R[3]1\\L\\?o I_S] A[U]F[ V]T] W] N[S\\R]R[ S] ]L]6\\U\\ ']T]/\\O\\V\\@\\H\\A\\O\\V\\M_0o@o@o"
+ "@o@o?m>l>].].].].].].].].]-]F^G]P]U]C]E]F]E]F]E]F]E]F]E]=d?^V]L]F]H]C]H]C]H]C]H];]6]N^>]O`"
+ "?]M]>]M]>]M]>]M]>]M]>]M]>]M]?].].].].]-].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J] K]U]R]@]L]?]L]?"
+ "]L]?]L]=^U^:]K]>^U^ L\\P]Q]F\\D]F\\U^U^V]F\\U^M]F\\D]B\\/^U^ OuD]V[ =sFwBk;i@]L]?]L]<]R]7]V];]"
+ " F^ Nu=[T^3]S]R]O]N_V\\ N](] 1] ].]L]6]1_%]Aq0]>]K]8]7]J]/] Md:u:d>]3\\R\\K\\S\\Po@]"
+ "J]A].]F]E].].]E]F]H]C].].]S^9].]RaR]H]P^V]C]E]F].]E]F]M],]8]6]H]>]U^8]W^Q^W]H^U^4]2^3]+],]"
+ " R^M]>]K]A].]K]@],]0]K]?]L]?].].]X_5].]M]M]N]L]@]J]@]K]A]K]?]$`;].]L]=^U^8]W]T]W]@b5]U]5"
+ "^,]3]'] J\\Q_Q[G]N^=]L]A]O];]J]@].]L];]U]8]R];]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]5]L]?]K];"
+ "^4].^G^F]Q]Q]F]H]?_W]W_B]E]F_#]#_B\\U]U\\B_H_A\\U]U[ H]1]R[3]1]N]?o H`V] @[T]G[ U]T] X] N[S\\Q"
+ "]S[ S] ]L]6\\U\\ (]T]/]P\\U\\A]I]B]P\\U\\M^/o@o@o@o@o@o@m>].].].].].].].].]-]F]F]P^V]C]E]F]"
+ "E]F]E]F]E]F]E]>_X_?]W^L]F]H]C]H]C]H]C]H];]6]P_=]M^@^M]?^M]?^M]?^M]?^M]?^M]?^M]?].].].].]-]"
+ ".].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J] K]U\\Q]@]L]?]L]?]L]?]L]<]U]9]K]=]U] K]Q]Q]F]E]F]W^S^W]F"
+ "]W^L]F]E]B\\.]U] NuC\\V[ =eXZXdFgXhAi9h@]L]?]L]<]R]7]V];] E] Nu=[S]3\\R]R]O]M_X\\ M]("
+ "] 1] ].]L]6]2_$]Aq0]>]K]8]7]J]/] Ke=u=e<]3\\R\\K\\S\\Po@]J]A].]F]E].].]E]F]H]C].].]R^:].]Ra"
+ "R]H]O^W]C]E]F].]E]F]M^-]8]6]H]>]U]7]W]O]W]I^S^5]3^2]+],] R]L]>]K]A].]K]@],]0]K]?]L]?].]."
+ "]W_6].]M]M]N]L]@]J]@]K]A]K]?]\"_<].]L]<]U]7]W]T]W]Ac5^W^6^+^4](] H[R\\X]S\\G]N^=]L]A]O];]J]A"
+ "^.]L]:]W^9^R];]L]@]O]O]J]S]S]@]P]>]S]S]@]J]B]J]9]5]L]?]K];^4]-]G]D]R]R]E]H]>kA]E]E_$]$_B^V"
+ "]V^B_J_A^V]V] I]1]R[3]0\\N\\>o G`X] ?\\U_Q[T\\ T]T] ] N\\T\\Q]T\\ S] ]L]6\\U\\ )]T].\\P\\T\\A\\I]A"
+ "\\P\\T\\N^.o@o@o@o@o@o@m>].].].].].].].].]-]F]F]O^W]C]E]F]E]F]E]F]E]F]E]?_V_@]W]K]F]H]C]H]C]H"
+ "]C]H];]6k<]L^A]L]?]L]?]L]?]L]?]L]?]L]?]L]?].].].].]-].].].]/]J]@]L]@]J]A]J]A]J]A]J]A]J] K]"
+ "V\\P]@]L]?]L]?]L]?]L]<^W^9]K]=^W^ J]R]R]D]G]D]W\\Q\\W]D]W\\L]D]G]A\\.^V] NuC]W[ <cWZXdEfXh@g8"
+ "g?]L]?]L]=^R^8^X^:] F] G\\R\\5[S]4]R]R]O]Lb M](\\ 0] ].]L]6]3_#]Aq0]>]K]9]6]J]/] H"
+ "e@u@e H\\R]M]T]Q^J]A]J]@]/]G^E].]-]F]F]H]C].].]Q^;].]Q_Q]H]N]W]B]G]E]-]G^F]L]-]8]6]I^>^W^7]"
+ "W]O]W]I^R^6]4^1]+],] R]M^>^M^@]/^M^?]-]0^M^?]L]?].].]V_7].]M]M]N]L]@^L]?^M^A^M^?] ]<].]L"
+ "]<]U]7]X]R]X]B^W^5]W]6^)]4](] H\\T]W]U\\F]O_=]L]A]P^;^L^A]-]L]:]W]8]P]<]L]@]O]O]J^T]T]?]P]>"
+ "]S]S]@^L]A^L]8]5]L]@^J]=^3]-^I^D^S]S^E]H]<g>]G]C_%]%_A_W]W_A_L_@_W]W_ J]0]S[3]0]P]5]4],b ="
+ "[ThT[ R]T]!] M[T\\P]U[ R] ]L]6\\U\\ *]T].]P[S\\B]J]A]P[S\\N].^J]B^J]B^J]B^J]B^J]B^K^A]M]=]"
+ "/].].].].].].].]-]G^F]N]W]B]G]D]G]D]G]D]G]D]G]?_T_AbK]E]I^C]I^C]I^C]I^;]6j;]K]A]M^?]M^?]M^"
+ "?]M^?]M^?]M^?]M_?].].].].].].].].]/]J]@]L]@^L]@^L]@^L]@^L]@^L] J^X]Q]?]L]?]L]?]L]?]L];]W]8"
+ "^M^<]W] I]R]S]C]H]C]VZOZW]C]VZL]C]H]@\\-]W] MuC]X[ ;cWZWbDeWZXe>e6e>]L]?]L]=]P]8^X^:] "
+ " F^ H\\R\\5[S]5]Q]R]O^L` K]*] 0] !^.]L]6]4_\"]2],^>^M]8]6]J]0] DeCuCe E]R\\M]T\\P]I]A]J]@"
+ "]/]G]D].]-]F]F]H]C].].]P^<].]Q_Q]H]N^X]B]G]E]-]G]E]L^.]8]5]J]<]W]6^X]O]X^J^Q^6]5^0]+^-] "
+ "R]M^>^M]?].]M^?]-]/]M^?]L]?].].]U_8].]M]M]N]L]?]L]?^M]?]M^?] ]<].]M^<^W^6aRbB^V^6]W]7^(]4]"
+ "(] GcUcE]P_=]L]A]P]9]L]@]-]L]:^X]9^P]<]M^@]P^O]I]T]T]?]P]>]S]S]@^L]@]L]8]5]M]?]I]>^2],]I]"
+ "B_U]U_D]H]:c<]G]B_&]&_?_X]X_?_N_>_X]X_ I]0]S[3]0_T_5]4]+` ;[SfU[ P^U^#] L[U\\P]V[ Q] ]M^"
+ "6\\U\\ ,^U^-\\P\\S\\B\\J]@\\P\\S\\N].]I]B]I]B]I]B]I]B]I]B]I]B^M]=]/].].].].].].].]-]G]E]N^X]B]G]D"
+ "]G]D]G]D]G]D]G]@_R_A`J]D]J]A]J]A]J]A]J]:]6g8]K]A]M^?]M^?]M^?]M^?]M^?]M^?]M_?].].].].].].]."
+ "].].]L]?]L]?]L]?]L]?]L]?]L]?]L]3^;aP]?]M^?]M^?]M^?]M^;]W]8^M];]W] H]S]T^B]J^B]J^B]J^B]J^@"
+ "\\-]W] G^1_ :aW[V`BcW[Wc<d5c=]L]>]N]<]P]7]X]8] F]KZ X]S]5[S]5\\P]R]N]K_ K]*] 0] !]"
+ ",]N]5]5_\"]1],]<]M]9^6^L^0] Ad Nd A\\R]O^U\\P^I^B]K^?]H[C]H^D].],]G]F]H]C].].]O^=].]P^Q]H]M]"
+ "X]A]I]D],]I^E]K]AZH^8]5]J]<]W]5bObJ^O^7]6_0]*]-] R]M^>^M]?^/]M^?^.]/]M^?]L]?].].]T_9].]M"
+ "]M]N]L]?]L]?^M]?]M^?] ]<].]M^;]W]5aRaB^U^6c8_(]4](] FaSaD]P_=]M]@]P]9]L]@]-]L]9b9]O^=^N^?"
+ "\\P_Q]H]T]T]?]P]=]T]T]?^L]@]L]8]4]N]@^I^?]1],^K^A`W]W`C]H]7]8]I]@^&]&^=i=^N^<i H]0^T[3]1l6]"
+ "4])_ <\\RbT\\ O]T]#] L\\V\\O]X\\ M^N^6\\U\\ ,]T]-\\OhF\\J]@\\OhQ]/^I^D^I^D^I^D^I^D^I^C]I]B]L]<"
+ "]H[C].].].].].].].]-]H]D]M]X]A]I]B]I]B]I]B]I]B]I]@_P_B_J]C]J]A]J]A]J]A]J]:]6].]K]A]M^?]M^?"
+ "]M^?]M^?]M^?]M^?]M_?^/^/^/^/^/].].].].]L]?]L]?]L]?]L]?]L]?]L]?]L]3^;`O]?]M^?]M^?]M^?]M^;c8"
+ "^M];c G^U]U^@^M^@^M^@^M^@^M^?\\-c H^0_ 9^U[U^@aV[Va:b3a<]L]>^P^=^P]7]X]8_ H^M[ F] 6"
+ "]S]>ZQ[T^6]P]S^N^K^ K]*] 0]:] 8]0],]O^5]6_2ZI]1]-^<^O^9]4]L]0]<].] Uc Pc1]2\\Q^S`W^P]G]B]K]"
+ ">^J\\C]I^C].],^H]F]H]C].].]N^>].]C]H]MbA^K^D],^K^D]K^B[I]7]5^L^<c5aMaJ^N]7]6^/]*]-] R^O_>"
+ "_O]=].]O_>].].]O_?]L]?].].]S_:].]M]M]N]L]>]N]>_O]=]O_?] ]<]-]O_;]X^5aRaC^S^6a8_']4](] D]P"
+ "^B^Ra>^N]@]Q]7]N]?^.]L]9a8]N]=^N^?]Q_Q]G]U]U]>]P]=]T]T]?_N]>]N]7]4^P^@]G]@^1]+^M^?mB]H]7]8"
+ "^K^?\\%]%\\;g;\\L\\:g G]/]T[3]2n7]4]'^ <\\F\\ M\\S\\ J\\F\\ L^N^6\\U\\ ,\\S\\-]OhG]K]@]OhQ]LZ=]G]"
+ "D]G]D]G]D]G]D]G]D]G]D^L]<^J\\C].].].].].].].]-]J_D]MbA^K^B^K^B^K^B^K^B^K^A_N_B^K]B^L^A^L^A^"
+ "L^A^L^:]6].]K]A^O_?^O_?^O_?^O_?^O_?^O_?^Oa?].].].].]/].].].]-]N]>]L]>]N]=]N]=]N]=]N]=]N]2^"
+ ";_O]=]O_>]O_>]O_>]O_:a7_O]9a E^P_>^P_>^P_>^P_>^P_>\\,a H^.] /[5]T[S\\8a1`<]L]=^R^<]O^8b7_ "
+ " H^O\\ F] 6\\R\\=[R[U^5\\N]T]L^M` L]*] 0]:] 8]1^+]P]4]7_1[L_1]<ZL^:^Q^8]4^N^>ZM];].] R` P"
+ "`.]2]QfXaN]G]B]L^=^L]C]K_B].]+_J]F]H]C].].]M^?].]C]H]La@^M^C]+^M^C]J]B]L^7]4^N^:a4aMaK^M^8"
+ "]7^.]*^.] Q]P`>`Q^=^NZ;^Q`>_LZ>].^Q`?]L]?].].]Q^;].]M]M]N]L]>^P^>`Q^=^Q`?]/ZL];]-^Q`:a4`"
+ "P`D^Q^7a8^&]4](] S]Sb>_P^@]R^7^P^>^MZ<]L]9a9]M]=_P`XZB]Q_Q]G^V]V^>]P]=^U]U^?`P^>^P^6]4]Q"
+ "^?]G]A^0]*^O^<i@]H]7]7^M^=Z$]%Z8e9ZKZ7e F]/^U[TZ9]3^V`V^8]4]&^ <\\H\\ K[R[ I\\H\\ K_P`XZ9"
+ "\\U\\ ,[R[,\\E\\D\\K]?\\E\\M]O\\=]G]D]G]D]G]D]G]D]G]D]G]D]K];^L]C].].].].].].].]-]K_C]La@^M^@^M^"
+ "@^M^@^M^@^M^A_L_C`N^A^N^?^N^?^N^?^N^9]6].]L]?]P`>]P`>]P`>]P`>]P`>]P`>]P]X^LZN^NZ;_LZ>_LZ>_"
+ "LZ>_LZ?].].].]-^P^>]L]>^P^=^P^=^P^=^P^=^P^2^:^P^=^Q`>^Q`>^Q`>^Q`:a7`Q^9a Dk<k<k<k<k>],a "
+ "H]-] /[,[._0_;]L]=j<]N]7`5a J_S^ F] 6\\R\\=^U[W_5]N^V^K_Rd L],] /]:] 8]1])^T^3]8_0^Q`0"
+ "]<]Q_8^S^8^3_R_=]R^:].] O] P]+]1\\PdW`N^G^C]N_;`R`C]NaA].]*`O`F]H]C].].]L^@].]C]H]La?`S`B]*"
+ "`S`B]J]B`Q_6]3_R_9a4aMaL^K^9]8^-])].] Q_Tb>aS^;_R\\:^Sa=`Q]>]-^Sa?]L]?].].]P^<].]M]M]N]L]"
+ "=_T_=aS^;^Sa?]/^R_:]-^Sa:a3_P_C^P^7_8^%]4](] S_V^X^?aS^>]T^5_T_=`R]<]L]8_8]M^>`SdA]SaS]E"
+ "^W]W^=]P^=_W]W_>]X]T_<_T_5^4^T^?^G^C^/])^Q^8c=]H]7]6`S` ?] ;c >c E]._W[V\\9]4^J^9]4]%] ;]L]"
+ " IZQZ H]L] !u ,`Sd9\\U\\ ,ZQZ,]E\\E]L]?]E\\M_S^>^G^F^G^F^G^F^G^F^G^F^G^F^K]:`R`C].].].].]."
+ "].].]-]ObB]La?`S`>`S`>`S`>`S`>`S`?]J]CcS`?_R_=_R_=_R_=_R_8]6].]V[R^?_Tb>_Tb>_Tb>_Tb>_Tb>_T"
+ "b>_T^V_Q]M_R\\:`Q]=`Q]=`Q]=`Q]?].].].],_T_=]L]=_T_;_T_;_T_;_T_;_T_1^:`T_;^Sa=^Sa=^Sa=^Sa9_6"
+ "aS^7_ Bi:i:i:i:i=]+` I],] /[,[-].]:]L]<h;]N]7`3q \"h E] 7]S]=k5]LdIjW^ M],] /]:] 8]1"
+ "](f9k?n?l/]<j6g7]1j<h9].] LZ PZ(]1]O`U]K]E]Cm8kBn?n?](nE]H]C].].]K^Am>]C]H]K`>kA])kA]J^Cm5"
+ "]2j7_2`M`K^J]9]8tC])].] PgX]>]Xf9h9fX]<k>],fX]?]L]?].].]O^=].]M]M]N]L]<h<]Xf9fX]?]/j9d4g"
+ "X]:a3_P_D^O^7_8m4]4](] RfXaBk=^V^3h;j<]L]8_9^L]>qA^U]W]U^Di<]O`?k=]Xg:h3a7f>uCn?]/eSe;]:"
+ "]H]7]5k >] :a <a D]-h>n?\\H\\8]4]%] 9^R^ *^R^ Xu ,q9\\U\\ /]D\\F]LfH]D\\Li>]E]F]E]F]E]F]E"
+ "]F]E]F]E]F]JnIkBn?n?n?n?].].].]-n@]K`>k<k<k<k<k=[H[Co<j;j;j;j7]6].]Vf=gX]=gX]=gX]=gX]=gX]="
+ "gX]=gTjLh9k<k<k<k?].].].]+h<]L]<h9h9h9h9h Fk:gX]=gX]=gX]=gX]9_6]Xf6_ @e6e6e6e6e;]+_ G\\+["
+ " /].]-[,[9]L];e:^N^8`2p e D] 7]S]<i4\\JbGgT^ M\\,\\ .]:] 8]1]'d8k?n>i-]<i4e6]0h;g8].] "
+ " I]0]3]E]Cl6h@l=n?]&jC]H]C].].]J^Bm>]C]H]K`<g?]'g?]I]Bj3]1h6_2_K_L^I^:]8tC])].] OdV]>]Wd"
+ "6f8dW]:i>]+dW]?]L]?].].]N^>].]M]M]N]L];f;]Wd7dW]?]/i7c3dV]9_2_P_E^M^8_8m4]4](] QdV`B]Xe;"
+ "d1f8h<]L]8_9]K]>]XdW_@eWeBg;]O`=g;]Vd8f1`6d=uCn?]/eSe;]:]H]7]3g <] 9_ :_ C]+f>n>ZFZ7]4]%] "
+ "7f &f Vu ,]XdW_9\\U\\ /\\C\\F\\KfH\\C\\Kg=]E]F]E]F]E]F]E]F]E]F]E]F]JnHh@n?n?n?n?].].].]-l>"
+ "]K`<g8g8g8g8g J]Vh:h9h9h9h6]6].]Ve;dV]<dV]<dV]<dV]<dV]<dV]<eRiJf7i:i:i:i?].].].]*f;]L];f7f"
+ "7f7f7f F]Xe7dV]<dV]<dV]<dV]9_6]Wd5_ <\\-\\-\\-\\-\\6]+_ FZ*[ /].],Z+Z9]L]8`8]L]7^.m W` "
+ "A] 7\\R\\7b2]H^BaP_ O].] .]:\\ 7]2^%`6k?n:b*]9c/a5],b6b5].\\ H]/\\4]C]Di0b=h9n?]#c?]H]C].].]I"
+ "_Dm>]C]H]J_9a<]$d?]I^?c0].b3_2_K_M^G^;]8tC](]/] M`T]>]U`2b4`U]7c;])`U]?]L]?].].]M^?].]M]"
+ "M]N]L]8`8]U`3`U]?],c2a0_T]9_2^N^F^K^8]7m4]4](] O`R^B]Va8b-`3d:]L]7]9^J]?]V`T]>cUc?c9]N_:"
+ "a8]T`3`-_4`<wDn?]/eSe;]:]H]7]0a 9] 8] 8] B])b<n @]4]&^ 5b \"b Tu ,]V`T]8\\U\\ 0].].]0b"
+ ";]C]H]C]H]C]H]C]H]C]H^E^H^JnEb=n?n?n?n?].].].]-h:]J_9a2a2a2a2a G\\Rb4b3b3b3b3]6].]Vc7`T]:`T"
+ "]:`T]:`T]:`T]:`T]:aMcEb2c4c4c4c<].].].]'`8]L]8`1`1`1`1` D]Ua2_T]9_T]9_T]9_T]8]5]U`2] "
+ "=] &[ O].] E] E] '] S] R] ^ (](]/] "
+ " C] S] '] V] F^ 7]4](] %])[ 4]7] @])_Q_:] 9]6] 6[ S]0[R"
+ "^ H]%\\U\\ A\\ @\\ /Z <\\ ,[ M^5](^ "
+ " =] &[ N]0] D\\ D] '\\ Q^DZ 1] _ )"
+ "](]/] D^ S] '] V] F] 6]4](] %] ;]7] @] /] 9]6] 6[ S]0"
+ "g H]%\\U\\ @\\ @\\ J\\ X]4](] "
+ " <] &[ N]0] D\\ E^ '\\ P^G] 2] X^ )]"
+ "(^0] D] R] '] V] G^ 6]4](] %] ;]7] @] /] 9]6] 6[ S]0e"
+ " F]%\\U\\ ?[ ?[ I[ ^4])^ "
+ " @ZV] &[ M]2] D] E] '] O_K_ 3] V^ *b"
+ ",]5b E^ R] '] V] G^ 6^5])^ %] ;]7] @] /] 9]6] 6[ S].a"
+ " D]%\\U\\ ?\\ @\\ J\\ !^4])^ "
+ " B\\V] &[ M]2] D\\ G\\ L`P` 2] U^ +b "
+ "=b RZN^ R^ '] V] H^ 4^6]*^ $] ;]7] @] /] 9]6] 6[ S] "
+ " J] :\\ @\\ J\\ \"^3]*^ A\\V"
+ "\\ %[ L]4] Vm 2^ S^ ,b =b "
+ " R\\Q_ R] &] V] I^ 3b:].b $] ;]7] @] /] 9]6] 6[ S] "
+ " J] @ZU] FZU] PZU] #^2]+^ @b "
+ " %[ Si 4b %i Ua &]"
+ " V] Mb 2a:].a #] ;]7] @] /] 9]6] .] J] @b "
+ " Fb Pb 'b2] E` "
+ " Qb 1a $g S` %] V] Ma /_:]._ !] "
+ " ;]7] @] /] 9]6] .] J] @a Ea "
+ " Oa &a1] D^ "
+ " X^ Ip Fc Q^ #] V] M_ A] )] ;]7] @] /] 9]6] "
+ " T] @` D` N` %_/"
+ "] BZ Ap "
+ " 6] "
+ " "
+ " p 6] "
+ " "
+ " "
+ " F]']2] +]']2^ D]']3_ E]']1] \"]']2^ "
+ "8] H";
+
+ // Define a 90x103 font (huge size).
+ static const char *const _data_font90x103[] = {
+ // Defined as an array to avoid MS compiler limit about constant string (65Kb).
+ // First string:
+ " "
+ " "
+ " "
+ " "
+ " HX 4V >X IX *W FW "
+ " "
+ " "
+ " "
+ " "
+ " HX W 4Z 3VCT <Z >X W 4Z "
+ " HX W 4Z 'VCT ;X W 3Y 2UCT KX W 3Y 0W "
+ " "
+ " "
+ " "
+ " "
+ " @W !W 4\\ 5YET ?XHX 8] >W !W 4\\ 7XGX KW !W 4\\ 7XHX "
+ " +YET :W !W 3[ 5ZFT ?XGX EW !W 3[ 7XGX 5W "
+ " "
+ " "
+ " "
+ " "
+ " >W \"V 3\\ 7]HU ?XHX 9` ?W \"V 3\\ 7XGX JW \"V 3\\ 7XHX -]HU"
+ " 9W \"V 3] 7]HT ?XGX DW \"V 3] 8XGX 5V "
+ " "
+ " "
+ " "
+ " "
+ " <W $V 3VNV 8_KV ?XHX 9` >W $V 3VNV 8XGX IW $V 3VNV 8XHX -_KV"
+ " 8W $V 2] 7_KU ?XGX CW $V 2] 8XGX 6V "
+ " "
+ " "
+ " "
+ " "
+ " :W &W 4VLV :j >XHX :VJV >W &W 4VLV 9XGX HW &W 4VLV 9XHX .j 6"
+ "W &W 3VMV 9i >XGX BW &W 3VMV 9XGX 7W MW "
+ " "
+ " "
+ " "
+ " "
+ " CV 'W 4VJV ;j >XHX ;UGV >V 'W 4VJV :XGX GV 'W 4VJV :XHX .j"
+ " 5V 'W 3VKV :i >XGX AV 'W 3VKV :XGX 8W N[ "
+ " "
+ " "
+ " "
+ " "
+ " DV )W 4VHU <VK_ =XHX ;TEU =V )W 4VHU :XGX FV )W 4VHU :XHX "
+ " /VK_ 3V )W 3VIV <UK_ =XGX @V )W 3VIV ;XGX 9W N] "
+ " "
+ " "
+ " "
+ " "
+ " DV *V 3UFU =UH\\ <XHX <UDT <V *V 3UFU ;XGX EV *V 3U"
+ "FU ;XHX /UH\\ 1V *V 2UGU <TH] =XGX ?V *V 2UGU ;XGX 9V a "
+ " "
+ " "
+ " "
+ " "
+ " EV ,V 3UDU >TEY ;XHX <TBT <V ,V 3UDU <XGX D"
+ "V ,V 3UDU <XHX /TEY /V ,V 2UEU =TFZ <XGX >V ,V 2UEU <XGX :V Na "
+ " "
+ " "
+ " "
+ " "
+ " DU -V 3VDV ?TCV :XHX <TBT ;U -V 3VD"
+ "V =XGX CU -V 3VDV =XHX /TCV -U -V 2UCU >TCU :XGX =U -V 2UCU =XGX ;V NV"
+ "IV \"W "
+ " "
+ " "
+ " "
+ " JU /V 3VBV ETBT :U /"
+ "V 3VBV FU /V 3VBV (U /V 2UAU DU /V 2UAU @V NVGV "
+ " $X "
+ " "
+ " *X "
+ " "
+ " JX GTBT "
+ " MX GX 7V :UEU DX GX 7V JX GX 7W 4X GX 6V "
+ " GX GX 5V (X &X "
+ " "
+ " )X 8V "
+ " "
+ " ;X FTBT "
+ " LX IX 7X <UCU DX IX 7X JX IX 6W 3X IX 6X GX IX 5X "
+ " *X &Y "
+ " "
+ " (X 9V "
+ " <X "
+ " ETBT KX "
+ "KX 6X 1TBT BTAT CX KX 6Y JX KX 6Y (TBT BX KX 5X 1TBT LX KX 4X +X "
+ " %T #W 9W "
+ " "
+ "3a :a <W 2W >W E\\ AW ,W ,W ,W ,W HY GV +Y "
+ " 4Z NX @X "
+ " %W DUDU "
+ " =Y 7W KW 6Z 4XDT BTAT BW KW 6Z IW KW 6[ ,Y )XDT AW KW 5Z 4XDT "
+ " KW KW 4Z ,W BW 8V (S "
+ " <S 9V 7V "
+ " 3a :a ;W 3W >W H_ AW ,W ,W ,W ,W "
+ " L] GV +] ;a #[ F^ "
+ " 8XGX +W BTEU "
+ " *R 9a :W MW 6\\ 6ZET ?XHX <TAT AW MW 6\\ 7XGX LW MW "
+ "5[ 7XGX .Y +ZET @W MW 5\\ 6ZET ?XHX DW MW 4\\ 7XHX 0W AW &XHX MZ "
+ " +T $Y BS 1W,V MY 8W 7W T 9X 5Z /"
+ "[ 0Z 8Z /Y GY .\\ <\\ [ 4[ :\\ -a "
+ " :a :W 4W >W Ja AW ,W ,W ,W ,W N_ GV +_ "
+ "?e 8] J] Jb 8[ <[ $Y FY 7XGX "
+ "=Z Di 5W 8Z .Y !W FW *Y 4W)V*W)V-Y(V <UFU 3\\ "
+ " +[ 0[ 0[ 0[ 0[ 4[=T <e ;W W 5\\ 7\\FT ?XHX <TAT @W W 6^ 8XGX KW W"
+ " 5] 8XGX .Z@R ?\\FT ?W W 4\\ 7\\FT ?XHX CW W 3\\ 7XHX 1W @W &XHX N\\ "
+ " ,T :U :U5U ` EX 2VFV .S 4]0W\"b DV V 5V T 7W"
+ " .` 3[ 7c 8d )Z Dq 8b Hy Bb 7` Na /Z @k .d Kj ?x Mt 7f MX/X'X -X -X2Z&X -]0]0["
+ "3X Dc Ii -c Ij 4f N~W$X/X.X&X.X4Y4XDY/Y/Y,Y'~S%a >W $a MY EW 5W >W Kb AW ,W ,W"
+ " ,W ,W !a GV +a Ch =f ^ Mf 2Z @"
+ "x Mx <c 3X C~Q)X?X?X Kc 2T .V .T CX $a !W.W N` ;XGX ![ Lb &Z Mi 7[ >a"
+ " 5a &W 0g #\\ -_ <\\*V.\\*V0a-V\"X )Z /Z /Z /Z /Z 4WJV 1~U+d Kx Mx Mx Mx MX -X -X -X ,j"
+ " @[3X Dc 8c 8c 8c 8c <cBV.X/X'X/X'X/X'X/X/Y,Y$X &h ;W \"W 5VNV 8]HU ?XHX <TAT ?W \"W 5"
+ "VNV 8XGX JW \"W 5VMV 9XGX -ZDV @]HU >W \"W 4VNV 8]HU ?XHX BW \"W 3VNV 8XHX 2W ?W &XHX "
+ " ^ K~\\ >S 3Q +[ @[;[ ;Q ;e HX 2VFV #VBV FS 6`"
+ "1V#g GV !V 3V !T 7W 0d :` ;j ?k -[ Dq :g Ky Df ;d $f 1Z @o 5j Np Ex Mt "
+ ":m\"X/X'X -X -X3Z%X -]0]0\\4X Gi Lm 4i Ln ;m#~W$X/X-X(X-X4Y4XCY1Y-Y.Y&~S%a >W $a N[ EV "
+ "5W >W Lc AW ,W ,W ,W ,W \"b GV +a Dk Aj \"_"
+ " h 3Z @x Mx ?i 6X C~Q)X?X?X Ni 6V /V /V DX &f #W0W e >XGX %c#"
+ "e +b\"i 9_ Be 9d 'V 3k %^ /c @^*V0^*V2d.V\"X )Z /Z /Z /Z /Z 3b 1~U.j Nx Mx Mx"
+ " Mx MX -X -X -X ,p F\\4X Gi >i >i >i >i BiEV.X/X'X/X'X/X'X/X.Y.Y#X 'j ;V \"V 5VLV :_IT >XH"
+ "X <TAT >V \"V 5VLV 9XGX IV \"V 4VMV 9XGX ,ZHY A_IT <V \"V 4VLV :_IT >XHX AV \"V 3VLV 9"
+ "XHX 2V >W &XHX !_ K~[ >T 4R -_ D_?_ >S =t Fh "
+ " IX 2VFV #VBV FS 7c4V#i HV \"W 3V !T 7V 0f @e >o Co 0\\ Dq <j Ly Fj ?h (i "
+ "\\ ?Z @r :o\"s Hx Mt <q$X/X'X -X -X4Z$X -]0]0\\4X Im Np 9m Np ?q%~W$X/X-X(X,W5[6XAX1X+X.X%~S%"
+ "a =V $a ] EV 6W >W Md AW ,W ,W ,W ,W HW 1b GV +b "
+ " Fm Dm #` \"j 4Z @x Mx Am 8X C~Q)X?X?X!m 9X 0V 0X EX 'h"
+ " $W0W \"h ?XGX 'g%g 0h%i :a Cf :f *V 4m %^ 0e A^+V/^+V1f1V!X )Z /Z /Z /Z /"
+ "Z 2` 1~V0o\"x Mx Mx Mx MX -X -X -X ,t J\\4X Im Bm Bm Bm Bm FmHV-X/X'X/X'X/X'X/X-X.X\"X (l ;"
+ "V $V 4UJU :ULXLU >XHX <UCU =V $V 5VJV :XGX HV $V 4VKV :XGX +ZL\\ AULXLU ;V $V 3UJU :ULX"
+ "LU >XHX @V $V 2UJU 9XHX 3V =W &XHX !` K~Z >T 4S /a FaAa @T "
+ " @w Hl KX 2VFV $WCV ES 8e5V$j HV \"V 1V \"T 7V 2j Eh ?q Dp 1\\ Dq >"
+ "l Ly Hn Bj +l %e E\\ At >s$v Kx Mt >u&X/X'X -X -X5Z#X -^2^0]5X Jo q ;o r Br%~W$X/X"
+ "-X(X,X6[6XAY3Y+Y0Y%~S%W 3V IW !_ FW 7W >W Md AW ,W ,W ,W ,W HW "
+ " 2[ ?V #[ Hn En #` #l 6\\ Ax Mx Cp 9X C~Q)X?X?X\"o "
+ " ;Z 1V 1Z FX KS 0i #W2W LV ,i ?XGX *l'h 3l'i ;c Dg ;g ,W 6o %^ 1g B"
+ "^,V.^,V0g3V X *\\ 1\\ 1\\ 1\\ 1\\ 2^ 0~V2s$x Mx Mx Mx MX -X -X -X ,v L]5X Jo Do Do Do Do HpKW"
+ "-X/X'X/X'X/X'X/X-Y0Y\"X )n <W &W 5VJV ;TI_ >XHX ;UEU <W &W 5VIV ;XGX HW &W 5VIV ;XGX *g"
+ " ?TI_ ;W &W 4VJV ;TI_ >XHX @W &W 3VJV :XHX 4W =W &XHX 1\\ 1\\ 1\\ 1\\ 1\\ =XMV K~Y "
+ " =S 4U 1c IdCc AU Dz In LX 2VFV $VBV ES 9g7V$k HV #W 1W #T "
+ " 8W 3l Fh ?r Eq 3] Dq ?m Ly Ip Em -n )k H\\ Au Av%x Mx Mt ?x(X/X'X -X -X6Z\"X -"
+ "^2^0]5X Ls\"s ?s\"s Et%~W$X/X,X*X+X6[6X@Y5Y)Y2Y$~S%W 3W JW \"a FW 8W >W NZ 6W ,W "
+ ",W ,W ,W HW 2X <V X H[G[ Go KZ %"
+ "[H[ 7\\ Ax Mx Ds ;X C~Q)X?X?X$s >\\ 2V 2\\ GX KS 1j #W2W LV -j ?XGX +ZEZ)VGY "
+ "5ZDZ)i <e EUFY <UFX -W 7q %VMU 2YIY CVMU,V.VMU,V0UFX3V X *\\ 1\\ 1\\ 1\\ 1\\ 1\\ 0~W4v%"
+ "x Mx Mx Mx MX -X -X -X ,x N]5X Ls Hs Hs Hs Hs LsMW,X/X'X/X'X/X'X/X,Y2Y!X *\\G[ <W (W 4UHU"
+ " <UH] =XHX ;VGV ;W (W 5VHV ;XGX GW (W 4UGU ;XGX )c =UH] 9W (W 3UHU <UH] =XHX ?W (W"
+ " 2UHU :XHX 5W <W &XHX 5c 8c 8c 8c 8c @WKU J~X >T 5V 2e KfEe CW G| "
+ " Jp MX 2VFV $VBV ES 9XIX8V$l HV #V /V #T 8V 3n Gh ?s Fr 5^ Dq @n Lx Ir"
+ " Go .o -q L^ Bv Cx&z x Mt A{)X/X'X -X -X7Z!X -^2^0^6X Mu#t Au#t Gu%~W$X/X,X*X+X6["
+ "6X?X5X'X2X#~S%W 2V JW #c FW 9W >W NX 4W ,W ,W ,W ,W HW "
+ " 2W ;V NW IZCY Hp JY &ZDZ 9^ Bx Mx Eu <X C~Q)X?X?X%u @"
+ "^ 3V 3^ HX KS 2k \"W4W KV -ZGW ?XGX -X=X+R@W 8X<X .XIX FQ@W <Q@W /W 7dGU"
+ " %QHU 3XEX DQHU-V-QHU-V/Q@W5V NX +^ 3^ 3^ 3^ 3^ 2\\ 0~W5x&x Mx Mx Mx MX -X -X -X ,z!^6"
+ "X Mu Ju Ju Ju Ju N}+X/X'X/X'X/X'X/X+X2X X +ZBY ;W *W 4UFU =TF\\ =XHX :VIV 9W *W 5VFV "
+ "<XGX FW *W 4VGV <XGX (_ :TF\\ 8W *W 3UFU =TF\\ =XHX >W *W 2UFU ;XHX 6W ;W &XHX 7h =h"
+ " =h =h =h DWJV K~X >T 5W 4g MgFg EY J~ K]FZ MX 2VFV $VBV "
+ "ES :XGX9V%\\GX HV $W /W 3PATAP GV 3[H[ Gh ?]F] GZE^ 6^ Dq A]FX Lx I\\F\\ G\\G[ "
+ " /[H] 0u N^ Bw E_D^&{!x Mt B`C_)X/X'X -X -X8Z X -_4_0_7X N^E^$u C^E^$u H^E\\%~W$X/X,Y,Y*W7"
+ "]8X>Y7Y'Y4Y#~S%W 2V JW $e FV 9W >W NW 3W ,W ,W ,W ,W HW "
+ " 2W ;V NW IY@X >X 4[AV IX &X@X 9^ Bx Mx F^E^ =X C~Q)X?X?X"
+ "&^E^ B` 4V 4` IX KS 3\\GW \"W4W KV .YBT ?XGX .V7V,P=W :W8W /VEV 3V +V /V "
+ " 7eGU KU 3WCW ;U-V$U-V LV5V NX +^ 3^ 3^ 3^ 3^ 3^ 1~W6_D^&x Mx Mx Mx MX -X -X -X ,{\""
+ "_7X N^E^ L^E^ L^E^ L^E^ L^E^ !^Ed*X/X'X/X'X/X'X/X+Y4Y X +Y?X ;V *V 4UDU >TEZ <XHX 9a "
+ "7V *V 4UDV =XGX EV *V 4VEV =XGX )] 7TEZ 6V *V 3UDU >TEZ <XHX =V *V 2UDU <XHX 6V :W &XH"
+ "X 9k @k @k @k @k EWJV K~W >T 5Y 5g MhHi G[ M~Q L\\AW M"
+ "X 2VFV $VCV DS :WEW:V%ZAU HV $V -V 3RCTCR HW 4ZDZ H\\LX ?Y?[ HV>\\ 8_ DX )[?T -Y J[B"
+ "[ I[CZ 0WAZ 2x ^ BX>^ G]=Z&X=b#X -X '];[)X/X'X -X -X:[ NX -_4_0_7X \\?\\%X@^ E\\?\\%X"
+ "?] J[=X =X <X/X+X,X)X8]8X=Y9Y%Y6Y )Y$W 2W KW %ZMZ FV :W >W X 3W 4W ,W "
+ " HW 3X ;V NX KY?X Ca 9Y:R HX (X>X :VNV "
+ "BZ /X '\\?\\ A^ FX0X)X?X?X'\\?\\ Db 5V 5b JX KS 3ZBT !W6W JV .X?R 4V4U HV ;V"
+ "4V 1VCV 4V *U 0V 7fGU KU 4WAW <U.V#U.V JU6V MX +^ 3^ 3^ 3^ 3^ 3^ 2XIX F]=Z&X -X"
+ " -X -X -X -X -X -X ,X=b$_7X \\?\\ N\\?\\ N\\?\\ N\\?\\ N\\?\\ #\\?`)X/X'X/X'X/X'X/X*Y6Y NX ,Y=W :V ,"
+ "V 3UDU >TDX ;a 6V ,V 4UBU GV ,V 3UCU 0` 6TDX 4V ,V 2UDU >TDX >V ,V 1UDU "
+ ":V 9W (o Do Do Do Do GWIU J~V >T 6Z 6i jIj I\\ N~R M[="
+ "U MX 2VFV %VBV H] AWCW;V%Y=R HV %W -V 4UETEU IV 4ZBZ IWGX ?V;[ IS9Z 9VNX DX *Z;R"
+ " -X JZ>Y JZ?Y 1U>Z 5`C_#` CX;[ H[7W&X9_$X -X (\\6X)X/X'X -X -X;[ MX -_4_0`8X![;[&X"
+ "=[ F[;[&X<[ LZ8U =X <X/X+X,X)X8]8X<X9X#X6X )Z$W 1V KW &ZKZ FV ;W >W W 2W 4"
+ "W ,W HW 3W :V MW KX=W Cc ;X7P HX ("
+ "W<W ;WNW BY /X ([;[ Gg JX0X)X?X?X([;[ Fd 6V 6d KX KS 4Y>R !X8X JV /X<P 6V1U IV"
+ " <U0U 2UAU 3U *U 1V 6fGU KU 4V?V <U/V\"U/V IU7V LX ,` 5` 5` 5` 5` 5` 3XIX "
+ "G[7W&X -X -X -X -X -X -X -X ,X9_%`8X![;[![;[![;[![;[![;[ %[;](X/X'X/X'X/X'X/X)X6X MX ,X;W"
+ " :V .V 3UBU ?TBT 7] 3V .V 4VAU GV .V 3UAU 4d 7TBT 1V .V 2UBU ?TBT ;V .V 1U"
+ "BU <V 8W )r Gr Gr Gr Gr IVHR GX+W =S 5[ 7i!kJk I] !^ "
+ " )Y:T MX 2VFV %VBV Le EVAV<V$X:P HV %W -W 6WFTFV IV 4X?Y IRBX ?T7Y IP5Z :VNX DX "
+ "+Z8P .Y JY<Y KY=X 1S;Y 6];\\$WNW CX9Z J[4U&X6]%X -X )[2V)X/X'X -X -X<[ LX -XNV6VNX"
+ "0`8X\"Z7Z'X;Z HZ7Z'X;Z LY4R =X <X/X*X.X(X8]8X<Y;Y#Y8Y *Z#W 1V KW 'ZIZ FV <W >W W "
+ " 2W 4W ,W HW 3W :V MW KW<X Dd <W -W "
+ " )W;X <WNW AY 0X )Z7Z Jl MX0X)X?X?X)Z7Z Hf 7V 7f LX KS 4X;P W8W IV /W \""
+ "V.U JV >U.U 4VAV &V 5U *U 2V 6gGU KU 5W?W =U/V\"U/V IU7V LX ,WNW 5WNW 5WNW 5"
+ "WNW 5WNW 5WNW 4XHX H[4U&X -X -X -X -X -X -X -X ,X6]&`8X\"Z7Z#Z7Z#Z7Z#Z7Z#Z7Z 'Z8['X/X'X/X'"
+ "X/X'X/X)Y8Y MX ,W:W 9V 0V 3U@U ?[ 1V 0V 3U@V GV 0V 3U?U 8h 1V 0V 2U@U "
+ " CV 0V 1U@U >V 7W *`L` I`L` I`L` I`L` I`L` JV =X,X >T 6] 9k\"lKl K_ "
+ " #\\ 'Y8S MX 2VFV %VBV Nk IVAV=V$X 1V %V +V 6YHTHY -V EW 5Y>Y :X ?R5"
+ "Z .Y ;VMX DX +Y DX IY<Y LY;X 2Q8Y 8[5[&WNW CX8Y KZ1T&X4\\&X -X *Z.T)X/X'X -X -X=["
+ " KX -XNV6VNX0a9X#Z5Z(X:Y IZ5Z(X:Z NY1P =X <X/X*X.X'W9WNV:X:Y=Y!Y:Y *Z\"W 1W LW (ZGZ -"
+ "W >W W 2W 4W ,W HW 3W :V MW KW;W De =W "
+ " -X *W:W <VLV @Y 1X *Z5Z Mp X0X)X?X?X*Z5Z Jh 8V 8h MX KS 5Y :X:X"
+ " IV /W #U+T JV ?U+T 5U?U &V 5U +V AgGU KU 5V=V =U0V!U0V IV8V KX ,WNW 5W"
+ "NW 5WNW 5WNW 5WNW 5WNW 4XHX IZ1T&X -X -X -X -X -X -X -X ,X4\\'a9X#Z5Z%Z5Z%Z5Z%Z5Z%Z5Z )Z5Z"
+ "(X/X'X/X'X/X'X/X(Y:Y LX -X:W !W 2\\LZ "
+ "EW +[@[ K[@[ K[@[ K[@[ K[@[ KV <X-X /P 0T 7^ 9k\"lLm La %Z "
+ " %Z6Q MX 2VFV %VCV n KWAW>V$X 1V &W +W 5XITIX +V EV 4X<X :X ?P2Y -X <WMX DX ,Y C"
+ "X JY:Y MX9W 2P7Y :Z0Z(WLW DX7X KY.R&X2Z&X -X *Y+R)X/X'X -X -X>[ JX -XNW8WNX0a9X#Y"
+ "3Y(X9Y JY3Y(X9Y NX LX <X/X*X.X'X:VMV:X9X=X NX:X *Z!W 0V LW )ZEZ .W >W W 2W "
+ " 4W ,W HW 3W :V MW LX;W Df >W ,W "
+ " +W8W >WLW @Y 2X +Z3Z!t\"X0X)X?X?X*Y3Y Kj 9V 9j AS 5X 8W:W HV /W #T)T KV "
+ " @T(T 6U?U &V 5T +V AhGU KU 5V=V =U0V!U0V JV7V WLW 7WLW 7WLW 7WLW 7WLW 7XNX "
+ "6XGX IY.R&X -X -X -X -X -X -X -X ,X2Z'a9X#Y3Y%Y3Y%Y3Y%Y3Y%Y3Y )Y3Z)X/X'X/X'X/X'X/X'X:X Ki"
+ " >W8V *XHZ FW ,Z<Z MZ<Z MZ<Z "
+ "MZ<Z MZ<Z LV <X.X .R 2S 7` :k#nMm Mb &Z $Y4P MX 2VFV &VBV!o "
+ "KV?V?V#W 0V &V )V 3XKTKX )V EV 5X:X ;X X -Y =VLX DX -Y CY JY:Y NY9X HX ;"
+ "Z-Y)WLW DX7Y MY,Q&X1Z'X -X +Y)Q)X/X'X -X -X?[ IX -XMV8VMX0XNX:X$Y1Y)X9Y KY1Y)X8X NX LX <X"
+ "/X)X0X&X:VMV:X9Y?Y NY<Y *Y W 0V LW *ZCZ /W >W W 2W 4W ,W HW"
+ " 3W :V MW LW:W Dg ?W ,X ,W8W >WLW ?Y 3X +Y1Y\"v#X"
+ "0X)X?X?X+Y1Y MYNVNY :V :YNVNY BS 5X 8X<X HV /W $T?ZBT*c AT&T 7U?U &V "
+ "6U -W @hGU KU 6V;V >U1V U1V KW7V NWLW 7WLW 7WLW 7WLW 7WLW 7WLW 6XGX JY,Q&X -X "
+ "-X -X -X -X -X -X ,X1Z(XNX:X$Y1Y'Y1Y'Y1Y'Y1Y'Y1Y P)P$Y3[)X/X'X/X'X/X'X/X'Y<Y Km BW8W "
+ " +UDZ 7P 1W -Y8Y Y8Y Y8Y Y8Y Y8Y MV ;"
+ "W.X /T 4T 7a ;k#nMn Nc 6P :W4W ?Z ?X6X KY #Y 0X 2VFV &VBV\"p KV?V?V#"
+ "W 0V 'W )W 2XMTMX 'V FW 5X:X ;X Y -X >VKX DX -X BX IX8X NX7W KP 1P =X <Y)X+"
+ "XLX EX6X NY*P&X0Z(X -X ,Y'P)X/X'X -X -X@Z GX -XMV8VMX0XNX:X%Y/Y*X8X LY/Y*X8Y!X KX <X/X)X0"
+ "X&X:VMV:X8YAY LY>Y *Z W 0W MW +ZAZ 0W >W W 2W 4W ,W HW "
+ " 3W :V MW LW:W DSF[ @X -X -X8W ?WJW ?Y 4X ,Y/Y%z%X0X)"
+ "X?X?X,Y/Y YMVMY ;V ;YMVMY CS 5X 5P*Q JW<W GV /W %TBbET/g BTGb?T 8U?U &V"
+ " 7U 5_ ?hGU KU 6V;V >U2V NU2V$_7V NXLX 9XLX 9XLX 9XLX 9XLX 8WLW 6XGX KY*P&X -X"
+ " -X -X -X -X -X -X ,X0Z)XNX:X%Y/Y)Y/Y)Y/Y)Y/Y)Y/Y\"R+R&Y3]*X/X'X/X'X/X'X/X&Y>Y Jp EW:Y "
+ " +R@Y 7Q 2W .XEVFY\"X5Y\"X5Y\"X5Y\"X5Y N"
+ "V ;X/X 0V 5T 8c <k#nNo e >^ AW4W ?Z >W6W KY \"Y 0X 2VFV &VCW#[LSKZ K"
+ "V?V@V\"W 0V 'W )W 1XNTNX &V FW 6Y:Y <X NX -X ?WKX DX .Y CY IX8X NX7W NS 1S @"
+ "X =X&X,WJW EX6X NY /X/Y(X -X ,Y /X/X'X -X -XAZ FX -XMW:WMX0XMX;X%Y/Y*X8Y MY/Y*X8Y!X KX <X"
+ "/X)Y1X%W;WMW;W6XAX JX>X *Z NW 0W MW ,Z?Z 1W >W W 2W 4W ,W H"
+ "W 3W :V MW LW:W DPAY ?Y .W -W6W @WJW >Y 5X ,X-X&"
+ "_MXM_&X0X)X?X?X,Y/Y !YLVLY <V <YLVLY DS 6Y 6R,R JX>W FV /X 'TCfFT2i CUGfB"
+ "T 9U?U &V 7U 5] >iGU KU 6V;V >U2V NU2V$]5V NWJW 9WJW 9WJW 9WJW 9WJW 9WJW 8XFX"
+ " KY /X -X -X -X -X -X -X -X ,X/Y)XMX;X%Y/Y)Y/Y)Y/Y)Y/Y)Y/Y#T-T'Y3]*X/X'X/X'X/X'X/X%X>X Ir "
+ "GW=\\ GY 9S 3W /XDVDX$X2X$X2X$X"
+ "2X$X2X V ;X0X 0X 7T 8d <k#~`!g Bd DW4W ?[ ?X7W LY !X /X 2VFV &VCV#Z"
+ "JSGV KV?VAV!W 0V 'V 'V /d $V FV 5X8X <X NX -X ?VJX DX .X BX HX8X Y7X #V 1V C"
+ "X >X$X-WJW EX6X Y .X.Y)X -X -Y .X/X'X -X -XBZ EX -XLV:VLX0XMX;X&Y-Y+X7X NY-Y+X7X!X KX <X/"
+ "X(X2X$X<VKV<X6YCY JY@Y +Z MW /V MW -Y;Y \"Z ;WDX 0Z 2XDW >Z <W !X :WDY IW ,W HX8X "
+ "MY 3Z *X 3X &X 7] <W 3W :V MW ;X :W:W 4Y @[ )\\ (Y 6X 8QEV :[ "
+ " JW6W @VIW =Y 6X -Y-Y(]JXJ]'X0X)X?X?X-Y-Y #YKVKY =V =YKVKY IZ 9X 6T.T JW>W FV "
+ ".X (TDgFT3j CTFhDT 9U?U &V 8U 4\\ =iGU KU 6V;V >U3V MU3V#\\5V MWJW 9WJW"
+ " 9WJW 9WJW 9WJW 9WJW 8XFX LY .X -X -X -X -X -X -X -X ,X.Y*XMX;X&Y-Y+Y-Y+Y-Y+Y-Y+Y-Y%V/V)Y3"
+ "_+X/X'X/X'X/X'X/X%Y@Y Is HW?^ ?Z /Z /Z /Z /Z /Z /Z6Y NZ 0Z /Z /Z /Z 8Y 1Y 3Z /Z /Z"
+ " /Z /Z 3ZCV 5WDX DXCVCW%X0W%X0W%X0W%X0W V :X1X 0X 7T 9f =k#~`\"h Cf "
+ "EW4W @\\ ?X8X LX !Y /X 2VFV 'VBV#XHSET KV?VAV!W 0V (W 'W .` \"V GW 5X"
+ "8X <X NX -X @VIX DX .X BX HX8X X5W &Y 1Y FX >W\"W.XJX FX6X X -X.Y)X -X -X -X/X'X -X"
+ " -XCZ DX -XLV:VLX0XLX<X&X+X+X7X NX+X+X7X!X KX <X/X(X2X$X<VKV<X5YEY HYBY +Z LW /W NW .Y9Y"
+ " 'b ?WG^ 7b 9^GW A` Gl 2_GW MWG_ DW ,W ,W8Y MW ,WG^>^4WG_ 9` @WG^ 9^GW MWG\\ ;f Gm <W6W#"
+ "X2X#W;X;W5Y7Y#W1X\"u 6W :V MW >^BV\"W:W 3X ?^ 0e AWG_ KV.X ?X <W6W HTG[ K}!WCWCW Ca"
+ " 7p&{ NW6W AWHW >Z 7X -X+X)\\HXH\\(X0X)X?X?X-X+X $YJVJY >V >YJVJY Ma =X 7V0V JW@W E"
+ "V .Y *TEiET5k DTEiDT :VAV &V 9U 3_ ;W6W NiGU KU 6V;V >U3V MU3V#_8V NXJX"
+ " ;XJX ;XJX ;XJX ;XJX ;XJX :XEX LX -X -X -X -X -X -X -X -X ,X.Y*XLX<X&X+X+X+X+X+X+X+X+X+X&X"
+ "1X*X3`+X/X'X/X'X/X'X/X$YBY Ht IW@_ Cb 7b 7b 7b 7b 7b 7b>a'b 7` 5` 5` 5` AW ,W ,W ,W DY EW"
+ "G_ 9` 5` 5` 5` 5` (Z <`GV W6W MW6W MW6W MW6W#W1X NWG^ HW1X NWBVBW&W.W&WJP:PJW&W4PJW&W."
+ "W!V :X2X 0X 6S 8g >k#~`#j Fj GW4W @\\ >W8W LX X .X 2VFV 'VBV$XGSCR "
+ "KV?VBV X 1V (W 'W ,\\ V GW 5X8X <X NX -X AWIX DX /X BY HX8X X5W ([ 1[ HX ?W "
+ "W/WHW FX6X!Y -X-Y*X -X .Y -X/X'X -X -XDZ CX -XLW<WLX0XKW<X'Y+X+X7X Y+X+X7X!X KX <X/X'X4X#"
+ "X<VKV<X4XFY FXBX *Y KW /W NW /Y7Y +g AWIb ;f =bIW De Il 3bIW MWIc FW ,W ,W9Y LW ,WIbBb"
+ "6WIc >f CWIb =bIW MWI^ =j Im <W6W\"W2W\"W<Z<W4X7X!W2W!u 6W :V MW @bEW\"W:W 2X @c 8j CW"
+ "Ic MX0W =W <W6W IW/W\"VI^ L}!WCWCW Ee =t&{ W4W BWHW =Y 7X .X*Y*ZFXFZ(X0X)X?X?X.Y+X #WIVIW "
+ " =V =WIVIW f ?X 8X2X KW@W EV .Z +SE[GVDS6ZDV DSDVDXDS 9UAU %V :U 2` <W6W"
+ " NiGU KU 6V;V >U4V LU4V\"`:V GX /WHW ;WHW ;WHW ;WHW ;WHW ;WHW :XEX MY -X -X -X -X -X "
+ "-X -X -X ,X-Y+XKW<X'Y+X,Y+X,Y+X,Y+X,Y+X'Z3Z,Y4WNY,X/X'X/X'X/X'X/X#XBX Gu JWB\\ Ag <g <g <g "
+ "<g <g <gBe+f <e :e :e :e CW ,W ,W ,W Mc FWIc >f ;f ;f ;f ;f +Z >eJU NW6W MW6W MW6W MW6W\"W"
+ "2W MWIb IW2W NWAVAW(W,W(WJR<RJW(W4RJW(W,W\"V 9W2X 1X 6T 9i ?k#~`#k Hl HW4W @] ?X9"
+ "W LW NX .X 2VFV 'VCW$WFSAP KV?VBV NW 1V (V &W *X MV GV 5X6X =X N"
+ "X -X AVHX DX /X BX GX8X X5X ,^ 1^ LX ?W MW0WHW FX6X!X ,X-Y*X -X .X ,X/X'X -X -XEZ B"
+ "X -XKV<VKX0XKX=X'Y+Y,X7X Y+Y,X7X!X KX <X/X'X4X\"W=WKV<W3YGY FYDY +Z KW .V NW 0Y5Y /l C"
+ "WJe ?j AeJW Eh Kl 5eJW MWJe GW ,W ,W:Y KW ,WJdDd7WJe @h DWJe AeJW MWJ_ ?l Im <W6W\"W2W!W=Z="
+ "W2X9X W2W!u 6W :V MW BeFV!W;X 1W ?f =k CWJe NY2X =X =W6W JW-W$WI` N}!WCWCW Gi Av&{ "
+ "W4W BVGW <Y 8X .X)X+ZEXEZ)X0X)X?X?X.Y+Y #UHVHU <V <UHVHU !j AX 9Z4Z KWBW DV -Z -"
+ "TFY@RDT8XAV ETDVBWET :VCV %V ;V )X =W6W NiGU KU 6V;V >U5V KU5V GX<V FX /WHW"
+ " ;WHW ;WHW ;WHW ;WHW ;WHW :WDX MX ,X -X -X -X -X -X -X -X ,X-Y+XKX=X'Y+Y-Y+Y-Y+Y-Y+Y-Y+Y'Z"
+ "5Z+Y5WMY,X/X'X/X'X/X'X/X#YDY GX@^ KWCZ Al Al Al Al Al Al AlFh.j ?h =h =h =h EW ,W ,W ,W !g"
+ " GWJe @h =h =h =h =h ,Z @hLV NW6W MW6W MW6W MW6W\"W2W MWJe KW2W W@VAW)W+W)WJT>TKW)W4TKW"
+ ")W+W\"V 9X3X 2X 5T :k ?i\"~`$m Jn IW4W A^ ?X:X MW NY .X 2VFV 7~X2XFS"
+ " <V?VCV MX 2V )W %W +X MV GV 5X6X =X NX -X BVGX DX /X BX GX8X X5X LX -X 7a 1a "
+ "X @W KW2XHX GX6X!X ,X,X*X -X .X ,X/X'X -X -XFZ AX -XKV<VKX0XJW=X'X)X,X7X X)X,X7X!X KX <X/"
+ "X'X4X\"X>VIV>X2YIY DYFY +Z JW .V NW 1Y3Y 1n DWLh Bm ChLW Gk Ll 6hLW MWKg HW ,W ,W;Y JW "
+ ",WKfGg8WKg Cl FWLh ChLW MWK` @m Im <W6W\"X4X!W=Z=W1X;X NW3X!u 6W :V MW CgGV!W;W 0X ?"
+ "g Am CWKg [4X >Y =W6W JW-W&YJb }!WCWCW Hk Dx&{ W4W CWFW <Y 9X /Y)X,ZDXDZ*X0X)X?X?X.X)X P #"
+ "SGVGS %P 7V 9P0P CSGVGS !l BX 8ZGWFZ JWCX DV ,Z .SEW<PCS8V?V .P>P JSCVAVDS :WEV "
+ "$V <V &W >W6W NiGU KU 6V;V BP>P /U5V KU5V EW=V FX 0XHX =XHX =XHX =XHX =XHX =XHX <XDX"
+ " MX ,X -X -X -X -X -X -X -X ,X,X+XJW=X'X)X-X)X-X)X-X)X-X)X&Z7Z*X5WKX,X/X'X/X'X/X'X/X\"YFY F"
+ "X=[ KWDY @n Cn Cn Cn Cn Cn CnHj1m Bk @k @k @k FW ,W ,W ,W $j GWKg Cl Al Al Al Al .Z Bs MW6"
+ "W MW6W MW6W MW6W\"W3X MWLh LW3X V?V@W*V)W*VJV@VKW*V4VKW*V)W#V 9X4X 2X 4S :l ?i\"~`"
+ "%o Lp JW4W A^ >W:X MW NX -X 2VFV 7~X2WES <V?VDV LX 2V )W %W -\\ V "
+ "HW 5X6X =X NX .X BWGX DX 0X BY FX:X NX5X LX -X :d 1d $Y @V IV2WFW GX6X\"Y ,X,Y+X -X /Y "
+ ",X/X'X -X -XH[ @X -XKW>WKX0XJX>X(Y)X,X7X!Y)X,X7X!Y LX <X/X&X6X!X>VIV>X1YKY BXFX +Z IW .W "
+ " W 2Y1Y 2o EWMj Dn DjMW Hn Nl 7jMW MWLi IW ,W ,W<Y IW ,WLhIi9WLi En GWMj EjMW MWLa An I"
+ "m <W6W!W4W W=Z=W1Y=Y MW4W u 6W :V MW DiIV W;W /W =g Cm CWLi![4W =Y =W6W KW+W(ZKd!}!"
+ "WCWCW Im Fy&{ W4W CWFW ;Y :X /X'X-YCXCY*X0X)X?X?X/Y)X!R #QFVFQ $R 9V :R1R DQFVFQ \"n BX "
+ "7ZJ\\JZ HWDW CV +[ 1TFW.T:W?V /Q?Q KTCVAWET :XIX $V =V #U >W6W NiGU KU 6V;V BQ"
+ "?Q 0U6V JU6V BU>V EX 0WFW =WFW =WFW =WFW =WFW =WFW <XDX NY ,X -X -X -X -X -X -X -X ,X,Y,XJ"
+ "X>X(Y)X.Y)X.Y)X.Y)X.Y)X%Z9Z*Y6WJX,X/X'X/X'X/X'X/X!XFX EX;Z LWDX ?o Do Do Do Do Do DoKn4n C"
+ "n Cn Cn Cn HW ,W ,W ,W %l HWLi En Cn Cn Cn Cn /Z Cs LW6W MW6W MW6W MW6W!W4W LWMj LW4W "
+ "W?V?V+W(V+WKXBXKV+W5XKV+W(V$W 8W4X 2X 5T ;n ?g!~_%p LZDZ JW4W A^ >W:W MW "
+ " MX -X 2VFV 7~X2WES <WAWDV KX 3V )W %W /` \"V HV 4X6X =X Y .X BVFX DX 0X BX E"
+ "X:X NX5X LX -X <e /e 'Y @V GV4XFX HX7X!X +X+X+X -X /X +X/X'X -X -XI[ ?X -XJV>VJX0XIW>X(X"
+ "'X-X7X!X'X-X7X!Y LX <X/X&X6X!X>VIV>X1YKY AXHX +Z HW -V W 3Y/Y 3p FWMk Fo EkMW Io Nl 8"
+ "kMW MWMk JW ,W ,W=Y HW ,WMjJj:WMk Gp HWMk GkMW MWMb Bo Im <W6W!W4W W>\\>W0X=X LW5X u 6W :V "
+ " MW EkJV W<X /W >j Fn CWMk\"\\6X =Z >W6W KW+W)[Ke\"}!WCWCW Jo Hz&{ W4W DWDW ;Y ;X /X'X."
+ "YBXBY+X0X)X?X?X/X'X#T HV IT :V ;T3T :V CV +o BX 6ZM`MZ GXFX CV *\\ 3SFW,S:V>V 0R@R "
+ " KSBV@VDS 9e #V ?W \"V ?W6W NiGU KU 6V;V BR@R 1U6V JU6V BV?V EX 1XFX ?XFX ?XFX ?XFX"
+ " ?XFX ?XFW =XCX NX +X -X -X -X -X -X -X -X ,X+X,XIW>X(X'X/X'X/X'X/X'X/X'X%Z;Z)X5VHX-X/X'X/"
+ "X'X/X'X/X XHX DX:Y LWEX >p Ep Ep Ep Ep Ep EpMp6o Do Do Do Do HW ,W ,W ,W 'o IWMk Gp Ep Ep "
+ "Ep Ep 0Z Ds KW6W MW6W MW6W MW6W!W5X LWMk MW5X V>V?W,V'W,VKZDYKW,V5YKW,V'W%W 8X5W 2"
+ "X 4T ;o @g ~^%q NY@Y KW4W B` ?X<X MV LX -X 2VFV 7~X2WES ;VAVDV JY 4V )"
+ "V $W 1d $V HV 4X6X =X X .Y CWFX DXLY =XEX 'Y EY<X MX5X LX -X ?e )e +Y ?V:X6V4WDW "
+ "HX7X!X +X+X+X -X /X +X/X'X -X -XJ[ >X -XJW@WJX0XIX?X(X'X-X7X!X'X-X8Y Y MX <X/X%W6X W?WIV>"
+ "W/YMY @YJY +Y GW -V W 4X+X 4YE\\ FWNXG\\ H]EX F\\GXNW J\\F[ GW ,\\GXNW MWNXG[ JW ,W ,W?Z GW"
+ " ,WNXH[KXH[:WNXG[ H]H] IWNXG\\ I\\GXNW MWNXFQ C\\CW CW ,W6W!X6X NW?\\?W.X?X JW6W 1X 6W :V MW "
+ " 9X=X\"[IZKW W=Y /W @m H]DV CWNXG[\"\\6W =[ >W6W LW)W*ZJWKY\"}!WCWCW K\\H] J{&{ V3W DWDW :Y "
+ "<X /X'X.XAXAX+X0X)X?X?X/X'X$V IV JV ;V <V5V ;V CV ,^MSKW BX 5x EWFW BV ,_ 5TFW,S:V?W"
+ " 1SAS LTBV@VDS 9d \"V @W U ?W6W NiGU KU 5V=V ASAS 2U7V IU7V @U@V DX 1WDW ?WDW ?"
+ "WDW ?WDW ?WDW ?XFX >XCX NX +X -X -X -X -X -X -X -X ,X+X,XIX?X(X'X/X'X/X'X/X'X/X'X$Z=Z(X6WH"
+ "X-X/X'X/X'X/X'X/X YJY DX9Y MWEW =YE\\ EYE\\ EYE\\ EYE\\ EYE\\ EYE\\ EYE]N\\G[7]EX E\\F[ F\\F[ F\\F[ "
+ "F\\F[ IW ,W ,W ,W (p IWNXG[ H]H] G]H] G]H] G]H] G]H] 1Z E]H^ JW6W MW6W MW6W MW6W W6W KWNXG\\"
+ " MW6W NV>V>V,V&V,VJZFYIV,V6YIV,V&V%W 7W6X 3X LR:T ;q @e N~^&s!Y>Y LW4W B` >W<X N"
+ "W $x FX 2VFV 7~X2WES ;VAVEW IY 5V *W #W 4XNTNX &V IW 5X5X =X X .X "
+ "CWEX Di AXH_ +X CX<X MX5X LX -X Be #e /Z @V<^IUDV5WDW HX8Y!X +X+X+X -X /X +X/X'X -X -XK["
+ " =X -XIV@VIX0XHW?X(X'X-X7X!X'X-X8X NZ NX <X/X%X8X NX@VGV@X.c >XJX +Z GW -W !W 5X)X 5U>"
+ "Z G_CZ I[>T FZC_ KZAZ HW -ZB_ M^BZ KW ,W ,W@Z FW ,^CZMVCZ;^BZ IZBZ I_CZ IZC_ M^ 5Y<S CW ,W"
+ "6W W6W MW?\\?W.YAY JW6W 2Y 6W :V MW ;\\A\\%YDYLV NW>Y .W AXJa IZ<Q C^BZ MX8X =\\ ?W6W LW)"
+ "W+YIXJY LW=W JWCWCW LZBZ K]F] ;W >W2W EWDW 9Y =X /X'X/YAXAY,X0X)X?X?X/X'X%X JV KX <V =X7"
+ "X <V CV -\\JSHT BX 4v DXHX BV -b 7SEV*S;V?W 2TBT LSAV@VCS 9b !V AV MU ?W6W MhGU"
+ " KU 5V=V ATBT 3U8V HU8V ?UAV CX 1WDW ?WDW ?WDW ?WDW ?WDW ?WDW ?XBX NX +X -X -X -X -X -"
+ "X -X -X ,X+X,XHW?X(X'X/X'X/X'X/X'X/X'X#Z?Z'X7WGX-X/X'X/X'X/X'X/X NXJX CX9Y MWFW <U>Z FU>Z "
+ "FU>Z FU>Z FU>Z FU>Z FU>eBZ9[>T FZAZ HZAZ HZAZ HZAZ JW ,W ,W ,W )r J^BZ IZBZ GZBZ GZBZ GZBZ"
+ " GZBZ 1Z EZB[ JW6W MW6W MW6W MW6W W6W K_CZ MW6W V=V>V-V%V-VHZHYHV-V6YHV-V%V%W 7X7X "
+ " 4X NU:T <s Ae N~^'u\"X<X LW4W BWNW >W<W MW $w EX 2~X2WES ;WCWEV GY "
+ "9W #W 5XMTMX 'V IV 4X4X >X !Y 0Y BVDX Dk CXJc -X BX>X LX5Y MX -X Ee Le 3Z ?U=bKUC"
+ "U6XDX IX9Y X +X+X+X -X /X +X/X'X -X -XL[ <X -XIV@VIX0XHX@X(X'X-X8Y!X'X-X8X N[ X <X/X%X8X "
+ "NX@VGV@X.c =XLX +Z FW ,V !W AR9Y H]?Y KZ:R GY?] LY=Y IW -Y?] M]@Y KW ,W ,WAY DW ,]@X"
+ "NV@X;]@Y JY>Y J]?Y KY?] M] 4X8P CW ,W6W X8X MW?\\?W-XAX IW7X 3Y 5W :V MW =_C_(YBXLV NW"
+ "?Z -W CXC\\ KY ,]@Y LW8X >] ?W6W LW)W,YHWHY MW=W JWCWCW MY>Y L[B[ ;W >W2W FWBW 9Y >X 0X%X0X"
+ "@X@X,X0X)X?X?X/X'X&Y JV KY =V >Y7Y =V CV .[HSFR BX 3t BWHW AV .WN\\ 9SFV)S;V?W 3UCU "
+ " LSAV@VCS 7_ V BV LU ?W6W MhGU KU 5W?W AUCU 4U8V HU8V ?UAV CX 2XDX AXDX AXDX AX"
+ "DX AXDX AXDX @XBX NX +X -X -X -X -X -X -X -X ,X+X,XHX@X(X'X/X'X/X'X/X'X/X'X\"ZAZ&X8WFX-X/X'"
+ "X/X'X/X'X/X MXLX BX8X MWFW <R9Y GR9Y GR9Y GR9Y GR9Y GR9Y GR9a>Y;Z:R GY=Y JY=Y JY=Y JY=Y KW"
+ " ,W ,W ,W *]E[ J]@Y JY>Y IY>Y IY>Y IY>Y IY>Y 2Z FY>Y JW6W MW6W MW6W MW6W W7X K]?Y NW7X "
+ " V=V=U-V$U-VGZJYFU-V7YFU-V$U%W 7X8X &~X/X:T =t @c L~\\'v\"W:W LW4W CXNX ?X>X MV "
+ " $x EX 2~X2WES :VDWEV FZ :W #W 7XKTKX )V IV 4X4X >X !X 0Y BWDX Dm FXKf "
+ "/Y AYBY KX5Y MX -X Gd ~X d 5Y ?V>dLUCU6WBW IX;Z Y +X+Y,X -X 0Y +X/X'X -X -XM[ ;X -XIWBWIX"
+ "0XGW@X)Y'Y.X8X!Y'Y.X9Y M] #X <X/X$X:X MX@VGV@X-a <YNY ,Z EW ,V !W AP6X H\\=Y LY7P HY="
+ "\\ LX;X IW .Y=\\ M[=X KW ,W ,WBY CW ,[=]=W;[=X KY<Y K\\=Y MY=\\ M\\ 4X *W ,W6W NW8X MW@VNV@W,XC"
+ "X GW8W 3Y 4W :V MW >aEa)X@XNW NWA[ ,W DW?[ LX +[=X KW:X =] ?W6W MW'W-XGWGX MW=W JWCWC"
+ "W MX<Y NZ>Z <W >W2W FWBW 9Z ?X 0X%X0X@X@X,X0X(X@X@X/Y'Y(Y IV JY >V ?Y5Y >V CV .YFSDP B"
+ "X 2q @XJX AV /WK[ :SFV)S;V@X 4VDV LSAV@VCS 6\\ MV CV KU ?W6W MhGU KU 4V?V @V"
+ "DV 5U9V GU9V >UBV BX 2WBW AWBW AWBW AWBW AWBW AXDX @XBX Y +X -X -X -X -X -X -X -X ,X+Y-XGW"
+ "@X)Y'Y1Y'Y1Y'Y1Y'Y1Y'Y\"ZCZ&Y9WEY.X/X'X/X'X/X'X/X MYNY BX8Y NWFW <P6X GP6X GP6X GP6X GP6X G"
+ "P6X GP6_<X;Y7P GX;X JX;X JX;X JX;X KW ,W ,W ,W *Z?Y K[=X KY<Y KY<Y KY<Y KY<Y KY<Y 3Z GY<Y "
+ "KW6W MW6W MW6W MW6W NW8W J\\=Y NW8W NV=V=V.V$V.VFZLYEV.V8YEV.V$V&W 6W8X &~X2\\<T =v"
+ " Ab K~\\(x$W8W MW4W CXNX ?X>X NW $w DX $VBV#XFS :WFXEV H] ;W #W 9XITIX"
+ " +V JW 4X4X >X \"Y 3[ BWCX Dn GXLi 1X ?ZFZ JY7Z MX -X Je M~X Me 9Y >U?gMUCV7WBW IX>\\"
+ " NX *X*X,X -X 0X *X/X'X -X -XNZ 9X -XHVBVHX0XGXAX)X%X.X9Y!X%X.X:Y La 'X <X/X$X:X LWAWGV@W+"
+ "_ :XNX ,Z DW ,W \"W &W H[;X MY .X;[ MX9X JW .X;[ M[<X LW ,W ,WCY BW ,Z<\\<X<[<X LX:X K"
+ "[;X MX;[ M[ 3W )W ,W6W NW8W KWAVNVAW*XEX FW9X 4Y 3W :V MW ?cGc+Y?WNV MWD] +W DV=Z LX "
+ "+Z;X LW:X >_ @W6W MW'W.YGWFX NW=W JWCWCW NX:X NY<Y <W >W2W FWBW 8Z @X 0X%X0X@X@X,X0X(X@X@X"
+ "/X%X)Y HV IY ?V @Y3Y ?V CV /YES 6X 1\\H[ JcJc LV 0WI\\ =TFV)S;WAX 5WEW MTAVAWCS 3"
+ "W 4~W.W KV ?W6W LgGU KU 4WAW @WEW 6U9V GU9V ?VBV BX 2WBW AWBW AWBW AWBW AWBW AWBW A"
+ "XAX X *X -X -X -X -X -X -X -X ,X*X-XGXAX)X%X1X%X1X%X1X%X1X%X!ZEZ%X9WCX.X/X'X/X'X/X'X/X LXN"
+ "X AX7X NWFW !W ,W ,W ,W ,W ,W ,]:X=Y .X9X LX9X LX9X LX9X LW ,W ,W ,W +Z=X K[<X LX:X KX:X K"
+ "X:X KX:X KX:X 3Z GX<Z KW6W MW6W MW6W MW6W NW9X J[;X NW9X NU<V=V.U#V.UDZNYDV.U8YDV.U#V&"
+ "V 5X9W %~X3]<T >x A` J~\\(y%W8W MW4W CXMW >W>W MV $x DX $VCV\"XFS 9X"
+ "IXEV H_ <W #W ;YHTHY -V JV 3X4X >X #Y ?g AVBX Do HXMk 3Y >l HX7Z MX -X Me J~X Je "
+ "=Y >V?hNUBU8XBX Ju MX *X*X,w Lq IX *~R'X -X -c 8X -XHVBVHX0XFWAX)X%X.X9Y!X%X.X;Z Ke ,X <X/"
+ "X$X:X LXBVEVBX+_ 9` +Y CW +V \"W %W IZ9X NX .X9Z MW7W JW /X9Z MZ;X LW ,W ,WDY AW ,Z;["
+ ";W<Z;X MY:Y LZ9X X9Z MZ 2W )W ,W6W NX:X KWAVNVAW*YGY EW:W 4Z 3W :V MW ?XMYIe,X>WNV MW"
+ "Ib +W EW;Y MW *Z;X KV:W =_ @W6W NW%W/XFWFX NW=W JWCWCW NW8X!Y:Y =W >| GW@W 8Y @X 0X%X1Y@X@"
+ "Y-X0X(X@X@X/XImIX*Y GV HY @V AY1Y @V CV /XDS 6X 0YDY JdLd LV 1WF[ >SFV'S<WBY 6XFX "
+ " MS@VAVAS @~W/W JU >W6W LgGU KU 3WCW ?XFX 7U:V FU:V >UBV AX 3XBX CXBX CXBX CXBX"
+ " CXBX CXBX BXAw?X *w Lw Lw Lw LX -X -X -X ,X*X-XFWAX)X%X1X%X1X%X1X%X1X%X ZGZ$X:WBX.X/X'X/X"
+ "'X/X'X/X K` @X7X NWFW W ,W ,W ,W ,W ,W ,[8W=X -W7W LW7W LW7W LW7W LW ,W ,W ,W ,Y:X LZ;X M"
+ "Y:Y MY:Y MY:Y MY:Y MY:Y \"Y=\\ LW6W MW6W MW6W MW6W MW:W IZ9X NW:W NV<V=V/V#V/VCcCV/V9YC"
+ "V/V=X>V&V 4W:X %~X2TNV<S =y KWM^LW$~Z({&W7V MW4W CWLX ?X?W MV KX ,X"
+ " %VBV!XGS 9gFV Ha >W \"W ;WFTFW -V JV 3X4X >X #Y ?f AWBX Dp IXNm 4X <j GX7Z MX -X"
+ " !e G~X Ge AY =U?ZH^BU8W@W Jt LX *X*X,w Lq IX *~R'X -X -b 7X -XHWDWHX0XFXBX)X%X.X:Y X%X.X<"
+ "Z Ih 0X <X/X#X<X KXBVEVBX*] 8` ,Z CW +V \"W %W IZ9X X -X9Z NX7X KW /X9Z MY9W LW ,W ,W"
+ "EY @W ,Y:Z:W<Y9W MX8X LZ9X X9Z MY 1W )W ,W6W MW:W JWAVNVAW)XGX DW:W 4Y 3X :V MW @VHXK"
+ "WGV,W<^ MWIa *W FW9Y NW *Y9W KW<X >` @W6W NW%W/WEWEW NW=W JWCWCW X8X!X8X =W >| GW@W 7Y AX "
+ "0X%X1X?X?X-X0X(X@X@X/XImIX+Y FV GY AV BY/Y AV DX 1XCS 6X 0W@X KdLd LV 1VCZ ?SFV'S;WE"
+ "[ 7XFX G~X .S@VBWAS @~W0W .P>W >W6W KfGU KU 3XEX >XFX 8U;V:W3U;VCZ9P>WCV:W/Y 3W@"
+ "W CW@W CW@W CW@W CW@W CXBX CX@w?X *w Lw Lw Lw LX -X -X -X 5p9X-XFXBX)X%X1X%X1X%X1X%X1X%X N"
+ "ZIZ#X:VAX.X/X'X/X'X/X'X/X K` @X7X NWFW W ,W ,W ,W ,W ,W ,[8X?X -X7X NX7X NX7X NX7X MW ,W "
+ ",W ,W ,X9X LY9W MX8X MX8X MX8X MX8X MX8X \"X=] LW6W MW6W MW6W MW6W MW:W IZ9X NW:W NVLu"
+ "KU/VLuKU/VBaAU/V:YAU/V=X=U&V 4X;X %~X2RLW>T >{!z'~Z)}(W6W NW4W DXLX ?X@X MV "
+ " KX ,X %VBV!YHS 8eEV Ic ?W !W ;UETEU ,V KW 3X4X >X $Y >c ?WAX DWD^ JbG] "
+ "5X 9d DY9[ MX -X #d D~X Dd DY <U@YD\\BU9X@X Kq IX *X*X,w Lq IX *~R'X -X -a 6X -XGVDVGX0XEWB"
+ "X)X%X.X;Z X%X.X?\\ Gk 4X <X/X#X<X KXBVEVBX)[ 6^ ,Z BW +W #W %W IY7W X -W7Y NW5W KW 0X"
+ "7Y MY9W LW ,W ,WFY ?W ,Y:Z:W<Y9W MW6W LY7W W7Y MY 1W )W ,W6W MW:W JWBVLVBW(XIX CW;X 5Y 2X "
+ ":V MX BUDVKVDU.X<] LWI_ :WEW FV7X NW *Y9W JV<X >a AW6W NW%W0XEWEX W=W JWCWCW W6W!X8X "
+ "=W >| HX@X 7Y BX 0X%X1X?X?X-X0X(X@X@X/XImIX,Y EV FY BV CY-Y BV DX 1XCS 6X 1W>W KeNe LV"
+ " 1VB[ ASFV'S;YI] 9YGY F~X .S@VDX@S @~W1V ,TEZ >W6W JeGU IX +U 2YIY <YGY :U;V:W3U"
+ ";VGa<TEZCV:W/X 3X@X EX@X EX@X EX@X EX@X EX@X DX@w?X *w Lw Lw Lw LX -X -X -X 5p9X-XEWBX)X%X"
+ "1X%X1X%X1X%X1X%X MZKZ\"X;WAX.X/X'X/X'X/X'X/X J^ ?X7X NWFX !W ,W ,W ,W ,W ,W ,Z6W?X -W5W NW5"
+ "W NW5W NW5W MW ,W ,W ,W -X7W LY9W MW6W MW6W MW6W MW6W MW6W \"W=^ LW6W MW6W MW6W MW6W MW;X "
+ "IY7W NW;X NVLuKU/VLuKU/VA_@U/V;Y@U/V=X=U&V 4X<X $~X,W>T ?|\"}(~X)~(W6W NW4W DXKW >"
+ "W@X MV KX ,X %VBV!ZIS 7cEV IYNZ8W 0W !W :RCTCR +V KW 3X4X >X %Y"
+ " =b >V@X DS=\\ K`C[ 6Y 8b BX9[ Nd A~X Ad HY <VAX@ZBV:X?W Kq IX *X*X,w Lq IX *~R'X -X -a"
+ " 6X -XGVDVGX0XEXCX)X%X.X=[ NX%X.u Fl 6X <X/X\"W<W IWCWEVBW([ 5\\ ,Z AW +W #W $V IY7X\"X"
+ " -X7Y NW5W KW 0X7Y MX8X MW ,W ,WHZ >W ,X8X8W=X8X X6X MY7X\"X7Y MX 0W )W ,W6W MX<X IWCVLVCW&"
+ "XKX AW<W 5Y 1W 9V LW 4P /TBVMVBT.X;\\ LWI` =\\HW GW7X NW *X8X KV=X >XMW AW6W NW%W0XEWDW W"
+ "=W JWCWCW!X6X#X6X >W >| HW>W 6Y CX 0X%X1X?X?X-X0X'XAXAX.XImIX-Y DV EY CV DY+Y CV DX 2X"
+ "BS 6X 1V<V KeNe LV 2V?Y ASFV'S:dNV :XFY E~X .S@i?S @~W2i >h =W6W JeGU IX 4g :g :"
+ "YFX DgEV:X<gEVHe>hCV:X/X 3X?W EX?W EX?W EX?W EX?W EX@X EX?w?X *w Lw Lw Lw LX -X -X -X 5p9X"
+ "-XEXCX)X%X1X%X1X%X1X%X1X%X LZMZ!X<W@X.X/X'X/X'X/X'X/X I\\ >X7X NWFY !V +V +V +V +V +V +Y6W@"
+ "X ,W5W NW5W NW5W NW5W MW ,W ,W ,W -X7X MX8X X6X X6X X6X X6X X6X $X=_ MW6W MW6W MW6W MW6W "
+ "LW<W HY7X NW<W MVLuKU/VLuKU/V@]?U/V<Y?U/V=X=U&V 3W<X $~X+V>S >}%~R)~V(~P)W6W NW4W"
+ " DWJX ?XAW L~^ $X ,X %VCV N\\LS 6aDVAW0XLZ9W 0W !W :PATAP +V KV 2X"
+ "4X >X &Z =e BW@X DP8[ L^?Z 7X :h EY;\\ \"d >~X ?e LY ;U@W>YAU:W>W Ks KX *X*X,w Lq IX6f+~R"
+ "'X -X -b 7X -XGWFWGX0XDWCX)X%X.X@^ NX%X.s Bl 8X <X/X\"X>X IXDVCVDX)[ 4\\ -Z @W *V #W $"
+ "W JX5W\"X -W5X W4W KW 0W5X MX7W MW ,W ,WIZ =W ,X8X8W=X7W W4W MX5W\"W5X MX 0X *W ,W6W LW<W HW"
+ "CVLVCW&YMY AW=X 6Y 1X 9V LX 1X.Q /TA]AU/W:\\ LWIb A`JW GV5X NW +X7W KW>X >XMX BW6W W#W1WD"
+ "WDW W=W JWCWCW!W4W#X6X >W >| HW>W 7Y BX 0X%X1X?X?X-X0X'XAXAX.XImIX.Y CV DY DV EY)Y DV "
+ "DX 2XBS 6X 2W<W =^ =V 2V>Y BSFV'S9bMV ;XFY D~X .S@h>S @~W2i >g <W6W HcGU IX 4g 9"
+ "e 8YFX EgEV;Y<gEVHf?gBV;Y0Y 3W>W EW>W EW>W EW>W EW>W EW>W EX?w?X *w Lw Lw Lw LX -X -X -X 5"
+ "p9X-XDWCX)X%X1X%X1X%X1X%X1X%X Ke X=W?X.X/X'X/X'X/X'X/X I\\ >X7X NWEY \"W ,W ,W ,W ,W ,W ,X5W"
+ "@X -W4W W4W W4W W4W MW ,W ,W ,W -W6X MX7W W4W W4W W4W W4W W4W $W=VMW MW6W MW6W MW6W MW6W "
+ "LW=X HX5W NW=X MVLuKU/VLuKU/V?[>U/V=Y>U/V=X=U&V 3X=W 7X FW@T ?~&~T*~V)~R*W5V NW4"
+ "W EXJX ?XBX L~^ $X ,X &VBV Mb 4]CVC]4XJZ:W 0W !W +T KV KV 2X4X >"
+ "X 'Z <g EW?X +Z L]=Z 9Y <l GZ=] %e e!Y :UAW<XAU;X>X Lu MX *X*X,w Lq IX6f+~R'X -X -c "
+ "8X -XFVFVFX0XDXDX)X%X.u MX%X.r ?l :X <X/X\"X>X IXDVCVDX)\\ 4Z ,Y ?W *V #W $W JX5W\"W ,W"
+ "5X W3W LW 0W5X MX7W MW ,W ,WJY ;W ,X8X8W=X7W W4W MX5W\"W5X MX 0X *W ,W6W LW<W HWCVKUCW%XMX "
+ "?W>W 6Y 0X 9V LX 5`3R 0T?[?T/W:[ KWId DbKW HW5X NW +X7W JV>W =WLX BW6W W#W1WDWDW W=W JWC"
+ "WCW!W4W#W4W >W >| IX>X 9Y AX 0X%X1X?X?X-X0X'XAXAX.XImIX/Y BV CY EV FY'Y EV DX 2WAS ?r "
+ "CV:V =^ =V 2V=Y CSFV'S8`LV <XFX B~X .S@e;S @~W2i >e :W6W GbGU IX 4g 8c 5XFX FgFV"
+ ":Y<gFVGg@eAV:Y1Y 3X>X GX>X GX>X GX>X GX>X GX>X FX?w?X *w Lw Lw Lw LX -X -X -X 5p9X-XDXDX)X"
+ "%X1X%X1X%X1X%X1X%X Jc NX>W>X.X/X'X/X'X/X'X/X HZ =X7X NWEZ #W ,W ,W ,W ,W ,W ,X4WAW ,W3W!W3"
+ "W!W3W!W3W NW ,W ,W ,W .X5W MX7W W4W W4W W4W W4W W4W $W>VLW MW6W MW6W MW6W MW6W KW>W GX5W "
+ "MW>W LVLuKU/VLuKU/V>Z>U/V>Y=U/V=X=U&V 2W>X 8Y FW@T ?~P(~V*~T(~Q)V4V NW4W EXJX >W"
+ "BX L~^ $X ,X &VBV Ld 4WAVD`6XHZ;W 0W !W +T KV LW 2X4X >X 'Y ;i G"
+ "V>X *Z M\\;Y 9X =p HZ?^ 'd Id$Y 9UAW<XAU;W<W Lw X *X*X,w Lq IX6f+~R'X -X -d 9X -XFVFV"
+ "FX0XCWDX)X%X.t LX%X.p ;k ;X <X/X!X@X HXDVCVDX*^ 4X ,Z ?W *W $W $W JX5W\"W ,W5X W3W LW"
+ " 0W5X MW6W MW ,W ,WKY :W ,W7W7W=W6W W4W MX5W\"W5X MX /Y ,W ,W6W LX>X GWEVJVEW#a >W>W 7Y 1Y "
+ "8V KY 9e8T 0T?Z>T0X:[ KWIf GdLW HW4W MW ,W6W JV?X >XKW BW6W W#W2XDWDX!W=W JWCWCW!W4W#W4W"
+ " >W >| IW<W :Y @X 0X%X1X?X?X-X0X&XBXBX-XImIX0Y AV BY FV GY%Y FV DX 2WAS ?r DW:W =\\ <V "
+ "2V;W CSFV'S7]JV =XFX A~X .S@d:S (V Ii <a 8W6W FaGU IX 4g 6_ 2XFX GgGV:Z<gGVFUFY?"
+ "a@V:Z2Y 2W<W GW<W GW<W GW<W GW<W GX>X GX>w?X *w Lw Lw Lw LX -X -X -X 5p9X-XCWDX)X%X1X%X1X%",
+
+ // Second string:
+ "X1X%X1X%X Ia MX?W=X.X/X'X/X'X/X'X/X GX <X7X NWDZ $W ,W ,W ,W ,W ,W ,X4WAW ,W3W!W3W!W3W!W3W"
+ " NW ,W ,W ,W .W4W MW6W W4W W4W W4W W4W W4W $W?VKW MW6W MW6W MW6W MW6W KW>W GX5W MW>W "
+ "LVLuKU/VLuKU/V?\\?U/V?Y<U/V=X=U&V 2W>X 8X DWBT ?~Q)~W)~R&~(V4V NW4W EWHW >WBW K~^ "
+ " $X ,X &VBV Kg \"VEc8WFZ=W /W !W +T 4~W 5V 1X4X >X (Y -] IW>X )Y M[9X 9"
+ "X >\\F\\ H[C` 'a Ca$Y 9UAV:WAU;W<W LX<\\!X *X*X,X -X 0X6f+X/X'X -X -XN[ :X -XEVHVEX0XCX"
+ "EX)X%X.s KX%X.o 6h <X <X/X!X@X GWDVCVDW*_ 4X -Z >W )V $W 6i JX5X$X -X5X V2W LW 1W3W "
+ "MW6W MW ,W ,WLY 9W ,W7W7W=W6W!X4X NX5X$X5X MW .[ .W ,W6W KW>W FWEVJVEW#a >W?X 8Z 4\\ 8V K["
+ " =i<V 0S=Y=S0X:[ KW@^ IfMW HW4W MY .W6W JW@W =XKX CW6W W#W2WCWCW!W=W JWCWCW\"X4X%X4X ?W >W"
+ "2W IW<W :Y @X 0X%X1X?X?X-X0X&XBXBX-X%X1~` GV H~` GV H~` GV DX 3XAS ?r DV8V =\\ <V 2V;X "
+ "DSFV'S4W /XFX @~X .S@VIX;S (V Ii 8Z 5W6W D_GU IX 4g 3Y .XFX HgGV;TNU<gGVFQ@W;Z=V;T"
+ "NU3Y 1W<W GW<W GW<W GW<W GW<W GW<W GX>X X *X -X -X -X -X -X -X -X ,X*X-XCXEX)X%X1X%X1X%X1X"
+ "%X1X%X H_ LX@W<X.X/X'X/X'X/X'X/X GX <X7X NWD\\ 8i >i >i >i >i >i >i3WBX ,V2W!V2W!V2W!V2W NW"
+ " ,W ,W ,W .W4W MW6W!X4X\"X4X\"X4X\"X4X\"X4X M~Y2X@VIW NW6W MW6W MW6W MW6W KW?X GX5X NW?X L"
+ "VLuKU/VLuKU/V@^@U/V@Y;U/V=X=U&V 2X?W 8X CWBT ?~R*~X)~Q%}(V4W W4W FXHX ?XDX K~^ "
+ " $X ,X 'WCV Ii &VEe:XEZ>W /W !W +T 4~W 5V 1X4X >X )Y )[ KW=X (Y N[9Y ;Y "
+ "?Z@Z I]Gb '^ =^$X 9U@V:WAU<X<X MX9Z\"X *X*X,X -X 0X6f+X/X'X -X -XM[ ;X -XEVHVEX0XBWEX"
+ ")X%X.r JX%X.q 4e =X <X/X!X@X GXFVAVFX*` 5X .Z =W )V $W :m JW3W$W ,W3W!W2W LW 1W3W MW"
+ "6W MW ,W ,WMY 8W ,W7W7W=W6W!W2W NW3W$W3W MW -^ 2W ,W6W KX@X FWEVJVEW\"_ <W@W 7Y :b 7V Jb F"
+ "mAX 0S<W<S0W8Y JW<[ KYHVMV GV3X MZ 0W6W IVAX >XIW CW6W!W!W3WCWCW!W=W JWCWCW\"W2W%W3X ?W >W"
+ "2W JW;X <Y ?X 0X&Y1X?X?X-X0X&YCXCY-X%X2~a GV H~a HV I~b HV DX 3W@S ?r DV8V <Z ;V 2W;W "
+ "DSFV'S <XFX =V .S@VGW<S (V \"W6W A\\GU IX 2XFX *V;TMU LV2V V;TMU4Z 2X<X IX<"
+ "X IX<X IX<X IX<X IX<X IX=X X *X -X -X -X -X -X -X -X ,X*X-XBWEX)X%X1X%X1X%X1X%X1X%X G] KX@"
+ "V;X.X/X'X/X'X/X'X/X GX <X8Y NWC\\ =m Bm Bm Bm Bm Bm Bm3WBW ,W2W\"W2W\"W2W\"W2W NW ,W ,W ,W /X4"
+ "X NW6W!W2W\"W2W\"W2W\"W2W\"W2W M~Y2W@VHW NW6W MW6W MW6W MW6W JW@W FW3W MW@W KVLuKU/VLuKU/V"
+ "A`AU/VAY:U/V=X=U&V 1W@X 9X BWBS >~R+~Z*~P#{'V4W W4W FXHX ?XDX K~^ $X "
+ " ,X 'VBV Gi (VFg;WCZ?W /W !W +T 4~W 6W 1X4X >X *Y &Z LW=X (Y NZ7X ;X ?Z>Z ImNX "
+ "'[ 8\\%Y 9UAW:WAU<W:W MX7Y#X *X*X,X -X 0X6f+X/X'X -X -XL[ <X -XEWJWEX0XBXFX)X%X.p HX%X.r"
+ " 0a >X <X/X XBX FXFVAVFX+b 6X /Z <W )W %W =p JW3W$W ,W3W!| LW 1W3W MW6W MW ,W ,WNY 7"
+ "W ,W7W7W=W6W!W2W NW3W$W3W MW -b 6W ,W6W JW@W EWFVHVFW!] ;WAX 8Y 9` 5V H` HrG[ 0S<W<S0W8Y"
+ " JW:Y KXF^ HW2W Kc ;W6W IVAX >XIW CW6W!W!W3WCWCW!W=W JWCWCW\"W2W%W2W ?W >W2W JW:W =Y >X 0Y'"
+ "X0X?X?X-X0X%XCXCX,X%X2~a GV H~a HV I~b HV DX 3W@S ?r DV8V <Z FW;W DSFV'S =XFX <V "
+ ".S@VFW=S (V \"W6W <WGU IX 1XFX +V;SLU LV2V V;SLU5Z 1W:W IW:W IW:W IW:W IW:W I"
+ "X<X IX=X X *X -X -X -X -X -X -X -X ,X*X-XBXFX)X%X1X%X1X%X1X%X1X%X F[ JXAW;X.X/X'X/X'X/X'X/"
+ "X GX <X8X MWB] Bp Ep Ep Ep Ep Ep E~eBW ,|\"|\"|\"| NW ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2W\"W2W\"W2W "
+ "M~Y2WAWHW NW6W MW6W MW6W MW6W JWAX FW3W MWAX KV<V=V/V#V/VBbCV/VBY:V/V=X>V&V 1XAW 9"
+ "X @WDT ?~S+~Z)}!y'W4W W4W FWFW >WDW J~^ *r ?V &VBV Eh *VEXIX<XBZ@W /W"
+ " !W +T 4~W 5f 8V 0X4X >X +Y $Z NW<X 'X NZ7X ;X ?X:X HkMX '[ 7[%X 8UAV8VAU=X:X NX6"
+ "X#X *X*X,X -X 0X6f+X/X'X -X -XK[ =X -XDVJVDX0XAWFX)X%X.m EX%X.XA\\ -^ ?X <X/X XBX FXFVAVFX,"
+ "c 6X /Y ;W (V %W ?r JW3W$W ,W3W!| LW 1W3W MW6W MW ,W ,a 6W ,W7W7W=W6W!W2W NW3W$W3W M"
+ "W ,e :W ,W6W JW@W DWGVHVGW N[ 9WBW 8Y 8^ 3V F^ I~X 0S;U;T1W8Y JW8X MXC\\ HW2W Ia ;W6W IWB"
+ "W >XHX DW6W!W<W<W3WCWCW!W=W JWCWCW\"W2W%W2W ?W >W2W KX:X ?Y =X /X'X0Y@X@Y-X0X%YDXDY,X%X2~a "
+ "GV H~a HV I~b HV DX 3W@S ?r DV8V ;X DW;V DSFV'S >XFX ;V .S@VFW=S (V \"W6W "
+ ":UGU IX 0XFX -V;TLU MV0U!V;TLU6Y 0X:X KX:X KX:X KX:X KX:X KX:X JW<X X *X -X -X -X -X"
+ " -X -X -X ,X*X-XAWFX)X%X1X%X1X%X1X%X1X%X F[ JXBW:X.X/X'X/X'X/X'X/X GX <X9Y MWA] Er Gr Gr G"
+ "r Gr Gr G~gBW ,|\"|\"|\"| NW ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2W\"W2W\"W2W M~Y2WBWGW NW6W MW6W MW6W "
+ "MW6W IWBW EW3W LWBW IU<V=V.U#V.UCdDV.UCY9V.U=X>V&V 1XBX :X ?WDT ?~S,~[({ x&W4W W"
+ "4W FWFX ?XFX JV \"q >V &VBV Af -VEXGX=W@ZBW .W !W +T 4~W 5f 8V 0X4X "
+ ">X ,Y \"Y W;X 'X NZ7X <Y @Y:Y HiLX '^ =^%X 8UAV8VAU=X:X NX5X$X *X*X,X -X 0X(X+X/X'X -"
+ "X -XJ[ >X -XDVJVDX0XAXGX)X%X.i AX%X.X>Z ,\\ ?X <X/X NWBW DWFVAVFW+XMY 7X 0Z ;W (V %W "
+ "@s JW3W$W ,W3W!| LW 1W3W MW6W MW ,W ,` 5W ,W7W7W=W6W!W2W NW3W$W3W MW +g =W ,W6W JXBX DWGVH"
+ "VGW N[ 9WBW 9Y 7^ 3V F^ I[Gr /S;U;T1W8X IW7X NWA[ HW2W F^ ;W6W HVCX >XGW DW6W!W<W<W3WCWC"
+ "W!W=W JWCWCW\"W2W%W2W ?W >W2W KW9X ?Y =X /X'X/X@X@X,X0X$YEXEY+X%X2~a GV H~a HV I~b HV DX "
+ "3W@S 6X 3V8V ;X DX<V DTFV)T >WEW :V .TAVEW?T (V \"W6W :UGU IX /WEW .V;"
+ "TKU NV/U\"V;TKU7Y /X:X KX:X KX:X KX:X KX:X KX:X KX<X X *X -X -X -X -X -X -X -X ,X*X-XAXGX)X"
+ "%X1X%X1X%X1X%X1X%X G] KXCW9X.X/X'X/X'X/X'X/X GX <X9Y MW?] Hs Hs Hs Hs Hs Hs H~hBW ,|\"|\"|\"|"
+ " NW ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2W\"W2W\"W2W M~Y2WBVFW NW6W MW6W MW6W MW6W IWBW EW3W LWBW "
+ " IU<V=V.U#V.UDYMZEV.UDY8V.U#V&V 0WBX ;X >WDS >~T-~\\(y Mw&W4W W4W GXFX ?XFX JV "
+ " #r >V 'WCV <c .VEWEW=W?ZCW .W !W :~W 5f 9W 0X4X >X -Y Y!W;X 'Y Y5X =X"
+ " @Y8Y HgKX 'a Ca%X 8UAV8VAU=W8W NX4X%X *X+Y,X -X 0X(X+X/X'X -X -XI[ ?X -XDWLWDX0X@WG"
+ "X)X&Y.X 0X&Y.X=Y *[ @X <X/X NXDX DXHW@VHX,YMZ 8X 1Z :W (W &W At JW3W$W ,W3W!| LW 1W3"
+ "W MW6W MW ,W ,` 5W ,W7W7W=W6W!W2W NW3W$W3W MW )g ?W ,W6W IWBW CWGVHVGW MY 8WCX :Y 6` 5V H"
+ "` IW@m -S;V<T1W8X IW7X W@[ HW2W Ia ;W6W HVCW >XFX EW6W!W<W<W3WCWCW!W=W JWCWCW\"W2W%W2W ?W "
+ ">W2W KW8W @Y <X /X'X/X@X@X,X0X#YFXFY*X&Y2~a GV H~a HV I~b HV DX 3W@S 6X 3V8V ;X CX=V "
+ " CSFV)S =WEW :V -SAVDW@S 'V \"W6W :UGU IX /WEW .V<TJU NV/U\"V<TJU8Z /W8W KW"
+ "8W KW8W KW8W KW8W KX:X KX<X X *X -X -X -X -X -X -X -X ,X+Y-X@WGX)X&Y1X&Y1X&Y1X&Y1X&Y H_ LX"
+ "DW9Y.X/X'X/X'X/X'X/X GX <X:Y LW>] Jt It It It It It I~iBW ,|\"|\"|\"| NW ,W ,W ,W /W2W NW6W!W"
+ "2W\"W2W\"W2W\"W2W\"W2W M~Y2WCVEW NW6W MW6W MW6W MW6W IWCX EW3W LWCX IV=V=V.V$V.VFYKZFV.VFY"
+ "7V.V$V&V 0XCW ;Y =WFT >~T-~\\'w Ku%W4W W4W GXEW >WFW IV #q =V 6~X "
+ "JSN^ /VEWCW?W=ZDW .W !W :~W 5f 9V /X4X >X .Y MX\"W:X &X Y5X >Y @X6X FcJX &d Id"
+ "%X 8UAV8VAU>X8X X4X$X +X+X+X -X /X)X+X/X'X -X -XH[ @X -XCVLVCX0X@XHX(X'X-X /X'X-X<Y *Z @X "
+ "<X/X NXDX DXHV?VHX-YKY 8X 2Z 9W 'V &W B]?W JW3W$W ,W3W!| LW 1W3W MW6W MW ,W ,a 6W ,W"
+ "7W7W=W6W!W2W NW3W$W3W MW 'g AW ,W6W IWBW CWHVFVHW NZ 7WDW :Z 6a 6V Jb IU;i ,S;V<S0W7W IW"
+ "6W W?Z HW2W Kc ;W6W HWEX >XFX EW6W!W<W<W3WCWCW!W=W JWCWCW\"W2W%W2W ?W =V2V KX8X BY ;X /Y)Y/"
+ "X@X@X,X0X#YFXGZ)X'X0~` GV H~` GV H~` GV DX 3W@S 6X 3V8V M| &Z?V CSFV)S:m AXFX ;V -S"
+ "AVDW@S 'V \"W6W :UGU *m 5XFX /V;SIU V.T\"V;SIU9Z /X8X MX8X MX8X MX8X MX8X MX8X "
+ "MX;X NX +X -X -X -X -X -X -X -X ,X+X,X@XHX(X'X/X'X/X'X/X'X/X'X Ha LXFW8X-X/X'X/X'X/X'X/X G"
+ "X <X;Z LW<\\ L]?W J]?W J]?W J]?W J]?W J]?W J]?{BW ,|\"|\"|\"| NW ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2"
+ "W\"W2W\"W2W M~Y2WDVDW NW6W MW6W MW6W MW6W HWDW DW3W KWDW HV=V>V-V%V-VGYIZHV-VGY7V-V%V%V "
+ "/WDX ;X <WFT >~T-~\\'v Is$W4W W4W GWDX ?XGW HV %r =V 6~X JSJ[ 0VEV"
+ "AV?W<ZFW -W !W \"V Lf 9V /X5X =X /Z MX\"V9X &X NX5X >X ?X6X D`IX $d Ne#X 8UAV8"
+ "VBU=x X4X$X +X+X+X -X /X)X+X/X'X -X -XG[ AX -XCVLVCX0X?WHX(X'X-X /X'X-X;Y *Y @X <X/X MXFX "
+ "CXHV?VHX-XIY 9X 3Z 8W 'V &W CZ;W JW3W$W ,W3W!| LW 1W3W MW6W MW ,W ,b 7W ,W7W7W=W6W!W"
+ "2W NW3W$W3W MW %f BW ,W6W IXDX BWIVFVIW N\\ 8WEX :Y .[ 7V K\\ BT8e *S<X=S0W7V HW6X\"W=X GW2"
+ "W Me ;W6W GVEX >WDW EW6W!W<W<W3WCWCW!W=W JWCWCW\"W2W%W2W ?W =W4W KW6W CY :X .X)X.YAXAY,X0X\""
+ "ZHXHZ(X'X/Y AV BY FV GY%Y FV DX 3W@S 6X 2V:V L| %ZAV BSEV*S:m @XFX <V -SAVCWAS "
+ " 'V \"W6W :UGU *m 6XFX .V<TIU V/U\"V<TIU9Y .x Mx Mx Mx Mx Mx Mu NX +X -X -X -X -X "
+ "-X -X -X ,X+X,X?WHX(X'X/X'X/X'X/X'X/X'X Ic MXGW7X-X/X'X/X'X/X'X/X GX <X=[ KW:[ NZ;W KZ;W K"
+ "Z;W KZ;W KZ;W KZ;W KZ;{BW ,|\"|\"|\"| NW ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2W\"W2W\"W2W &WEVCW NW6W "
+ "MW6W MW6W MW6W HWEX DW3W KWEX GV>V>V,V&V,VIYGZIV,VIY6V,V&V&W /XEW N~X'VGT =~T-~\\"
+ "&u Ir#W4W NV4W HXDX ?XHX HV KX ,V 6~X JSHZ 2VDVAV?W;ZGW -W !W \"V "
+ "Lf :W .X6X =X 0Z LY#~ /X NX5X >X @X5Y AYFX !d >~X >d X 8UAV8VBU>z!X3X%X +X+X+X -X /X"
+ ")X+X/X'X -X -XF[ BX -XCWNWCX0X?XIX(X'X-X /X'X-X:X )Y AX <X/X MXFX BWHV?VHW-YIY 9X 3Y 7W 'W"
+ " 'W CX9W JW3W$W ,W3W!W 'W 1W3W MW6W MW ,W ,WNZ 8W ,W7W7W=W6W!W2W NW3W$W3W MW !c CW ,"
+ "W6W HWDW AWIVFVIW N] 8WFW :Y *Y 8V KY ?R3` (S<X=S0W7V HW5W\"W=X GW2W N[ 0W6W GWFW >XDX FW"
+ "6W!W<W<W3WCWCW!W=W JWCWCW\"W2W%W2W ?W =W4W LX6X DY :X .X)X-XAXAX+X0X!ZIXIZ'X'X.Y BV CY EV"
+ " FY'Y EV DX 3W@S 6X 2V:V L| $[CV BTFW,T:m ?XFX =V -TBVBVBT 'V \"W6W :UGU "
+ " *m 7XFX .V<THU!V/U\"V<THU:Y .z z z z z Nx Nv NX +X -X -X -X -X -X -X -X ,X+X,X?XIX(X'X/X'"
+ "X/X'X/X'X/X'X Je NXGV6X-X/X'X/X'X/X'X/X GX <X@^ KW9[ X9W KX9W KX9W KX9W KX9W KX9W KX9W MW "
+ ",W ,W ,W ,W )W ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2W\"W2W\"W2W &WFVBW NW6W MW6W MW6W MW6W GWFW CW3"
+ "W JWFW FV>V?W,V'W,VJYEZKW,VJY6W,V'W&W /XFX N~X'WHT =~T-~\\%s Gp\"W4W NV4V GXCW >WH"
+ "X HW LX ,V 6~X JSGY 3VDWAW@W:ZIW ,W !W \"V Lf :W .X6X =X 1Z JX#"
+ "~ /X NX5X ?Y @X4X .X Md A~X Ad LX 8UAV8VBU>z!X3X%X +X+X+X -X /X)X+X/X'X -X -XE[ CX -XB"
+ "VNVBX0X>WIX(X'X-X /X'X-X9X *Y AX <X/X MXFX BXJW?WJX.YGY :X 4Z 7W 'W 'W DX8W JW3W$W ,"
+ "W3W!W 'W 1W3W MW6W MW ,W ,WLY 9W ,W7W7W=W6W!W2W NW3W$W3W MW K_ DW ,W6W HXFX AWIVFVIW ^ 8W"
+ "FW ;Y (Y 9V LY >Q.X $T>Z?T0W8W HW5W\"W<W GW2W Y -W6W GWGX >WCX FW6W!W<W<W3WCWCW!W=W JWCWC"
+ "W\"W2W%W2W ?W =W4W LX6X EY 9X .Y+Y-YBXBY+X0X ZJXJZ&X'X-Y CV DY DV EY)Y DV DX 3W@S 6X 2W"
+ "<W L| #\\FW ASFW,S9m >XFX >V ,SBVBWCS &V \"W6W :UGU *m 8XFX .V<TGU\"V.U#V<T"
+ "GU;Y -z z z z z z v NX +X -X -X -X -X -X -X -X ,X+X,X>WIX(X'X/X'X/X'X/X'X/X'X KZMZ XHW6X-X"
+ "/X'X/X'X/X'X/X GX <u JW7Y!X8W LX8W LX8W LX8W LX8W LX8W LX8W MW ,W ,W ,W ,W )W ,W ,W ,W /W2"
+ "W NW6W!W2W\"W2W\"W2W\"W2W\"W2W &WGWBW NW6W MW6W MW6W MW6W GWFW CW3W JWFW FW?V?V+W(V+WKXCY"
+ "KV+WKX5V+W(V%W .WFX N~X'WHT =~T-~\\$q Eo\"W4W NV4V GWBW >XIW GW LX "
+ " ;~X JSFX 3VDV?V@W9ZJW +V \"W !V V -X6X =X 2Z IX#~ /X NX5X ?X ?X4X .X Jd D~"
+ "X Dd IX 8UAV8VCV>z!X3X%Y ,X,Y+X -X /Y*X+X/X'X -X -XD[ DX -XBVNVBX0X>XJX(Y)X,X /Y)X,X9Y *X "
+ "AX <X/X LXHX AXJV=VJX.XEY ;X 5Z 6W &V 'W DW7W JW3W$W ,W3W!W 'W 1W3W MW6W MW ,W ,WKY "
+ ":W ,W7W7W=W6W!W2W NW3W$W3W MW H\\ DW ,W6W GWFW @WJVDVJW!` 9WGX <Y &X 9V LX =P (T?\\@T0W8"
+ "X IW5W\"W<W GW2W X ,W6W FVGW >XBW FW6W!W<W<W3WCWCW!W=W JWCWCW\"W2W%W2W ?W =W4W LW4W FY 8X -X"
+ "+X+YCXCY*X0X N\\MXM\\%Y)X+Y DV EY NQFVFQ Y+Y CV DX 3W@S 6X 1V<V K| ![HW @TFW.T9m =XFX"
+ " ?V ,TCVAVDT &V \"W6W :UGU *m 9XFX -V<SFU\"V/U\"V<SFU;X ,z z z z z z v NY ,X -"
+ "X -X -X -X -X -X -X ,X,Y,X>XJX(Y)X.Y)X.Y)X.Y)X.Y)X KZKZ!YJW6X,X/X'X/X'X/X'X/X GX <t IW6Y\"W"
+ "7W LW7W LW7W LW7W LW7W LW7W LW7W MW ,W ,W ,W ,W )W ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2W\"W2W\"W2W "
+ " &WHWAW NW6W MW6W MW6W MW6W GWGX CW3W JWGX EV?V@W*V)W*VJVAWKW*VJV5W*V)W%W .XGW M~X"
+ "&WJT <~S,kNn#o Cm!W4W NV4V HXBX ?XJX FW MY <~X JSEX 5VCV?V@W8ZLW "
+ "*W #W !V V -X6X =X 3Z HX#~ /X NX5X @Y ?X4X /X Ge G~X Ge GX 8UAV9WCU>|\"X3X$X ,"
+ "X,X*X -X .X*X+X/X'X -X -XC[ EX -XA\\AX0X=WJX'X)X,X .X)X,X8X *X AX <X/X LXHX AXJV=VJX/YEY ;X"
+ " 6Z 5W &V 'W DW7W JW3W$W ,W3W!W 'W 1W3W MW6W MW ,W ,WJY ;W ,W7W7W=W6W!W2W NW3W$W3W M"
+ "W EZ EW ,W6W GWFW ?WKVDVKW!b 9WHW <Y $W 9V LW BTAVNUAT/W8X IW5W#W;V FW2W!X +W6W FWIX"
+ " >XBX GW6W!W<W<W3WCWCW!W=W JWCWCW\"W2W%W2W ?W =W4W MX4X HY 7X -Y-Y+ZDXDZ*X0X Mt#X)X*Y EV "
+ "FY NSGVGS Y-Y MQFVFQ X 3W@S 6X 1W>W 9X =\\KW >SEW<PCS 6XFX @V +SCVAWES %V "
+ "\"W6W :UGU &XFX -V<TFU#V/U\"V<TFU<X ,|\"|\"|\"|\"|\"|\"w MX ,X -X -X -X -X -X -X -X ,X,X+X="
+ "WJX'X)X-X)X-X)X-X)X-X)X LZIZ!XKW5X,X/X'X/X'X/X'X/X GX <s HW5X\"W7W LW7W LW7W LW7W LW7W LW7W"
+ " LW7W MW ,W ,W ,W ,W )W ,W ,W ,W /W2W NW6W!W2W\"W2W\"W2W\"W2W\"W2W &WIW@W NW6W MW6W MW6W MW6W"
+ " FWHW BW3W IWHW DW@VAW)W+W)WJT?UKW)WJT5W)W+W$W -WHX M~X&WJT ;eMQMe+jNQNj!m Bl W4"
+ "W NW6W HXBX >WJX FW LX <~X JSEX 6WCV?V@W7ZMW *W #W !V !W -X6"
+ "X =X 4Z GX#~ /X NX5X @X >X4X /X De J~X Je DX 8U@V:WDV>|\"X3X$X ,X-Y*X -X .X*X+X/X'X -X"
+ " -XB[ FX -XA\\AX0X=XKX'X*Y,X .X*Y,X8Y +X AX <Y1Y KWHW ?WJV=VJW/YCY <X 7Z 4W &W (W EW6"
+ "W JX5X$X -X5X!X (W 0W5X MW6W MW ,W ,WIY <W ,W7W7W=W6W!X4X NX5X$X5X MW CX EW ,W6W GXHX ?WK"
+ "VDVKW!XNY :WIX =Y #X :V MX BUCVMVBT/W9Y IW5W#W<W FW3X!W *W6W EVIX ?X@W GW6W!W=Y=W3XDW"
+ "DX!W=W JWCWCW\"X4X%X4W >W <W6W LX4X HY 7X ,X-X)ZEXEZ)X0X Lr\"X)X)Y FV GY NUHVHU Y/Y MSGVGS"
+ " !X 3XAS 6X 0W@W 8X ;\\NW =TEX@RDT 5XFY BV +TDV@WGT %V \"W6W :UGU (YF"
+ "X ,V=TEU#V0U!V=TEU<X ,|\"|\"|\"|\"|\"|\"w MX ,X -X -X -X -X -X -X -X ,X-Y+X=XKX'X*Y-X*Y-X*Y-X*Y-"
+ "X*Y MZGZ\"XLW5Y,Y1Y'Y1Y'Y1Y'Y1Y GX <r GW4X$W6W MW6W MW6W MW6W MW6W MW6W MW6X NX -X -X -X -X"
+ " *W ,W ,W ,W /W2W NW6W!X4X\"X4X\"X4X\"X4X\"X4X &WIV@X NW6W MW6W MW6W MW6W FWIX BX5X IWIX "
+ "CWAVAW(W,W(WJR=SJW(WJR4W(W,W$W -XIX M~X&WJS :dLQLd+iMQNj!l @j NW4W NW6W HW@W >WJW DW"
+ " MX .VCV :SDW 6VBV?V@W6b )W #W !V !V +X8X <X 5Z FX#~ /X MW5"
+ "X @X >X4X /X Ad L~X Ld AX 8VAV:WDU=|\"X3X$Y -X-Y*X -X .Y+X+X/X'X -X -XA[ GX -XA\\AX0X<WK"
+ "X'Y+X+X .Y+Y,X7X +X AX ;X1X JXJX ?XLW=WLX/XAY =X 7Y 3W %V (W EW7X JX5W\"W ,W5X W (W 0"
+ "W5X MW6W MW ,W ,WHY =W ,W7W7W=W6W W4W MX5W\"W5X MW BX FW ,W6W FWHW >WKVDVKW\"XLX 9WJW =Z #X"
+ " :V MX AUEVKVDU/X:Y IW5W#W<W EW4W!X *W6W EVJX >X@W GW6W!W=Y=W2WDWDW W=W JWCWCW\"X4W#W4"
+ "W >W <W6W LW2W IY 6X ,Y/Y(ZFXFZ(X0X Kp!Y+X'Y GV HY NWIVIW Y1Y MUHVHU \"X 2WAS 6X 0YDY 8X"
+ " :c <TE[FUDS 3XFY CV *SDV@WGS $V \"W6W :UGU )YFX ,V=TDU$V0V\"V=TDU=X +"
+ "|\"|\"|\"|\"|\"|#x MY -X -X -X -X -X -X -X -X ,X-Y+X<WKX'Y+X,Y+X,Y+X,Y+X,Y+X MZEZ#YNW4X*X1X%X1X"
+ "%X1X%X1X FX <p EW4X$W7X MW7X MW7X MW7X MW7X MW7X MW7Y MW ,W ,W ,W ,W *W ,W ,W ,W .W4W MW6W"
+ " W4W W4W W4W W4W W4W $WKV?W MW6W MW6W MW6W MW6W EWJW AX5W GWJW BXBVBW'X.W'XJP;QJW'XJP"
+ "4W'X.W#V ,XIW L~X%WLT :dLQLc*iMQMi k ?i NW4W NW6W IX@X ?XLX DW MY "
+ " 0VBV :SDW 7VAV?V@X6a )W #W !V !V +X8X <X 6Z EX#~ 0Y MW5X AY >X4X 0X =d ~X"
+ " d LUAW<XEV>X2X#X3X#X -X.Y)X -X -X+X+X/X'X -X -X@[ HX -X@Z@X0X<XLX&X+X+X -X+X+X7Y ,X AX "
+ ";X1X JXJX ?XLV;VLX0YAY =X 8Z 3W %V (W EW7X JX5W\"W ,W5X W (W 0W5X MW6W MW ,W ,WGY >W "
+ ",W7W7W=W6W W4W MX5W\"W5X MW BX FW ,W7X FWHW >WLVBVLW#YKX :WJW =Y !W :V MW @VHXJWHV-W:"
+ "Y IW5W#W<W EW4W!W )W6W EWKX ?X?X HW6W!X>Y>W1WDWDW W=W JWCWCW\"X4W#W4W >W <W6W MX2X KY 5X +Y"
+ "1Y'[GXH\\(X0X Jn NX+X&Y HV IY NYJVJY Y3Y MWIVIW #X 2WAS 6X 0[H[ 8X :V %` :TEiET 2YGY "
+ " DV *TEV?WIT $V \"W6W :UGU *YGY ,V<SCU%V0V\"V<SCU=X ,X2X$X2X$X2X$X2X$X2X$X2X"
+ "$X8X LX -X -X -X -X -X -X -X -X ,X.Y*X<XLX&X+X+X+X+X+X+X+X+X+X NZCZ#`3X*X1X%X1X%X1X%X1X FX"
+ " <m BW3W$W7X MW7X MW7X MW7X MW7X MW7X MW7Y MW ,W ,W ,W ,W *W ,W ,W ,W .W4W MW6W W4W W4W W4"
+ "W W4W W4W 5Z IWLV>W MW7X MW7X MW7X MW7X EWJW AX5W GWJW AXCVCW%X0W%X0W%X0W%X0W\"V +WJX "
+ " ?X 2WLT 9bKQKb)gLQMh Mi =g MW4W MV6W IX@X ?XLX CW MX 0VBV :SDW "
+ "7VAV?V@X5_ (W #W !V \"W +X8X <X 7Z DX 5X 'X LX7X @X =X4X 0X ;e Le JUAW<XFV"
+ "=X1W#X3X#Y .X.Y)X -X -Y,X+X/X'X -X -X?[ IX -X@Z@X0X;XMX&Y-Y+X -Y-Y+X6X ,X AX ;X1X IXLX >XL"
+ "V;VLX1Y?Y >X 9Z 2W %W )W EW7X JX5W\"X -W5X X )W 0X7Y MW6W MW ,W ,WFY ?W ,W7W7W=W6W W4"
+ "W MX5W\"W5X MW AW FW ,W7X FXJX =WMVBVMW#YJY ;WKX >Y W :V MW ?dId,W;Z IW5W#W=W DW4W!W"
+ " )W6W DVKW >X>W HW6W W>Y>W1WDWDW W=W JWCWDX\"X4W#W4W >W ;V7W LX2X LY 4X *X1X%]JXJ]'X0X Hj L"
+ "Y-Y%Y IV JY LYKVKY MY5Y MYJVJY $X 2XBS 6X 2q 9X :V #\\ 7TDgFT /XFX EV )TFV>VJT #"
+ "V \"W6W :UGU +XFX *V=TCU%V1V!V=TCU=X ,X1W$X1W$X1W$X1W$X1W$X2X%X7X LY .X -X -X -"
+ "X -X -X -X -X ,X.Y*X;XMX&Y-Y+Y-Y+Y-Y+Y-Y+Y-Y ZAZ$_3Y*X1X%X1X%X1X%X1X FX <i >W3W$W7X MW7X M"
+ "W7X MW7X MW7X MW7X MW7Z NX -X -X -X -X +W ,W ,W ,W .W4W MW6W W4W W4W W4W W4W W4W 5Z IWMV=W"
+ " MW7X MW7X MW7X MW7X EWKX AX5W GWKX @XDVDX$X2X$X2X$X2X$X2X\"V +XKW ?X 1WMT 7`JQKa"
+ "'fLQLf Kg <f LW4W MW8W HW>W >WLW BX NY 1VBV :SDW 8V@V?V?W4] &V $W "
+ " V \"V *Y:Y <X 8Z DY 5X 'X KW7X @X =X5Y 1Y 8e #e GU@W>YGW>X0X$X4Y\"Y /X/Y(X -X"
+ " ,Y-X+X/X'X -X -X>[ JX -X@Z@X0X;XMX%Y/Y*X ,Y/Y*X6Y -X AX ;Y3Y IXLX =WLV;VLW0X=Y ?X :Z 1W $"
+ "V )W EW8Y JY7X\"X -X7Y X )W 0X7Y MW6W MW ,W ,WEY @W ,W7W7W=W6W X6X MY7X\"X7Y MW AW FW"
+ " ,X8X EWJW <WMVBVMW#XHX :WLW >Y NW :V MW >bGc,W;[ JW6X#W=W DX6X!W )W6W DVLX >W=X IW7"
+ "X W>Y>W1XEWEX W=W IWDWDW!Y6X#X6X >W ;W8W MX0X MY 4X *Y3Y$^LXL^&X0X Ff IY/Y#Y JV KY JYLVL"
+ "Y KY7Y KYKVKY #X 2XBS 6X 3t ;X :V ![ 8TCfFT .XFX FV )UGV>WKT MW7X :UGU "
+ " ,XFX *V=TBU&V2W!V=TBU=X -X0X&X0X&X0X&X0X&X0X&X0W%X7X KY /X -X -X -X -X -X -X -X ,X/Y)"
+ "X;XMX%Y/Y)Y/Y)Y/Y)Y/Y)Y/Y Z?Z$^4Y)Y3Y%Y3Y%Y3Y%Y3Y FX <X -W3W$W8Y MW8Y MW8Y MW8Y MW8Y MW8Y "
+ "MW8[ NX -X -X -X -X +W ,W ,W ,W .X6X MW6W X6X X6X X6X X6X X6X 5Z I_=X MX8X MX8X MX8X MX8X "
+ "DWLW @Y7X FWLW >XEVFY\"X5Y\"X5Y\"X5Y\"X5Y!V *WLX @X /WNT 7`JQJ_&eKQKe Je :d KW4W MW8"
+ "W HW>X ?XNX AX Y 1VCV 9SDW 9V?V?V?X4\\ &W %W V \"V )X:X ;X 9Z"
+ " CX 4X (Y KW7X AX <Y6Y 1X 4e )e DVAX@ZHW=X0X$X4Y\"Y*P&X0Z(X -X ,Y-X+X/X'X -X -X=[ K"
+ "X -X?X?X0X:XNX%Y/Y*X ,Y/Y*X5X .Y AX :X3X HXLX =XNW;WNX1Y=Y ?X ;Z 0W $V )W EW8Y JY7W "
+ "W ,W7Y NX *W /W8Z MW6W MW ,W ,WDY AW ,W7W7W=W6W NW6W LY7W W7Y MW AW FW ,X9Y EWJW <WMVBVMW"
+ "$XFX ;WMX ?Y MW :V MW =`Ea+X<[ JW6W\"W>W BW6W W )W6W DWMX ?X=X IX8X W?[?W0WEWEW NW=W "
+ "IWDWDW!Y6W!W6W =W ;W8W MX0X NY 3X )Y5Y\"z%X0X C` FY/Y\"X JV KX HYMVMY IX7X IYLVLY \"X 1XCS"
+ " 6X 4v <X :V [ 8TBbET ,WEW FV (T$T LX8X :UGU ,WEW )V=m,V3W V=mCX -"
+ "X0X&X0X&X0X&X0X&X0X&X0X&X7X KY*P&X -X -X -X -X -X -X -X ,X0Z)X:XNX%Y/Y)Y/Y)Y/Y)Y/Y)Y/Y!Z=Z"
+ "%]3Y(X3X#X3X#X3X#X3X EX <X -W3W$W8Y MW8Y MW8Y MW8Y MW8Y MW8Y MW8[ MW ,X -X -X -X ,W ,W ,W "
+ ",W -W6W LW6W NW6W MW6W MW6W MW6W MW6W 4Z H^=W LX9Y MX9Y MX9Y MX9Y DWMX @Y7W EWMX =Y8Y "
+ "Y8Y Y8Y Y8Y Y8Y V *WLX AX .WNT 6^IQI]$cKRJc Id 8c KW4W MX:X IX>X ?XNX AY "
+ " Y4P VBV 9SDW 9V?V?V?Y4Z %W %W V #W )X:X ;X :Z CY 4X (Y KX9Y AX ;X6X 1"
+ "Y 1e /e @U@XB[JX<X/W$X4X Y,Q&X1Z'X -X +Y.X+X/X'X -X -X<[ LX -X?X?X0X:XNX$Y1Y)X +Y1Y"
+ ")X5Y /X @X :X4Y GXNX <XNV9VNX2Y;Y @X ;Y /W $W *W EW9Z JZ9X X -X9Z NX *W /X9Z MW6W MW"
+ " ,W ,WCY BW ,W7W7W=W6W NX8X LZ9X X9Z MW AW FW +W9Y EXLX <WNV@VNW%YEX ;WNW ?Y LW :V MW "
+ " <^C_)W=\\ JX7W\"W>W BX8X W )W6W CVNX >W;W IX8X X@[@X0XFWEW NW=W IWDWEX!Z8X!X8X =W :W:W LX"
+ "0X Y 2X (Y7Y Nv#X0X ?X AY1Y V IV JV FYNVNY GV5V GYMVMY !X 1XCS 6X 5x =X :V MZ 8T?ZBT"
+ " *VDV FV 'T&T KX8X :UGU ,VDV )V<m-V3V NV<mCX -X/W&X/W&X/W&X/W&X/W&X0X"
+ "'X6X JY,Q&X -X -X -X -X -X -X -X ,X1Z(X:XNX$Y1Y'Y1Y'Y1Y'Y1Y'Y1Y!Z;Z%[3Y'X4Y#X4Y#X4Y#X4Y EX"
+ " <X -W3W$W9Z MW9Z MW9Z MW9Z MW9Z MW9Z MW9] NX -X -X -X -X ,W ,W ,W ,W -X8X LW6W NX8X MX8X "
+ "MX8X MX8X MX8X 4Z H]=X KW9Y LW9Y LW9Y LW9Y CWNW ?Z9X DWNW ;Y;Z MY;Z MY;Z MY;Z MY;Z NV "
+ "*XMW AY -[ 3ZHRH[\"aJRI` Fb 6a JW4W LW:W HX=W >WNX @Y !Z6Q VBV K"
+ "P>SEW 9V>WAW>X3Z &W %W V #V 'X<X :X ;Z BY 4X )Y IW9X AY ;Y8Y 2Y .d 1d >U?"
+ "ZH^MZ<X.X%X5Y NY.R&X2Z&X -X *Y/X+X/X'X -X -X;[ MX -X&X0X9a$Z3Y(X *Y3Y(X4X$P-Y @X :Y5Y GXNX"
+ " <XNV9VNX2X9Y AX <Z /W #V *W EX:Z JZ9X NX .X9Z MX +W .X;[ MW6W MW ,W ,WBY CW ,W7W7W="
+ "W6W NX9Y LZ9X X9Z MW AW FW +W:Z DWLW :^@^$XDY <WNW @Z LW :V MW ;\\@['X>\\ JX8X\"W?W AX"
+ "9Y X *W6W CVNX ?X;X JX9Y NW@[@W/XFWFX NW=W IXEWEX!Z8X!X8W ;W ;W;X MX.X\"Y 1X 'Y9Y Lt\"X0X ?X"
+ " @Y3Y MT HV IT Dj ET3T EYNVNY X 0XDS 6X 6ZM`LY >X :V LY 7T)T (UCU ET(T "
+ " JX9Y :UGU ,UCU )V;m.V3V NV;mCY7P HX.X(X.X(X.X(X.X(X.X(X.X(X6X IY.R&X -X -X -X -"
+ "X -X -X -X ,X2Z'X9a$Z3Y&Z3Y&Z3Y&Z3Y&Z3Y!Z9Z&Z3Y&Y5Y#Y5Y#Y5Y#Y5Y EX <X -W3W$X:Z MX:Z MX:Z M"
+ "X:Z MX:Z MX:Z MX:^ NX -X -X -X -X -W ,W ,W ,W -X8X LW6W NX9Y MX9Y MX9Y MX9Y MX9Y 4Z H\\=Y K"
+ "W:Z LW:Z LW:Z LW:Z CWNW ?Z9X DWNW :[@[ K[@[ K[@[ K[@[ K[@[ MV )WNX AX ,[ 1WGRFW "
+ "N_IRH^ Da 5_ IW4W LX<X HW<W >` >Y !Y8S MX +VBV KQ?SFX 9V=VAV=Y6] &V &W"
+ " NV BX 1X 1V 'Y>Y :X <Z BY 3X GP3Z IX;Y AX :Y9Z 2X GX -X 7a 1a .X 6V@iNa;X.X%X6Z N"
+ "Z1T&X4\\&X -X *Z0X+X/X'X -X -X:[ NX -X&X0X9a#Z5Z(X *Z5Z(X4Y%R/Y @X 9Y7Y EWNW :WNV9VNW2Y9Y A"
+ "X =Z .W #V *W EX;[ J[;X MY .X;[ MY2P JW .Y=\\ MW6W MW ,W ,WAY DW ,W7W7W=W6W MX:X K[;X"
+ " MX;[ MW /P4X FX ,X<[ DXNX :^@^%XBX <` @Y KW :V MW 8V;W%X?^ KY9X!V@X @X:X NX *W6W C_"
+ " >X:W JY;Z NXB]BX.XGWGX MW=W HXFWFX [:X NX:X ;W :W<W LX.X\"Y 1X &Y;Y Ip X0X ?X @Z5Z LR GV "
+ " HR Bh CR1R Cj NX 0YES 6X 7ZJ\\IY ?X :V KY 8U+U 'TBT DU+T IY;Z :UGU "
+ " ,TBT (V;m.V4V MV;mCY8Q HX.X(X.X(X.X(X.X(X.X(X.X)X5X IZ1T&X -X -X -X -X -X -X -X ,X4\\'"
+ "X9a#Z5Z%Z5Z%Z5Z%Z5Z%Z5Z\"Z7Z&Z5Z%Y7Y!Y7Y!Y7Y!Y7Y DX <X -W4X$X;[ MX;[ MX;[ MX;[ MX;[ MX;[ MX"
+ ";`3P=Y .Y2P LY2P LY2P LY2P LW ,W ,W ,W ,X:X KW6W MX:X KX:X KX:X KX:X KX:X 3Z GZ<X JX<[ LX<"
+ "[ LX<[ LX<[ C` ?[;X C` 9_J_ I_J_ I_J_ I_J_ I_J_ LV )` AX +Z S <[GRFZ A_ 4^ HW4W"
+ " KX>X HX<X ?` =Z \"Y:T MX +VCV JSASFX :V<VAV<Y8_ 'W 'W NV BX 1X 2W"
+ " &X>X 9X =Z 1P2Z 3X GQ5Z GX=Y @X 9Y:Y KP8Z GX -X 4^ 1^ +X 5U?gM_9W,W%X7Z L[4U&X6]%X -X )"
+ "[2X+X/X'X -X -X9[ X -X&X0X8`\"Z7Z'X )Z7Z'X3X%T2Y ?X 9Z9Z E` :_9_3Y7Y BX >Z -W #W +W D"
+ "X=\\ J\\=Y LY7P HY=\\ LY5R JW -Y?] MW6W MW ,W ,W@Y EW ,W7W7W=W6W MY<Y K\\=Y MY=\\ MW /R6W DW ,Y"
+ "=[ CWNW 9^@^&X@X <^ @Y JW :V MW HXA` LZ;X V@W ?Y<Y MX +W6W B^ ?X9W JZ<Z NXB]BX.YHW"
+ "HY MW=W HYGWGY \\<Y NY<X :W :X>X LX.X#Y 0X %Y=Z Gl MX0X ?X ?Z7Z JP FV GP @f AP/P Ah MX "
+ "/YFSDP BX 8ZFVEY @X :V JX 7V.U %SAS CU.U HZ<Z :UGU ,SAS (V:m/V5W"
+ " MV:mBY;S HW,W(W,W(W,W(W,W(W,W(X.X)X5X H[4U&X -X -X -X -X -X -X -X ,X6]&X8`\"Z7Z#Z7Z#Z7Z#Z7"
+ "Z#Z7Z\"Z5Z&[8Z$Z9Z!Z9Z!Z9Z!Z9Z DX <X -W4W\"X=\\ LX=\\ LX=\\ LX=\\ LX=\\ LX=\\ LX=b6R<Y7P GY5R KY5R"
+ " KY5R KY5R LW ,W ,W ,W ,Y<Y KW6W MY<Y KY<Y KY<Y KY<Y KY<Y 3Z GY<Y JY=[ LY=[ LY=[ LY=[ B^ >"
+ "\\=Y B^ 7r Gr Gr Gr Gr KV (_ BX )Y S 8RBSCR <] 2\\ GW4W KZBZ HX;W >_ <[ "
+ " $[=U MX ,VBV JUCSHY :V;WCW<[<b (W 'W NV BX 1X 2W &Y@Y 9X >Z 0R5Z 2X GT9[ G"
+ "Y?Z AY 9[>[ KR;Z FX -X 1[ 1[ (X 5V>dL^9X,X&X9[ J[7W&X9_$X -X (\\6Z+X/X'X -X -X8[!X -X&X0X"
+ "8`![;[&X ([;[&X3Y&W7[ ?X 8Z;Z D` :^7^3X5Y CX ?Z ,W #W +W DY?] J]?Y KZ:R GY?] LZ8T JW"
+ " -ZA^ MW6W MW ,W ,W?Y FW ,W7W7W=W6W LY>Y J]?Y KY?] MW /T9X DX ,Y@] CWNW 9]>]'Y@Y =^ AY IW"
+ " :V MW HYCXNW L\\>Y VAX >Y>Y LY ,W6W B] >X9X K[>[ MXDVMVDX,YIWIY LW=W GYHWHY N]>Y LY"
+ ">Y :X :X@X LX,X%Y /X $ZAZ Ch KX0X ?X >[;[ ?V 6d >f LX /[HSFR BX 9Z3Y AX :V IX 7"
+ "V1V #R@R BU0U G[>[ :UGU ,R@R 'V(U)V6W LV(U<Z>U IX,X*X,X*X,X*X,X*X,X"
+ "*X,X*W4X G[7W&X -X -X -X -X -X -X -X ,X9_%X8`![;[![;[![;[![;[![;[\"Z3Z(];[\"Z;Z NZ;Z NZ;Z NZ"
+ ";Z CX <X -WJP;X\"Y?] LY?] LY?] LY?] LY?] LY?] LY?XNZ9T<Z:R GZ8T KZ8T KZ8T KZ8T LW ,W ,W ,W "
+ "+Y>Y JW6W LY>Y IY>Y IY>Y IY>Y IY>Y 2Z FY>Y HY@] KY@] KY@] KY@] B^ >]?Y A^ 6o Do Do Do "
+ "Do IV (_ CX (Y S (S ,[ 0[ GW4W J\\H\\ GW:W >^ :\\ %[@W MX ,VBV JXFS"
+ "IZ :V:WEW:\\@e (V 'V MV BX 1X 2V $ZDZ 8X ?Z /U;] 2X GV=\\ EZC[ @X 7[@[ JT?[ EX -X /Y "
+ " 1Y &X 5V=bK\\7X,X&X<^ I]=Z&X=b#X -X ']:\\+X/X'X -X -X7[\"X -X&X0X7_ \\?\\%X '\\?\\%X2X&Z<\\ >X 7["
+ "?[ B^ 9^7^4Y5Y CX ?Y +W \"V +W DZB_ J_CZ I[>T G[C_ K[=W JW ,\\GXNW MW6W MW ,W ,W>Y GW "
+ ",W7W7W=W6W KZBZ I_CZ J[C_ MW /W>Z DZ .ZB^ C` 8\\>\\&X>Y =\\ AY HW :V MW GZFYNY N]AZ N"
+ "WCX <ZBZ JZ:Q EW6W B] ?X7W K\\A^ NYFWMWFY,ZJWJY KW=X H[JWJ[ N_BZ JZBZ 8Y <ZDZ LX,X&Y .X #ZC"
+ "Z >_ FX0X ?X =\\?\\ >V 5b <d KX .\\JSHT BX 8X2X @X :V IX 5V4U Q?Q AV4V "
+ " F\\A^ ;UGU ,Q?Q 'V'U*V6W LV'U<[AW IX,X*X,X*X,X*X,X*X,X*X,X+X4X F]=Z&X -X -X -X"
+ " -X -X -X -X ,X=b$X7_ \\?\\ N\\?\\ N\\?\\ N\\?\\ N\\?\\ X1X(`?\\ [?[ L[?[ L[?[ L[?[ BX <X -WJS@Z\"ZB_ "
+ "LZB_ LZB_ LZB_ LZB_ LZB_ LZBYM\\>W;[>T F[=W J[=W J[=W J[=W LW ,W ,W ,W *ZBZ IW6W KZBZ GZBZ "
+ "GZBZ GZBZ GZBZ 1Z F[BZ GZB^ KZB^ KZB^ KZB^ A\\ =_CZ ?\\ 3l Al Al Al Al HV (^ BX (X "
+ " NS (S ,Z .Y FW4W In GX:X ?^ 9_ (]FZ MX ,VBV J[ISL\\ :V9XGX9^Fi )W )W "
+ " MV BX 1X 3W #[H[ Et Mx MZC_ 1X GZD^ C[G\\ @Y 7^F] IXF] DX -X ,V 1V #X 4V<^IY5X*X'y G"
+ "_D^&{!y NX &`B`+X/X'X -X -X6[#w LX&X0X7_ N^E^$X &^E^$X2Y'^C^ =X 7^E^ B^ 8]7]4Y3Y DX @~U&W "
+ "\"W ,W C\\HYNW JWNXG\\ H]EX F\\GXNW J]D[ JW +kMW MW6W MW ,W ,W=Y HW ,W7W7W=W6W K]H] IWNX"
+ "G\\ I\\GXNW MW /[E\\ Be 9[GXNW B^ 7\\>\\'X<X =\\ AX GW :V MW G\\IYM^$`F\\ MWEX ;]H] J]BV E"
+ "W6W A\\ ?X7X L_GaKP#ZJYMYJZ*[LWL[ KW=Y H\\LWL\\ MWNXG] J]H\\ 7a C[H[ L~W'x MX 1iEi HX CX0X ?X "
+ "<^E^ =V 4` :b JX -^MSLX Lz V0V ?X :V HW 4V7V MP>P @W8W 3~W :_GaKP"
+ " @UGU ,P>P 'V&U+V6V KV&U;]GZ JX*X,X*X,X*X,X*X,X*X,Y,Y,X4y7_D^&y Ny Ny Ny NX -X -X -"
+ "X ,{\"X7_ N^E^ L^E^ L^E^ L^E^ L^E^ MV/V(dE^ N^E^ L^E^ L^E^ L^E^ BX <X -WJWF[ \\HYNW K\\HYNW K"
+ "\\HYNW K\\HYNW K\\HYNW K\\HYNW K\\H[K^E[:]EX E]D[ I]D[ I]D[ I]D[ LW ,W ,W ,W )[F[ HW6W K]H] G]H"
+ "] G]H] G]H] G]H] 1Z F]G] F[GXNW J[GXNW J[GXNW J[GXNW A\\ =WNXG\\ ?\\ 1h =h =h =h =h FV ']"
+ " AV &W T )T +X -X EW4W Hl FX9W ?^ 8~R Jp MX ,VCV It 9V8XIX7sLZ "
+ "*W )W MV BX 1X 3W #n Et Mx Mu 0X Gs Ao @X 5t In CX -X )S 1S X 4V9XFU1X*X'x Ex&z y "
+ "NX %|*X/X'X -X -X5[$w LX&X0X6^ Mu#X %u#X1X'y =X 6u A^ 8]7]4X1X DX @~U&W \"W ,W ClMW J"
+ "WMk Fo EkMW Is JW *jMW MW6W MW ,W ,W<Y IW ,W7W7W=W6W Jp HWMk GkMW MW /q Ae 9kMW B^ 7\\=[(Y;"
+ "X >\\ Av 6W :V MW FkL]$u LXGX 9p Hp EW6W A[ ?X6X LpN\\#hKh)s JW<] Lu LWNm Hp 6` Bl K~"
+ "W'x MX 1iEi HX CX0X ?X ;u <V 3^ 8` IX ,o Lz NT.T >X :V HW 3X=X )X<X 2"
+ "~W :pN\\ @UGU V&U+V7i.V&U:o JX*X,X*X,X*X,X*X,X*X,X*X-X3y6x&y Ny Ny Ny NX -X "
+ "-X -X ,z!X6^ Mu Ju Ju Ju Ju KT-T(} Lu Ju Ju Ju AX <X -WJk NlMW KlMW KlMW KlMW KlMW KlMW Kn"
+ "Is9o Ds Hs Hs Hs LW ,W ,W ,W )p HW6W Jp Ep Ep Ep Ep Ls EkMW JkMW JkMW JkMW A\\ =WMk >\\ "
+ " /c 8c 8c 8c 8c CV '\\ ?T %W U *T *W ,V DW4W Gj EW8W >\\ 5~P In LX "
+ " -VBV Is 9V7g6qJZ *V )V LV BX 1X 3V !l Dt Mx Mt /X Gr ?m ?X 4r Hm BX -X &P 1P LX"
+ " 3V 3X*X'w Cv%x My NX #x(X/X'X -X -X4[%w LX&X0X5] Ls\"X $s\"X1Y(w ;X 5s ?\\ 7\\5\\5Y1Y EX @~U&W"
+ " !V ,W BjLW JWMj Dn DjMW Hr JW )hLW MW6W MW ,W ,W;Y JW ,W7W7W=W6W In GWMj EjMW MW /p"
+ " ?d 8iLW B^ 6Z<[)Y:Y >Z @v 6W :V MW EiK]$t JYLZ 7n Fo EW6W A[ ?X5W LWNfM\\\"gKg'q IW<"
+ "] Ks KWMk Fn 5` Aj J~W'x MX 1iEi HX CX0X ?X :s ;V 2\\ 6^ HX +n Lz MR,R =X :V HW "
+ "1ZEZ %ZDZ 0~W :WNfM\\ @UGU !V%U,V6i/V%U9n JX*X,X*X,X*X,X*X,X*X,X*X-"
+ "X3y5v%y Ny Ny Ny NX -X -X -X ,x NX5] Ls Hs Hs Hs Hs IR+R(WMs Js Hs Hs Hs @X <X -WJk MjLW J"
+ "jLW JjLW JjLW JjLW JjLW JmHr8n Cr Gr Gr Gr LW ,W ,W ,W (n GW6W In Cn Cn Cn Cn Ls CiLW Ii"
+ "LW IiLW IiLW @Z <WMj <Z +] 2] 2] 2] 2] @V &[ >R $V NU *U *U *U DW4W Fh DW8X ?\\ "
+ "4~ Hl KX -VBV Hp 8V5e4nGZ +W +W LV BX 1X 3V j Ct Mx Mr -X Gq =j "
+ ">Y 3p Gl AX -X 2X 3W 5X(X(u ?s$v Ky NX \"v'X/X'X -X -X3[&w LX&X0X5] Kq!X #p X0X(v :X "
+ "4p =\\ 7\\5\\6Y/Y FX @~U&W !V ,W AhKW JWLh Bm ChLW Gq JW (eJW MW6W MW ,W ,W:Y KW ,W7W7W"
+ "=W6W Hl FWLh ChLW MW /o >d 7gKW A\\ 5Z<Z(X8X >Z @v 6W :V MW DgI\\$s He 5l Dn EW6W @Y "
+ ">W4X MWMeM\\!eIe%o HW<] Jq JWLi Dk 2_ @h J~Y(x MX 1iEi HX CX0X ?X 9q :V 1Z 4\\ GX *m"
+ " Lz LP*P <X :V HW 0m \"l .~W :WMeM\\ @UGU !V%U,V6i/V%U8l JX(X.X(X"
+ ".X(X.X(X.X(X.Y)X/X2y3s$y Ny Ny Ny NX -X -X -X ,v LX5] Kq Fq Fq Fq Fq GP)P'VKp Gp Ep Ep Ep "
+ ">X <X -WJj KhKW IhKW IhKW IhKW IhKW IhKW IjEq7m Bq Fq Fq Fq LW ,W ,W ,W &j EW6W Hl Al Al A"
+ "l Al Ls AgKW HgKW HgKW HgKW @Z <WLh ;Z MV &[ =P \"U V +V )S (S CW4W "
+ "De DX8X ?\\ 2| Fh IX -VBV Ek 6V4c1kEZ +V +V KV BW 0X 4W Mf At Mx "
+ "Mq ,X Go :h =X 0l Ej ?X -W 1X 2W 6X(X(s ;o\"s Hy NX r%X/X'X -X -X2['w LX&X0X4\\ Im NX"
+ " !m NX0Y(t 9X 2m ;Z 5[5[5X-X FX @~U&W !W -W @fJW JWJe ?j AeJW En IW 'cIW MW6W MW ,W "
+ ",W9Y LW ,W7W7W=W6W Fh DWJe AeJW MW .m ;b 6eJW A\\ 5Z<Z)X6X >X ?v 6W :V MW CeG[$r Fc "
+ "2h Am EW6W @Y ?X3W MWMdL\\ cGc#m GW;\\ Hm HWKg Ah /] ?f I~Y(x MX 1iEi HX CX0X ?X 7m 8V 0"
+ "X 2Z FX (j Kz AX :V HW -g Lh ,~W :WMdL\\ @UGU \"V$U-V5i0V$"
+ "U7i HX(X.X(X.X(X.X(X.X(X.X(X/X2y1o\"y Ny Ny Ny NX -X -X -X ,t JX4\\ Im Bm Bm Bm Bm %VHm Dm "
+ "Bm Bm Bm =X <X -WJh HfJW HfJW HfJW HfJW HfJW HfJW HhBn4j ?n Cn Cn Cn KW ,W ,W ,W %h DW6W F"
+ "h =h =h =h =h KVMi >eJW GeJW GeJW GeJW ?X ;WJe 9X MW &Z =U W ,W *"
+ "R &Q BW4W B` AW6W >[ /y Dd GX -VCV Af 5V2a.gBZ ,W -W KV CX 0X 4V "
+ " Kd @t Mx Km *X Ek 6d ;X .h Bh >X .X 1X 1W 7X(X(q 7j Np Ey NX Mm\"X/X'X -X -X1[(w LX"
+ "&X0X4\\ Gi LX Ni LX/X$n 7X 0i 9Z 5[5[6Y-Y GX @~U&W V -W >cIW JWIb <g =bIW Ci FW %_G"
+ "W MW6W MW ,W ,W8Y MW ,W7W7W=W6W Ef CWIb =bIW MW +h 8a 5cIW @Z 4Y:Y*Y5X ?X ?v 6W :V MW "
+ " AbDY$WMf Ca 0f >k EW6W @Y ?W2W MWK`I[ NaEa i EW;\\ Fi FWIc >e ,\\ =b G~Y(x MX 1iEi HX CX0"
+ "X ?X 5i 6V /V 0X EX &f Iz AX :V /P;W *c Gb )~W :WK`I[ @UGU "
+ " #V#U.V4i1V#U6f FX(X.X(X.X(X.X(X.X(X.X(X/X2y/j Ny Ny Ny Ny NX -X -X -X ,p FX4\\ Gi >i "
+ ">i >i >i $VEi @i >i >i >i ;X <X -WIf EcIW FcIW FcIW FcIW FcIW FcIW Fd>i0g ;i >i >i >i HW "
+ ",W ,W ,W #d BW6W Ef ;f ;f ;f ;f JUJe ;cIW FcIW FcIW FcIW ?X ;WIb 7X MW %Y "
+ " =T X -X )P %P AW4W ?Z >W6X ?Z ,w B` EX .VBV <] 1V0]*b?[ -W -W"
+ " KV CW /X 4V I` >t Mx Hg 'X Bf 2` :X +d =b ;X .W 0X 1X 9X&X)m 0d Kj ?y NX Jg "
+ "NX/X'X -X -X0[)w LX&X0X3[ Dc IX Kf LX/Y!g 4X .e 7Z 5Z3Z7Y+Y HX @~U&W V -W =`GW JWG"
+ "^ 7b 9^GW Ad CW \"YDW MW6W MW ,W ,W7Y NW ,W7W7W=W6W B` @WG^ 9^GW MW (c 2] 3_GW @Z 3X:X*Y4Y "
+ "@X ?v 6W :V MW ?_AW$WKb @^ +` 9g CW6W ?W ?X2X NWJ^GY K]B^ Ke CW:[ Dd CWG_ 9` 'Y ;^ "
+ "F~[)x MX 1iEi HX CX0X ?X 2c 3V .T .V DX $b Gz AX :V /R>X &[ ?Z %~W "
+ " :WJ^GY ?UGU #V +V +V 1b EX&X0X&X0X&X0X&X0X&X0Y'X1X1y,d Ky Ny Ny Ny NX -X -X "
+ "-X ,j @X3[ Dc 8c 8c 8c 8c !VBc ;e :e :e :e 9X <X -WFa B`GW E`GW E`GW E`GW E`GW E`GW D`:d*"
+ "b 7d 9d 9d 9d EW ,W ,W ,W !` @W6W B` 5` 5` 5` 5` HVHa 7_GW D_GW D_GW D_GW ?X ;WG^ 5X "
+ " MW 7S @r >Y BS .V,W#Z ;V -V "
+ " 7W ;W EX ;\\ 6] +Z 5\\ 5Z <W 7X %\\ <] \"X ([ "
+ " 4c E] /[ (W W .W :Y #X 0Z 2X *\\ $W &W .Z =WDX 3XDW I["
+ " 0Y 8W -W :V MW <Z ;WH[ 9Y &Z 1] LW ?W >WGXBU FX=X E` \"W >] @WDY 3Z "
+ "2X C[ >T :[ KV /TAY EWGXBU =UGU"
+ " BT 6V +V +V ,Y ?\\ +[ 0[ 0[ 0[ 0[ KT=[ 2[ 0[ 0["
+ " 0[ 7Z ;Y .Y .Y .Y .Y .Y -Y2\\\"Z /\\ 1\\ 1\\ 1\\ CZ 3Z /Z /Z /Z /Z FVCZ 1Y .Y ."
+ "Y .Y ,W :WDX 2W LW 7R #S"
+ " >W /W 8W :V \"W 5X )X "
+ " &Z CW NV .W :W %W @W :W "
+ " -X -W :V MW LW FW ?W >W NW 0W =W "
+ " 3S GV /XGZ DW HUGU AT %"
+ "T 'R JT "
+ " #T (X :W NX LW "
+ " 7S =V /V 7W :V \"W 4X'Q "
+ "&Y %Z DW NV .W :W %W @W :W "
+ " -W ,W :V MW LW FW ?W >W NW 0W =W "
+ " 3S GV /j CW HUGU @T "
+ " %T 'P HT "
+ " \"Q 'W 9W NW KW "
+ " 7S =W 1W 7V :W \"V 2X)R "
+ " &X #Z EW NW /W :W %W "
+ " @W :W -W ,X ;V NX LW FW ?W >W NW 0W =W "
+ " 3S GV /j CW HUGU @U "
+ " &U U "
+ " \"P 'W 9W NW KV "
+ " 6S <V 1V 6V :V !V 1Y-U "
+ " 'X \"Z FW MV /W ;X %W "
+ " @W :W .X +W ;V NW KW FW ?W >W NW 0W =W "
+ " 3S GV /h AW HUGU ?T "
+ " %T NT "
+ " )X 9W X KV "
+ " 6S <W 3V 6V 9V \"V "
+ " /Z1X (X !Z Ga (V 9a ;W "
+ "$W @W :W .W *W ;V NW KW FW ?W >W NW 0W =W"
+ " 3S GV .f @W HUGU ?"
+ "U &U "
+ " U *W 8W W JV "
+ " 6S ;V 3V 6V :W \"V "
+ " .[5[ *Y Z Ha (W :a <X"
+ " $W @W :W /X *X <V X KW FW ?W >W NW 0W"
+ " =W 3S GV +a >W HUGU "
+ " >T %T "
+ " NT +X 8W !X (VIV "
+ " 6S :V 5V 5U 9W \""
+ "U +\\;] )X MZ Ia (W :a "
+ " =Y %W ?W :W /W )[ ?V #[ KW FW ?W >W N"
+ "W 0W =W 3S GV 'Z ;W "
+ " HUGU >U &U "
+ " U ,W 7W !W 'VIV "
+ " 6S :V 6W 6V "
+ " 4V *_C` )Y LZ Ja :a "
+ " (P7Y $W ?W :W 0X (b GV +b JW FW ?W >W "
+ " NW 0W =W 3S GV "
+ "7W HUGU >U &U "
+ " U -X 7W \"X 'VJW "
+ " 6S 9V 7V 5U "
+ " 3U 'x (Z KZ Ka :a "
+ " (R:Z $W ?W :W 0X (b GV +b JW FW ?W >W"
+ " NW 0W =W 3S GV "
+ " 7W #U &U "
+ " U -X 7W \"X &UJW "
+ " 6S 9W 9W "
+ " Bu ([ IZ La :a "
+ " (T>[ $X ?W :W 1X &a GV +a IW FW ?W >W N"
+ "W 0W =W 3S GV 7W "
+ " $V 'V "
+ " !V .X 6W #X %VLW "
+ " 5S "
+ " 2p -a 8XE] %Y"
+ " >W :W 3Z $_ GV +_ GW FW ?W >W NW 0W =W "
+ " 3S GV 7W /QGW "
+ " 2QGW ,QG"
+ "W 0Z 6W %Z %a "
+ " 5S 0l "
+ " +a 8p +_ "
+ " >W :W ;a !] GV +] EW FW ?W >W NW 0W =W "
+ " 3S GV 7W /` "
+ " 1` +` "
+ " 7a 5W -a #` "
+ " >e '`"
+ " 7o *^ =W :W "
+ " ;` KY GV +Y AW FW ?W >W NW 0W =W "
+ " 3S GV 7W /` 1` "
+ " +` "
+ " 7` 4W -` \"_ "
+ " 8\\ #_ "
+ " \"} 3n )^ =W :W ;` 9V "
+ " BW FW ?W >W NW 0W =W 'V "
+ " 7W /_ 0_ "
+ " *_ 6` 4W -` "
+ " !] "
+ " -] "
+ " } 3l '] <W :W ;_ 8V BW FW ?"
+ "W >W NW 0W =W 'V "
+ " 7W /^ /^ "
+ " )^ 5_ 3W -_ N[ "
+ " "
+ " ,[ M} 2j "
+ " &\\ ;W :W ;^ 7V BW FW ?W >W NW 0W =W"
+ " 7W -Y "
+ " *Y $Y "
+ " 2^ 2W -^ LX "
+ " "
+ " *X J} /d #Z 9W :"
+ "W ;\\ 5V BW FW ?W >W NW 0W =W "
+ " 7W "
+ " "
+ " /\\ 0W HT "
+ " "
+ " I} *[ NW 6W :W ;Z 3V "
+ " BW FW ?W >W NW 0W =W "
+ " 7W "
+ " /Z .W "
+ " "
+ " =} "
+ " "
+ " "
+ " "
+ " D" };
+
+ // Define a 40x38 'danger' color logo (used by cimg::dialog()).
+ static const unsigned char logo40x38[4576] = {
+ 177,200,200,200,3,123,123,0,36,200,200,200,1,123,123,0,2,255,255,0,1,189,189,189,1,0,0,0,34,200,200,200,
+ 1,123,123,0,4,255,255,0,1,189,189,189,1,0,0,0,1,123,123,123,32,200,200,200,1,123,123,0,5,255,255,0,1,0,0,
+ 0,2,123,123,123,30,200,200,200,1,123,123,0,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,29,200,200,200,
+ 1,123,123,0,7,255,255,0,1,0,0,0,2,123,123,123,28,200,200,200,1,123,123,0,8,255,255,0,1,189,189,189,1,0,0,0,
+ 2,123,123,123,27,200,200,200,1,123,123,0,9,255,255,0,1,0,0,0,2,123,123,123,26,200,200,200,1,123,123,0,10,255,
+ 255,0,1,189,189,189,1,0,0,0,2,123,123,123,25,200,200,200,1,123,123,0,3,255,255,0,1,189,189,189,3,0,0,0,1,189,
+ 189,189,3,255,255,0,1,0,0,0,2,123,123,123,24,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,3,255,255,0,1,189,
+ 189,189,1,0,0,0,2,123,123,123,23,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,4,255,255,0,1,0,0,0,2,123,123,123,
+ 22,200,200,200,1,123,123,0,5,255,255,0,5,0,0,0,4,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,21,200,200,200,
+ 1,123,123,0,5,255,255,0,5,0,0,0,5,255,255,0,1,0,0,0,2,123,123,123,20,200,200,200,1,123,123,0,6,255,255,0,5,0,0,
+ 0,5,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,19,200,200,200,1,123,123,0,6,255,255,0,1,123,123,0,3,0,0,0,1,
+ 123,123,0,6,255,255,0,1,0,0,0,2,123,123,123,18,200,200,200,1,123,123,0,7,255,255,0,1,189,189,189,3,0,0,0,1,189,
+ 189,189,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,17,200,200,200,1,123,123,0,8,255,255,0,3,0,0,0,8,255,255,
+ 0,1,0,0,0,2,123,123,123,16,200,200,200,1,123,123,0,9,255,255,0,1,123,123,0,1,0,0,0,1,123,123,0,8,255,255,0,1,189,
+ 189,189,1,0,0,0,2,123,123,123,15,200,200,200,1,123,123,0,9,255,255,0,1,189,189,189,1,0,0,0,1,189,189,189,9,255,
+ 255,0,1,0,0,0,2,123,123,123,14,200,200,200,1,123,123,0,11,255,255,0,1,0,0,0,10,255,255,0,1,189,189,189,1,0,0,0,2,
+ 123,123,123,13,200,200,200,1,123,123,0,23,255,255,0,1,0,0,0,2,123,123,123,12,200,200,200,1,123,123,0,11,255,255,0,
+ 1,189,189,189,2,0,0,0,1,189,189,189,9,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,11,200,200,200,1,123,123,0,11,
+ 255,255,0,4,0,0,0,10,255,255,0,1,0,0,0,2,123,123,123,10,200,200,200,1,123,123,0,12,255,255,0,4,0,0,0,10,255,255,0,
+ 1,189,189,189,1,0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,12,255,255,0,1,189,189,189,2,0,0,0,1,189,189,189,11,
+ 255,255,0,1,0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,27,255,255,0,1,0,0,0,3,123,123,123,8,200,200,200,1,123,
+ 123,0,26,255,255,0,1,189,189,189,1,0,0,0,3,123,123,123,9,200,200,200,1,123,123,0,24,255,255,0,1,189,189,189,1,0,0,
+ 0,4,123,123,123,10,200,200,200,1,123,123,0,24,0,0,0,5,123,123,123,12,200,200,200,27,123,123,123,14,200,200,200,25,
+ 123,123,123,86,200,200,200,91,49,124,118,124,71,32,124,95,49,56,114,52,82,121,0 };
+
+ //! Get/set default output stream for the \CImg library messages.
+ /**
+ \param file Desired output stream. Set to \c 0 to get the currently used output stream only.
+ \return Currently used output stream.
+ **/
+ inline std::FILE* output(std::FILE *file) {
+ cimg::mutex(1);
+ static std::FILE *res = cimg::_stderr();
+ if (file) res = file;
+ cimg::mutex(1,0);
+ return res;
+ }
+
+ // Return number of available CPU cores.
+ inline unsigned int nb_cpus() {
+ unsigned int res = 1;
+#if cimg_OS==2
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+ res = (unsigned int)sysinfo.dwNumberOfProcessors;
+#elif cimg_OS == 1
+ res = (unsigned int)sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+ return res?res:1U;
+ }
+
+ // Lock/unlock mutex for CImg multi-thread programming.
+ inline int mutex(const unsigned int n, const int lock_mode) {
+ switch (lock_mode) {
+ case 0 : cimg::Mutex_attr().unlock(n); return 0;
+ case 1 : cimg::Mutex_attr().lock(n); return 0;
+ default : return cimg::Mutex_attr().trylock(n);
+ }
+ }
+
+ //! Display a warning message on the default output stream.
+ /**
+ \param format C-string containing the format of the message, as with <tt>std::printf()</tt>.
+ \note If configuration macro \c cimg_strict_warnings is set, this function throws a
+ \c CImgWarningException instead.
+ \warning As the first argument is a format string, it is highly recommended to write
+ \code
+ cimg::warn("%s",warning_message);
+ \endcode
+ instead of
+ \code
+ cimg::warn(warning_message);
+ \endcode
+ if \c warning_message can be arbitrary, to prevent nasty memory access.
+ **/
+ inline void warn(const char *const format, ...) {
+ if (cimg::exception_mode()>=1) {
+ char *const message = new char[16384];
+ std::va_list ap;
+ va_start(ap,format);
+ cimg_vsnprintf(message,16384,format,ap);
+ va_end(ap);
+#ifdef cimg_strict_warnings
+ throw CImgWarningException(message);
+#else
+ std::fprintf(cimg::output(),"\n%s[CImg] *** Warning ***%s%s\n",cimg::t_red,cimg::t_normal,message);
+#endif
+ delete[] message;
+ }
+ }
+
+ // Execute an external system command.
+ /**
+ \param command C-string containing the command line to execute.
+ \param module_name Module name.
+ \return Status value of the executed command, whose meaning is OS-dependent.
+ \note This function is similar to <tt>std::system()</tt>
+ but it does not open an extra console windows
+ on Windows-based systems.
+ **/
+ inline int system(const char *const command, const char *const module_name=0) {
+ cimg::unused(module_name);
+#ifdef cimg_no_system_calls
+ return -1;
+#else
+#if cimg_OS==1
+ const unsigned int l = (unsigned int)std::strlen(command);
+ if (l) {
+ char *const ncommand = new char[l + 16];
+ std::strncpy(ncommand,command,l);
+ std::strcpy(ncommand + l," 2> /dev/null"); // Make command silent.
+ const int out_val = std::system(ncommand);
+ delete[] ncommand;
+ return out_val;
+ } else return -1;
+#elif cimg_OS==2
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si;
+ std::memset(&pi,0,sizeof(PROCESS_INFORMATION));
+ std::memset(&si,0,sizeof(STARTUPINFO));
+ GetStartupInfo(&si);
+ si.cb = sizeof(si);
+ si.wShowWindow = SW_HIDE;
+ si.dwFlags |= SW_HIDE | STARTF_USESHOWWINDOW;
+ const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi);
+ if (res) {
+ WaitForSingleObject(pi.hProcess,INFINITE);
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+ return 0;
+ } else return std::system(command);
+#else
+ return std::system(command);
+#endif
+#endif
+ }
+
+ //! Return a reference to a temporary variable of type T.
+ template<typename T>
+ inline T& temporary(const T&) {
+ static T temp;
+ return temp;
+ }
+
+ //! Exchange values of variables \c a and \c b.
+ template<typename T>
+ inline void swap(T& a, T& b) { T t = a; a = b; b = t; }
+
+ //! Exchange values of variables (\c a1,\c a2) and (\c b1,\c b2).
+ template<typename T1, typename T2>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) {
+ cimg::swap(a1,b1); cimg::swap(a2,b2);
+ }
+
+ //! Exchange values of variables (\c a1,\c a2,\c a3) and (\c b1,\c b2,\c b3).
+ template<typename T1, typename T2, typename T3>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) {
+ cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3);
+ }
+
+ //! Exchange values of variables (\c a1,\c a2,...,\c a4) and (\c b1,\c b2,...,\c b4).
+ template<typename T1, typename T2, typename T3, typename T4>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) {
+ cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4);
+ }
+
+ //! Exchange values of variables (\c a1,\c a2,...,\c a5) and (\c b1,\c b2,...,\c b5).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5);
+ }
+
+ //! Exchange values of variables (\c a1,\c a2,...,\c a6) and (\c b1,\c b2,...,\c b6).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6);
+ }
+
+ //! Exchange values of variables (\c a1,\c a2,...,\c a7) and (\c b1,\c b2,...,\c b7).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
+ T7& a7, T7& b7) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7);
+ }
+
+ //! Exchange values of variables (\c a1,\c a2,...,\c a8) and (\c b1,\c b2,...,\c b8).
+ template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
+ inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
+ T7& a7, T7& b7, T8& a8, T8& b8) {
+ cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8);
+ }
+
+ //! Return the endianness of the current architecture.
+ /**
+ \return \c false for <i>Little Endian</i> or \c true for <i>Big Endian</i>.
+ **/
+ inline bool endianness() {
+ const int x = 1;
+ return ((unsigned char*)&x)[0]?false:true;
+ }
+
+ //! Reverse endianness of all elements in a memory buffer.
+ /**
+ \param[in,out] buffer Memory buffer whose endianness must be reversed.
+ \param size Number of buffer elements to reverse.
+ **/
+ template<typename T>
+ inline void invert_endianness(T* const buffer, const cimg_ulong size) {
+ if (size) switch (sizeof(T)) {
+ case 1 : break;
+ case 2 : { for (unsigned short *ptr = (unsigned short*)buffer + size; ptr>(unsigned short*)buffer; ) {
+ const unsigned short val = *(--ptr);
+ *ptr = (unsigned short)((val>>8)|((val<<8)));
+ }
+ } break;
+ case 4 : { for (unsigned int *ptr = (unsigned int*)buffer + size; ptr>(unsigned int*)buffer; ) {
+ const unsigned int val = *(--ptr);
+ *ptr = (val>>24)|((val>>8)&0xff00)|((val<<8)&0xff0000)|(val<<24);
+ }
+ } break;
+ default : { for (T* ptr = buffer + size; ptr>buffer; ) {
+ unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T);
+ for (int i = 0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe));
+ }
+ }
+ }
+ }
+
+ //! Reverse endianness of a single variable.
+ /**
+ \param[in,out] a Variable to reverse.
+ \return Reference to reversed variable.
+ **/
+ template<typename T>
+ inline T& invert_endianness(T& a) {
+ invert_endianness(&a,1);
+ return a;
+ }
+
+ // Conversion functions to get more precision when trying to store unsigned ints values as floats.
+ inline unsigned int float2uint(const float f) {
+ int tmp = 0;
+ std::memcpy(&tmp,&f,sizeof(float));
+ if (tmp>=0) return (unsigned int)f;
+ unsigned int u;
+ // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler.
+ std::memcpy(&u,&f,sizeof(float));
+ return ((u)<<1)>>1; // set sign bit to 0.
+ }
+
+ inline float uint2float(const unsigned int u) {
+ if (u<(1U<<19)) return (float)u; // Consider safe storage of unsigned int as floats until 19bits (i.e 524287).
+ float f;
+ const unsigned int v = u|(1U<<(8*sizeof(unsigned int)-1)); // set sign bit to 1.
+ // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler.
+ std::memcpy(&f,&v,sizeof(float));
+ return f;
+ }
+
+ //! Return the value of a system timer, with a millisecond precision.
+ /**
+ \note The timer does not necessarily starts from \c 0.
+ **/
+ inline cimg_ulong time() {
+#if cimg_OS==1
+ struct timeval st_time;
+ gettimeofday(&st_time,0);
+ return (cimg_ulong)(st_time.tv_usec/1000 + st_time.tv_sec*1000);
+#elif cimg_OS==2
+ SYSTEMTIME st_time;
+ GetLocalTime(&st_time);
+ return (cimg_ulong)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour)));
+#else
+ return 0;
+#endif
+ }
+
+ // Implement a tic/toc mechanism to display elapsed time of algorithms.
+ inline cimg_ulong tictoc(const bool is_tic);
+
+ //! Start tic/toc timer for time measurement between code instructions.
+ /**
+ \return Current value of the timer (same value as time()).
+ **/
+ inline cimg_ulong tic() {
+ return cimg::tictoc(true);
+ }
+
+ //! End tic/toc timer and displays elapsed time from last call to tic().
+ /**
+ \return Time elapsed (in ms) since last call to tic().
+ **/
+ inline cimg_ulong toc() {
+ return cimg::tictoc(false);
+ }
+
+ //! Sleep for a given numbers of milliseconds.
+ /**
+ \param milliseconds Number of milliseconds to wait for.
+ \note This function frees the CPU ressources during the sleeping time.
+ It can be used to temporize your program properly, without wasting CPU time.
+ **/
+ 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);
+#else
+ cimg::unused(milliseconds);
+#endif
+ }
+
+ inline unsigned int _wait(const unsigned int milliseconds, cimg_ulong& timer) {
+ if (!timer) timer = cimg::time();
+ const cimg_ulong current_time = cimg::time();
+ if (current_time>=timer + milliseconds) { timer = current_time; return 0; }
+ const unsigned int time_diff = (unsigned int)(timer + milliseconds - current_time);
+ timer = current_time + time_diff;
+ cimg::sleep(time_diff);
+ return time_diff;
+ }
+
+ //! Wait for a given number of milliseconds since the last call to wait().
+ /**
+ \param milliseconds Number of milliseconds to wait for.
+ \return Number of milliseconds elapsed since the last call to wait().
+ \note Same as sleep() with a waiting time computed with regard to the last call
+ of wait(). It may be used to temporize your program properly, without wasting CPU time.
+ **/
+ inline cimg_long wait(const unsigned int milliseconds) {
+ cimg::mutex(3);
+ static cimg_ulong timer = 0;
+ if (!timer) timer = cimg::time();
+ cimg::mutex(3,0);
+ return _wait(milliseconds,timer);
+ }
+
+ // Random number generators.
+ // CImg may use its own Random Number Generator (RNG) if configuration macro 'cimg_use_rng' is set.
+ // Use it for instance when you have to deal with concurrent threads trying to call std::srand()
+ // at the same time!
+#ifdef cimg_use_rng
+
+#include <stdint.h>
+
+ // Use a custom RNG.
+ inline unsigned int _rand(const unsigned int seed=0, const bool set_seed=false) {
+ static cimg_ulong next = 0xB16B00B5;
+ cimg::mutex(4);
+ if (set_seed) next = (cimg_ulong)seed;
+ next = next*1103515245 + 12345U;
+ cimg::mutex(4,0);
+ return (unsigned int)(next&0xFFFFFFU);
+ }
+
+ inline void srand() {
+ const unsigned int t = (unsigned int)cimg::time();
+#if cimg_OS==1
+ cimg::_rand(t + (unsigned int)getpid(),true);
+#elif cimg_OS==2
+ cimg::_rand(t + (unsigned int)_getpid(),true);
+#else
+ cimg::_rand(t,true);
+#endif
+ }
+
+ inline void srand(const unsigned int seed) {
+ _rand(seed,true);
+ }
+
+ inline double rand(const double val_min, const double val_max) {
+ const double val = cimg::_rand()/16777215.;
+ return val_min + (val_max - val_min)*val;
+ }
+
+#else
+
+ // Use the system RNG.
+ inline void srand() {
+ const unsigned int t = (unsigned int)cimg::time();
+#if cimg_OS==1 || defined(__BORLANDC__)
+ std::srand(t + (unsigned int)getpid());
+#elif cimg_OS==2
+ std::srand(t + (unsigned int)_getpid());
+#else
+ std::srand(t);
+#endif
+ }
+
+ inline void srand(const unsigned int seed) {
+ std::srand(seed);
+ }
+
+ //! Return a random variable uniformely distributed between [val_min,val_max].
+ /**
+ **/
+ inline double rand(const double val_min, const double val_max) {
+ const double val = (double)std::rand()/RAND_MAX;
+ return val_min + (val_max - val_min)*val;
+ }
+#endif
+
+ //! Return a random variable uniformely distributed between [0,val_max].
+ /**
+ **/
+ inline double rand(const double val_max=1) {
+ return cimg::rand(0,val_max);
+ }
+
+ //! Return a random variable following a gaussian distribution and a standard deviation of 1.
+ /**
+ **/
+ inline double grand() {
+ double x1, w;
+ do {
+ const double x2 = cimg::rand(-1,1);
+ x1 = cimg::rand(-1,1);
+ 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) 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;
+ }
+
+ //! Cut (i.e. clamp) value in specified interval.
+ template<typename T, typename t>
+ inline T cut(const T& val, const t& val_min, const t& val_max) {
+ return val<val_min?(T)val_min:val>val_max?(T)val_max:val;
+ }
+
+ //! Bitwise-rotate value on the left.
+ template<typename T>
+ inline T rol(const T& a, const unsigned int n=1) {
+ return n?(T)((a<<n)|(a>>((sizeof(T)<<3) - n))):a;
+ }
+
+ inline float rol(const float a, const unsigned int n=1) {
+ return (float)rol((int)a,n);
+ }
+
+ inline double rol(const double a, const unsigned int n=1) {
+ return (double)rol((cimg_long)a,n);
+ }
+
+ inline double rol(const long double a, const unsigned int n=1) {
+ return (double)rol((cimg_long)a,n);
+ }
+
+#ifdef cimg_use_half
+ inline half rol(const half a, const unsigned int n=1) {
+ return (half)rol((int)a,n);
+ }
+#endif
+
+ //! Bitwise-rotate value on the right.
+ template<typename T>
+ inline T ror(const T& a, const unsigned int n=1) {
+ return n?(T)((a>>n)|(a<<((sizeof(T)<<3) - n))):a;
+ }
+
+ inline float ror(const float a, const unsigned int n=1) {
+ return (float)ror((int)a,n);
+ }
+
+ inline double ror(const double a, const unsigned int n=1) {
+ return (double)ror((cimg_long)a,n);
+ }
+
+ inline double ror(const long double a, const unsigned int n=1) {
+ return (double)ror((cimg_long)a,n);
+ }
+
+#ifdef cimg_use_half
+ inline half ror(const half a, const unsigned int n=1) {
+ return (half)ror((int)a,n);
+ }
+#endif
+
+ //! Return absolute value of a value.
+ template<typename T>
+ inline T abs(const T& a) {
+ return a>=0?a:-a;
+ }
+ inline bool abs(const bool a) {
+ return a;
+ }
+ inline int abs(const unsigned char a) {
+ return (int)a;
+ }
+ inline int abs(const unsigned short a) {
+ return (int)a;
+ }
+ inline int abs(const unsigned int a) {
+ return (int)a;
+ }
+ inline int abs(const int a) {
+ return std::abs(a);
+ }
+ inline cimg_int64 abs(const cimg_uint64 a) {
+ return (cimg_int64)a;
+ }
+ inline double abs(const double a) {
+ return std::fabs(a);
+ }
+ inline float abs(const float a) {
+ return (float)std::fabs((double)a);
+ }
+
+ //! Return square of a value.
+ template<typename T>
+ inline T sqr(const T& val) {
+ return val*val;
+ }
+
+ //! Return <tt>1 + log_10(x)</tt> of a value \c x.
+ inline int xln(const int x) {
+ return x>0?(int)(1 + std::log10((double)x)):1;
+ }
+
+ //! Return the minimum between three values.
+ template<typename t>
+ inline t min(const t& a, const t& b, const t& c) {
+ return std::min(std::min(a,b),c);
+ }
+
+ //! Return the minimum between four values.
+ template<typename t>
+ inline t min(const t& a, const t& b, const t& c, const t& d) {
+ return std::min(std::min(a,b),std::min(c,d));
+ }
+
+ //! Return the maximum between three values.
+ template<typename t>
+ inline t max(const t& a, const t& b, const t& c) {
+ return std::max(std::max(a,b),c);
+ }
+
+ //! Return the maximum between four values.
+ template<typename t>
+ inline t max(const t& a, const t& b, const t& c, const t& d) {
+ return std::max(std::max(a,b),std::max(c,d));
+ }
+
+ //! Return the sign of a value.
+ template<typename T>
+ inline T sign(const T& x) {
+ return (T)(x<0?-1:x>0);
+ }
+
+ //! Return the nearest power of 2 higher than given value.
+ template<typename T>
+ inline cimg_ulong nearest_pow2(const T& x) {
+ cimg_ulong i = 1;
+ while (x>i) i<<=1;
+ return i;
+ }
+
+ //! Return the sinc of a given value.
+ inline double sinc(const double x) {
+ return x?std::sin(x)/x:1;
+ }
+
+ //! Return the modulo of a value.
+ /**
+ \param x Input value.
+ \param m Modulo value.
+ \note This modulo function accepts negative and floating-points modulo numbers, as well as variables of any type.
+ **/
+ template<typename T>
+ inline T mod(const T& x, const T& m) {
+ const double dx = (double)x, dm = (double)m;
+ return (T)(dx - dm * std::floor(dx / dm));
+ }
+ inline int mod(const bool x, const bool m) {
+ return m?(x?1:0):0;
+ }
+ inline int mod(const unsigned char x, const unsigned char m) {
+ return x%m;
+ }
+ inline int mod(const char x, const char m) {
+#if defined(CHAR_MAX) && CHAR_MAX==255
+ return x%m;
+#else
+ return x>=0?x%m:(x%m?m + x%m:0);
+#endif
+ }
+ inline int mod(const unsigned short x, const unsigned short m) {
+ return x%m;
+ }
+ inline int mod(const short x, const short m) {
+ return x>=0?x%m:(x%m?m + x%m:0);
+ }
+ inline int mod(const unsigned int x, const unsigned int m) {
+ return (int)(x%m);
+ }
+ inline int mod(const int x, const int m) {
+ return x>=0?x%m:(x%m?m + x%m:0);
+ }
+ inline cimg_int64 mod(const cimg_uint64 x, const cimg_uint64 m) {
+ return x%m;
+ }
+ inline cimg_int64 mod(const cimg_int64 x, const cimg_int64 m) {
+ return x>=0?x%m:(x%m?m + x%m:0);
+ }
+
+ //! Return the min-mod of two values.
+ /**
+ \note <i>minmod(\p a,\p b)</i> is defined to be:
+ - <i>minmod(\p a,\p b) = min(\p a,\p b)</i>, if \p a and \p b have the same sign.
+ - <i>minmod(\p a,\p b) = 0</i>, if \p a and \p b have different signs.
+ **/
+ template<typename T>
+ inline T minmod(const T& a, const T& b) {
+ return a*b<=0?0:(a>0?(a<b?a:b):(a<b?b:a));
+ }
+
+ //! Return base-2 logarithm of a value.
+ inline double log2(const double x) {
+ const double base = std::log(2.0);
+ return std::log(x)/base;
+ }
+
+ template<typename T>
+ inline T round(const T& x) {
+ return (T)std::floor((_cimg_Tfloat)x + 0.5f);
+ }
+
+ //! Return rounded value.
+ /**
+ \param x Value to be rounded.
+ \param y Rounding precision.
+ \param rounding_type Type of rounding operation (\c 0 = nearest, \c -1 = backward, \c 1 = forward).
+ \return Rounded value, having the same type as input value \c x.
+ **/
+ template<typename T>
+ inline T round(const T& x, const double y, const int rounding_type=0) {
+ if (y<=0) return x;
+ if (y==1) switch (rounding_type) {
+ case 0 : return round(x);
+ case 1 : return (T)std::ceil((_cimg_Tfloat)x);
+ default : return (T)std::floor((_cimg_Tfloat)x);
+ }
+ const double sx = (double)x/y, floor = std::floor(sx), delta = sx - floor;
+ return (T)(y*(rounding_type<0?floor:rounding_type>0?std::ceil(sx):delta<0.5?floor:std::ceil(sx)));
+ }
+
+ //! Return x^(1/3).
+ template<typename T>
+ inline double cbrt(const T& x) {
+#if cimg_use_cpp11==1
+ return std::cbrt(x);
+#else
+ return x>=0?std::pow((double)x,1.0/3):-std::pow(-(double)x,1.0/3);
+#endif
+ }
+
+ // Code to compute fast median from 2,3,5,7,9,13,25 and 49 values.
+ // (contribution by RawTherapee: http://rawtherapee.com/).
+ template<typename T>
+ inline T median(T val0, T val1) {
+ return (val0 + val1)/2;
+ }
+
+ template<typename T>
+ inline T median(T val0, T val1, T val2) {
+ return std::max(std::min(val0,val1),std::min(val2,std::max(val0,val1)));
+ }
+
+ template<typename T>
+ inline T median(T val0, T val1, T val2, T val3, T val4) {
+ T tmp = std::min(val0,val1);
+ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4);
+ val3 = std::max(val0,tmp); val1 = std::min(val1,val4); tmp = std::min(val1,val2); val2 = std::max(val1,val2);
+ val1 = tmp; tmp = std::min(val2,val3);
+ return std::max(val1,tmp);
+ }
+
+ template<typename T>
+ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6) {
+ T tmp = std::min(val0,val5);
+ val5 = std::max(val0,val5); val0 = tmp; tmp = std::min(val0,val3); val3 = std::max(val0,val3); val0 = tmp;
+ tmp = std::min(val1,val6); val6 = std::max(val1,val6); val1 = tmp; tmp = std::min(val2,val4);
+ val4 = std::max(val2,val4); val2 = tmp; val1 = std::max(val0,val1); tmp = std::min(val3,val5);
+ val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6);
+ val3 = std::max(tmp,val3); val3 = std::min(val3,val6); tmp = std::min(val4,val5); val4 = std::max(val1,tmp);
+ tmp = std::min(val1,tmp); val3 = std::max(tmp,val3);
+ return std::min(val3,val4);
+ }
+
+ template<typename T>
+ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8) {
+ T tmp = std::min(val1,val2);
+ val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5);
+ val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8);
+ val8 = std::max(val7,val8); val7 = tmp; tmp = std::min(val0,val1);
+ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4);
+ val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val6,val7);
+ val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val1,val2);
+ val2 = std::max(val1,val2); val1 = tmp; tmp = std::min(val4,val5);
+ val5 = std::max(val4,val5); val4 = tmp; tmp = std::min(val7,val8);
+ val8 = std::max(val7,val8); val3 = std::max(val0,val3); val5 = std::min(val5,val8);
+ val7 = std::max(val4,tmp); tmp = std::min(val4,tmp); val6 = std::max(val3,val6);
+ val4 = std::max(val1,tmp); val2 = std::min(val2,val5); val4 = std::min(val4,val7);
+ tmp = std::min(val4,val2); val2 = std::max(val4,val2); val4 = std::max(val6,tmp);
+ return std::min(val4,val2);
+ }
+
+ template<typename T>
+ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6, T val7, T val8, T val9, T val10, T val11,
+ T val12) {
+ T tmp = std::min(val1,val7);
+ val7 = std::max(val1,val7); val1 = tmp; tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp;
+ tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp; tmp = std::min(val5,val8);
+ val8 = std::max(val5,val8); val5 = tmp; tmp = std::min(val0,val12); val12 = std::max(val0,val12);
+ val0 = tmp; tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val0,val1);
+ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val2,val3); val3 = std::max(val2,val3); val2 = tmp;
+ tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val8,val11);
+ val11 = std::max(val8,val11); val8 = tmp; tmp = std::min(val7,val12); val12 = std::max(val7,val12); val7 = tmp;
+ tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val0,val2);
+ val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp;
+ tmp = std::min(val10,val11); val11 = std::max(val10,val11); val10 = tmp; tmp = std::min(val1,val4);
+ val4 = std::max(val1,val4); val1 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp;
+ tmp = std::min(val7,val8); val8 = std::max(val7,val8); val7 = tmp; val11 = std::min(val11,val12);
+ tmp = std::min(val4,val9); val9 = std::max(val4,val9); val4 = tmp; tmp = std::min(val6,val10);
+ val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4); val3 = tmp;
+ tmp = std::min(val5,val6); val6 = std::max(val5,val6); val5 = tmp; val8 = std::min(val8,val9);
+ val10 = std::min(val10,val11); tmp = std::min(val1,val7); val7 = std::max(val1,val7); val1 = tmp;
+ tmp = std::min(val2,val6); val6 = std::max(val2,val6); val2 = tmp; val3 = std::max(val1,val3);
+ tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; val8 = std::min(val8,val10);
+ val5 = std::max(val0,val5); val5 = std::max(val2,val5); tmp = std::min(val6,val8); val8 = std::max(val6,val8);
+ val5 = std::max(val3,val5); val7 = std::min(val7,val8); val6 = std::max(val4,tmp); tmp = std::min(val4,tmp);
+ val5 = std::max(tmp,val5); val6 = std::min(val6,val7);
+ return std::max(val5,val6);
+ }
+
+ template<typename T>
+ inline T median(T val0, T val1, T val2, T val3, T val4,
+ T val5, T val6, T val7, T val8, T val9,
+ T val10, T val11, T val12, T val13, T val14,
+ T val15, T val16, T val17, T val18, T val19,
+ T val20, T val21, T val22, T val23, T val24) {
+ T tmp = std::min(val0,val1);
+ val1 = std::max(val0,val1); val0 = tmp; tmp = std::min(val3,val4); val4 = std::max(val3,val4);
+ val3 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4); val2 = std::min(tmp,val3);
+ val3 = std::max(tmp,val3); tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp;
+ tmp = std::min(val5,val7); val7 = std::max(val5,val7); val5 = std::min(tmp,val6); val6 = std::max(tmp,val6);
+ tmp = std::min(val9,val10); val10 = std::max(val9,val10); val9 = tmp; tmp = std::min(val8,val10);
+ val10 = std::max(val8,val10); val8 = std::min(tmp,val9); val9 = std::max(tmp,val9);
+ tmp = std::min(val12,val13); val13 = std::max(val12,val13); val12 = tmp; tmp = std::min(val11,val13);
+ val13 = std::max(val11,val13); val11 = std::min(tmp,val12); val12 = std::max(tmp,val12);
+ tmp = std::min(val15,val16); val16 = std::max(val15,val16); val15 = tmp; tmp = std::min(val14,val16);
+ val16 = std::max(val14,val16); val14 = std::min(tmp,val15); val15 = std::max(tmp,val15);
+ tmp = std::min(val18,val19); val19 = std::max(val18,val19); val18 = tmp; tmp = std::min(val17,val19);
+ val19 = std::max(val17,val19); val17 = std::min(tmp,val18); val18 = std::max(tmp,val18);
+ tmp = std::min(val21,val22); val22 = std::max(val21,val22); val21 = tmp; tmp = std::min(val20,val22);
+ val22 = std::max(val20,val22); val20 = std::min(tmp,val21); val21 = std::max(tmp,val21);
+ tmp = std::min(val23,val24); val24 = std::max(val23,val24); val23 = tmp; tmp = std::min(val2,val5);
+ val5 = std::max(val2,val5); val2 = tmp; tmp = std::min(val3,val6); val6 = std::max(val3,val6); val3 = tmp;
+ tmp = std::min(val0,val6); val6 = std::max(val0,val6); val0 = std::min(tmp,val3); val3 = std::max(tmp,val3);
+ tmp = std::min(val4,val7); val7 = std::max(val4,val7); val4 = tmp; tmp = std::min(val1,val7);
+ val7 = std::max(val1,val7); val1 = std::min(tmp,val4); val4 = std::max(tmp,val4); tmp = std::min(val11,val14);
+ val14 = std::max(val11,val14); val11 = tmp; tmp = std::min(val8,val14); val14 = std::max(val8,val14);
+ val8 = std::min(tmp,val11); val11 = std::max(tmp,val11); tmp = std::min(val12,val15);
+ val15 = std::max(val12,val15); val12 = tmp; tmp = std::min(val9,val15); val15 = std::max(val9,val15);
+ val9 = std::min(tmp,val12); val12 = std::max(tmp,val12); tmp = std::min(val13,val16);
+ val16 = std::max(val13,val16); val13 = tmp; tmp = std::min(val10,val16); val16 = std::max(val10,val16);
+ val10 = std::min(tmp,val13); val13 = std::max(tmp,val13); tmp = std::min(val20,val23);
+ val23 = std::max(val20,val23); val20 = tmp; tmp = std::min(val17,val23); val23 = std::max(val17,val23);
+ val17 = std::min(tmp,val20); val20 = std::max(tmp,val20); tmp = std::min(val21,val24);
+ val24 = std::max(val21,val24); val21 = tmp; tmp = std::min(val18,val24); val24 = std::max(val18,val24);
+ val18 = std::min(tmp,val21); val21 = std::max(tmp,val21); tmp = std::min(val19,val22);
+ val22 = std::max(val19,val22); val19 = tmp; val17 = std::max(val8,val17); tmp = std::min(val9,val18);
+ val18 = std::max(val9,val18); val9 = tmp; tmp = std::min(val0,val18); val18 = std::max(val0,val18);
+ val9 = std::max(tmp,val9); tmp = std::min(val10,val19); val19 = std::max(val10,val19); val10 = tmp;
+ tmp = std::min(val1,val19); val19 = std::max(val1,val19); val1 = std::min(tmp,val10);
+ val10 = std::max(tmp,val10); tmp = std::min(val11,val20); val20 = std::max(val11,val20); val11 = tmp;
+ tmp = std::min(val2,val20); val20 = std::max(val2,val20); val11 = std::max(tmp,val11);
+ tmp = std::min(val12,val21); val21 = std::max(val12,val21); val12 = tmp; tmp = std::min(val3,val21);
+ val21 = std::max(val3,val21); val3 = std::min(tmp,val12); val12 = std::max(tmp,val12);
+ tmp = std::min(val13,val22); val22 = std::max(val13,val22); val4 = std::min(val4,val22);
+ val13 = std::max(val4,tmp); tmp = std::min(val4,tmp); val4 = tmp; tmp = std::min(val14,val23);
+ val23 = std::max(val14,val23); val14 = tmp; tmp = std::min(val5,val23); val23 = std::max(val5,val23);
+ val5 = std::min(tmp,val14); val14 = std::max(tmp,val14); tmp = std::min(val15,val24);
+ val24 = std::max(val15,val24); val15 = tmp; val6 = std::min(val6,val24); tmp = std::min(val6,val15);
+ val15 = std::max(val6,val15); val6 = tmp; tmp = std::min(val7,val16); val7 = std::min(tmp,val19);
+ tmp = std::min(val13,val21); val15 = std::min(val15,val23); tmp = std::min(val7,tmp);
+ val7 = std::min(tmp,val15); val9 = std::max(val1,val9); val11 = std::max(val3,val11);
+ val17 = std::max(val5,val17); val17 = std::max(val11,val17); val17 = std::max(val9,val17);
+ tmp = std::min(val4,val10); val10 = std::max(val4,val10); val4 = tmp; tmp = std::min(val6,val12);
+ val12 = std::max(val6,val12); val6 = tmp; tmp = std::min(val7,val14); val14 = std::max(val7,val14);
+ val7 = tmp; tmp = std::min(val4,val6); val6 = std::max(val4,val6); val7 = std::max(tmp,val7);
+ tmp = std::min(val12,val14); val14 = std::max(val12,val14); val12 = tmp; val10 = std::min(val10,val14);
+ tmp = std::min(val6,val7); val7 = std::max(val6,val7); val6 = tmp; tmp = std::min(val10,val12);
+ val12 = std::max(val10,val12); val10 = std::max(val6,tmp); tmp = std::min(val6,tmp);
+ val17 = std::max(tmp,val17); tmp = std::min(val12,val17); val17 = std::max(val12,val17); val12 = tmp;
+ val7 = std::min(val7,val17); tmp = std::min(val7,val10); val10 = std::max(val7,val10); val7 = tmp;
+ tmp = std::min(val12,val18); val18 = std::max(val12,val18); val12 = std::max(val7,tmp);
+ val10 = std::min(val10,val18); tmp = std::min(val12,val20); val20 = std::max(val12,val20); val12 = tmp;
+ tmp = std::min(val10,val20);
+ return std::max(tmp,val12);
+ }
+
+ template<typename T>
+ inline T median(T val0, T val1, T val2, T val3, T val4, T val5, T val6,
+ T val7, T val8, T val9, T val10, T val11, T val12, T val13,
+ T val14, T val15, T val16, T val17, T val18, T val19, T val20,
+ T val21, T val22, T val23, T val24, T val25, T val26, T val27,
+ T val28, T val29, T val30, T val31, T val32, T val33, T val34,
+ T val35, T val36, T val37, T val38, T val39, T val40, T val41,
+ T val42, T val43, T val44, T val45, T val46, T val47, T val48) {
+ T tmp = std::min(val0,val32);
+ val32 = std::max(val0,val32); val0 = tmp; tmp = std::min(val1,val33); val33 = std::max(val1,val33); val1 = tmp;
+ tmp = std::min(val2,val34); val34 = std::max(val2,val34); val2 = tmp; tmp = std::min(val3,val35);
+ val35 = std::max(val3,val35); val3 = tmp; tmp = std::min(val4,val36); val36 = std::max(val4,val36); val4 = tmp;
+ tmp = std::min(val5,val37); val37 = std::max(val5,val37); val5 = tmp; tmp = std::min(val6,val38);
+ val38 = std::max(val6,val38); val6 = tmp; tmp = std::min(val7,val39); val39 = std::max(val7,val39); val7 = tmp;
+ tmp = std::min(val8,val40); val40 = std::max(val8,val40); val8 = tmp; tmp = std::min(val9,val41);
+ val41 = std::max(val9,val41); val9 = tmp; tmp = std::min(val10,val42); val42 = std::max(val10,val42);
+ val10 = tmp; tmp = std::min(val11,val43); val43 = std::max(val11,val43); val11 = tmp;
+ tmp = std::min(val12,val44); val44 = std::max(val12,val44); val12 = tmp; tmp = std::min(val13,val45);
+ val45 = std::max(val13,val45); val13 = tmp; tmp = std::min(val14,val46); val46 = std::max(val14,val46);
+ val14 = tmp; tmp = std::min(val15,val47); val47 = std::max(val15,val47); val15 = tmp;
+ tmp = std::min(val16,val48); val48 = std::max(val16,val48); val16 = tmp; tmp = std::min(val0,val16);
+ val16 = std::max(val0,val16); val0 = tmp; tmp = std::min(val1,val17); val17 = std::max(val1,val17);
+ val1 = tmp; tmp = std::min(val2,val18); val18 = std::max(val2,val18); val2 = tmp; tmp = std::min(val3,val19);
+ val19 = std::max(val3,val19); val3 = tmp; tmp = std::min(val4,val20); val20 = std::max(val4,val20); val4 = tmp;
+ tmp = std::min(val5,val21); val21 = std::max(val5,val21); val5 = tmp; tmp = std::min(val6,val22);
+ val22 = std::max(val6,val22); val6 = tmp; tmp = std::min(val7,val23); val23 = std::max(val7,val23); val7 = tmp;
+ tmp = std::min(val8,val24); val24 = std::max(val8,val24); val8 = tmp; tmp = std::min(val9,val25);
+ val25 = std::max(val9,val25); val9 = tmp; tmp = std::min(val10,val26); val26 = std::max(val10,val26);
+ val10 = tmp; tmp = std::min(val11,val27); val27 = std::max(val11,val27); val11 = tmp;
+ tmp = std::min(val12,val28); val28 = std::max(val12,val28); val12 = tmp; tmp = std::min(val13,val29);
+ val29 = std::max(val13,val29); val13 = tmp; tmp = std::min(val14,val30); val30 = std::max(val14,val30);
+ val14 = tmp; tmp = std::min(val15,val31); val31 = std::max(val15,val31); val15 = tmp;
+ tmp = std::min(val32,val48); val48 = std::max(val32,val48); val32 = tmp; tmp = std::min(val16,val32);
+ val32 = std::max(val16,val32); val16 = tmp; tmp = std::min(val17,val33); val33 = std::max(val17,val33);
+ val17 = tmp; tmp = std::min(val18,val34); val34 = std::max(val18,val34); val18 = tmp;
+ tmp = std::min(val19,val35); val35 = std::max(val19,val35); val19 = tmp; tmp = std::min(val20,val36);
+ val36 = std::max(val20,val36); val20 = tmp; tmp = std::min(val21,val37); val37 = std::max(val21,val37);
+ val21 = tmp; tmp = std::min(val22,val38); val38 = std::max(val22,val38); val22 = tmp;
+ tmp = std::min(val23,val39); val39 = std::max(val23,val39); val23 = tmp; tmp = std::min(val24,val40);
+ val40 = std::max(val24,val40); val24 = tmp; tmp = std::min(val25,val41); val41 = std::max(val25,val41);
+ val25 = tmp; tmp = std::min(val26,val42); val42 = std::max(val26,val42); val26 = tmp;
+ tmp = std::min(val27,val43); val43 = std::max(val27,val43); val27 = tmp; tmp = std::min(val28,val44);
+ val44 = std::max(val28,val44); val28 = tmp; tmp = std::min(val29,val45); val45 = std::max(val29,val45);
+ val29 = tmp; tmp = std::min(val30,val46); val46 = std::max(val30,val46); val30 = tmp;
+ tmp = std::min(val31,val47); val47 = std::max(val31,val47); val31 = tmp; tmp = std::min(val0,val8);
+ val8 = std::max(val0,val8); val0 = tmp; tmp = std::min(val1,val9); val9 = std::max(val1,val9); val1 = tmp;
+ tmp = std::min(val2,val10); val10 = std::max(val2,val10); val2 = tmp; tmp = std::min(val3,val11);
+ val11 = std::max(val3,val11); val3 = tmp; tmp = std::min(val4,val12); val12 = std::max(val4,val12); val4 = tmp;
+ tmp = std::min(val5,val13); val13 = std::max(val5,val13); val5 = tmp; tmp = std::min(val6,val14);
+ val14 = std::max(val6,val14); val6 = tmp; tmp = std::min(val7,val15); val15 = std::max(val7,val15); val7 = tmp;
+ tmp = std::min(val16,val24); val24 = std::max(val16,val24); val16 = tmp; tmp = std::min(val17,val25);
+ val25 = std::max(val17,val25); val17 = tmp; tmp = std::min(val18,val26); val26 = std::max(val18,val26);
+ val18 = tmp; tmp = std::min(val19,val27); val27 = std::max(val19,val27); val19 = tmp;
+ tmp = std::min(val20,val28); val28 = std::max(val20,val28); val20 = tmp; tmp = std::min(val21,val29);
+ val29 = std::max(val21,val29); val21 = tmp; tmp = std::min(val22,val30); val30 = std::max(val22,val30);
+ val22 = tmp; tmp = std::min(val23,val31); val31 = std::max(val23,val31); val23 = tmp;
+ tmp = std::min(val32,val40); val40 = std::max(val32,val40); val32 = tmp; tmp = std::min(val33,val41);
+ val41 = std::max(val33,val41); val33 = tmp; tmp = std::min(val34,val42); val42 = std::max(val34,val42);
+ val34 = tmp; tmp = std::min(val35,val43); val43 = std::max(val35,val43); val35 = tmp;
+ tmp = std::min(val36,val44); val44 = std::max(val36,val44); val36 = tmp; tmp = std::min(val37,val45);
+ val45 = std::max(val37,val45); val37 = tmp; tmp = std::min(val38,val46); val46 = std::max(val38,val46);
+ val38 = tmp; tmp = std::min(val39,val47); val47 = std::max(val39,val47); val39 = tmp;
+ tmp = std::min(val8,val32); val32 = std::max(val8,val32); val8 = tmp; tmp = std::min(val9,val33);
+ val33 = std::max(val9,val33); val9 = tmp; tmp = std::min(val10,val34); val34 = std::max(val10,val34);
+ val10 = tmp; tmp = std::min(val11,val35); val35 = std::max(val11,val35); val11 = tmp;
+ tmp = std::min(val12,val36); val36 = std::max(val12,val36); val12 = tmp; tmp = std::min(val13,val37);
+ val37 = std::max(val13,val37); val13 = tmp; tmp = std::min(val14,val38); val38 = std::max(val14,val38);
+ val14 = tmp; tmp = std::min(val15,val39); val39 = std::max(val15,val39); val15 = tmp;
+ tmp = std::min(val24,val48); val48 = std::max(val24,val48); val24 = tmp; tmp = std::min(val8,val16);
+ val16 = std::max(val8,val16); val8 = tmp; tmp = std::min(val9,val17); val17 = std::max(val9,val17);
+ val9 = tmp; tmp = std::min(val10,val18); val18 = std::max(val10,val18); val10 = tmp;
+ tmp = std::min(val11,val19); val19 = std::max(val11,val19); val11 = tmp; tmp = std::min(val12,val20);
+ val20 = std::max(val12,val20); val12 = tmp; tmp = std::min(val13,val21); val21 = std::max(val13,val21);
+ val13 = tmp; tmp = std::min(val14,val22); val22 = std::max(val14,val22); val14 = tmp;
+ tmp = std::min(val15,val23); val23 = std::max(val15,val23); val15 = tmp; tmp = std::min(val24,val32);
+ val32 = std::max(val24,val32); val24 = tmp; tmp = std::min(val25,val33); val33 = std::max(val25,val33);
+ val25 = tmp; tmp = std::min(val26,val34); val34 = std::max(val26,val34); val26 = tmp;
+ tmp = std::min(val27,val35); val35 = std::max(val27,val35); val27 = tmp; tmp = std::min(val28,val36);
+ val36 = std::max(val28,val36); val28 = tmp; tmp = std::min(val29,val37); val37 = std::max(val29,val37);
+ val29 = tmp; tmp = std::min(val30,val38); val38 = std::max(val30,val38); val30 = tmp;
+ tmp = std::min(val31,val39); val39 = std::max(val31,val39); val31 = tmp; tmp = std::min(val40,val48);
+ val48 = std::max(val40,val48); val40 = tmp; tmp = std::min(val0,val4); val4 = std::max(val0,val4);
+ val0 = tmp; tmp = std::min(val1,val5); val5 = std::max(val1,val5); val1 = tmp; tmp = std::min(val2,val6);
+ val6 = std::max(val2,val6); val2 = tmp; tmp = std::min(val3,val7); val7 = std::max(val3,val7); val3 = tmp;
+ tmp = std::min(val8,val12); val12 = std::max(val8,val12); val8 = tmp; tmp = std::min(val9,val13);
+ val13 = std::max(val9,val13); val9 = tmp; tmp = std::min(val10,val14); val14 = std::max(val10,val14);
+ val10 = tmp; tmp = std::min(val11,val15); val15 = std::max(val11,val15); val11 = tmp;
+ tmp = std::min(val16,val20); val20 = std::max(val16,val20); val16 = tmp; tmp = std::min(val17,val21);
+ val21 = std::max(val17,val21); val17 = tmp; tmp = std::min(val18,val22); val22 = std::max(val18,val22);
+ val18 = tmp; tmp = std::min(val19,val23); val23 = std::max(val19,val23); val19 = tmp;
+ tmp = std::min(val24,val28); val28 = std::max(val24,val28); val24 = tmp; tmp = std::min(val25,val29);
+ val29 = std::max(val25,val29); val25 = tmp; tmp = std::min(val26,val30); val30 = std::max(val26,val30);
+ val26 = tmp; tmp = std::min(val27,val31); val31 = std::max(val27,val31); val27 = tmp;
+ tmp = std::min(val32,val36); val36 = std::max(val32,val36); val32 = tmp; tmp = std::min(val33,val37);
+ val37 = std::max(val33,val37); val33 = tmp; tmp = std::min(val34,val38); val38 = std::max(val34,val38);
+ val34 = tmp; tmp = std::min(val35,val39); val39 = std::max(val35,val39); val35 = tmp;
+ tmp = std::min(val40,val44); val44 = std::max(val40,val44); val40 = tmp; tmp = std::min(val41,val45);
+ val45 = std::max(val41,val45); val41 = tmp; tmp = std::min(val42,val46); val46 = std::max(val42,val46);
+ val42 = tmp; tmp = std::min(val43,val47); val47 = std::max(val43,val47); val43 = tmp;
+ tmp = std::min(val4,val32); val32 = std::max(val4,val32); val4 = tmp; tmp = std::min(val5,val33);
+ val33 = std::max(val5,val33); val5 = tmp; tmp = std::min(val6,val34); val34 = std::max(val6,val34);
+ val6 = tmp; tmp = std::min(val7,val35); val35 = std::max(val7,val35); val7 = tmp;
+ tmp = std::min(val12,val40); val40 = std::max(val12,val40); val12 = tmp; tmp = std::min(val13,val41);
+ val41 = std::max(val13,val41); val13 = tmp; tmp = std::min(val14,val42); val42 = std::max(val14,val42);
+ val14 = tmp; tmp = std::min(val15,val43); val43 = std::max(val15,val43); val15 = tmp;
+ tmp = std::min(val20,val48); val48 = std::max(val20,val48); val20 = tmp; tmp = std::min(val4,val16);
+ val16 = std::max(val4,val16); val4 = tmp; tmp = std::min(val5,val17); val17 = std::max(val5,val17);
+ val5 = tmp; tmp = std::min(val6,val18); val18 = std::max(val6,val18); val6 = tmp;
+ tmp = std::min(val7,val19); val19 = std::max(val7,val19); val7 = tmp; tmp = std::min(val12,val24);
+ val24 = std::max(val12,val24); val12 = tmp; tmp = std::min(val13,val25); val25 = std::max(val13,val25);
+ val13 = tmp; tmp = std::min(val14,val26); val26 = std::max(val14,val26); val14 = tmp;
+ tmp = std::min(val15,val27); val27 = std::max(val15,val27); val15 = tmp; tmp = std::min(val20,val32);
+ val32 = std::max(val20,val32); val20 = tmp; tmp = std::min(val21,val33); val33 = std::max(val21,val33);
+ val21 = tmp; tmp = std::min(val22,val34); val34 = std::max(val22,val34); val22 = tmp;
+ tmp = std::min(val23,val35); val35 = std::max(val23,val35); val23 = tmp; tmp = std::min(val28,val40);
+ val40 = std::max(val28,val40); val28 = tmp; tmp = std::min(val29,val41); val41 = std::max(val29,val41);
+ val29 = tmp; tmp = std::min(val30,val42); val42 = std::max(val30,val42); val30 = tmp;
+ tmp = std::min(val31,val43); val43 = std::max(val31,val43); val31 = tmp; tmp = std::min(val36,val48);
+ val48 = std::max(val36,val48); val36 = tmp; tmp = std::min(val4,val8); val8 = std::max(val4,val8);
+ val4 = tmp; tmp = std::min(val5,val9); val9 = std::max(val5,val9); val5 = tmp; tmp = std::min(val6,val10);
+ val10 = std::max(val6,val10); val6 = tmp; tmp = std::min(val7,val11); val11 = std::max(val7,val11); val7 = tmp;
+ tmp = std::min(val12,val16); val16 = std::max(val12,val16); val12 = tmp; tmp = std::min(val13,val17);
+ val17 = std::max(val13,val17); val13 = tmp; tmp = std::min(val14,val18); val18 = std::max(val14,val18);
+ val14 = tmp; tmp = std::min(val15,val19); val19 = std::max(val15,val19); val15 = tmp;
+ tmp = std::min(val20,val24); val24 = std::max(val20,val24); val20 = tmp; tmp = std::min(val21,val25);
+ val25 = std::max(val21,val25); val21 = tmp; tmp = std::min(val22,val26); val26 = std::max(val22,val26);
+ val22 = tmp; tmp = std::min(val23,val27); val27 = std::max(val23,val27); val23 = tmp;
+ tmp = std::min(val28,val32); val32 = std::max(val28,val32); val28 = tmp; tmp = std::min(val29,val33);
+ val33 = std::max(val29,val33); val29 = tmp; tmp = std::min(val30,val34); val34 = std::max(val30,val34);
+ val30 = tmp; tmp = std::min(val31,val35); val35 = std::max(val31,val35); val31 = tmp;
+ tmp = std::min(val36,val40); val40 = std::max(val36,val40); val36 = tmp; tmp = std::min(val37,val41);
+ val41 = std::max(val37,val41); val37 = tmp; tmp = std::min(val38,val42); val42 = std::max(val38,val42);
+ val38 = tmp; tmp = std::min(val39,val43); val43 = std::max(val39,val43); val39 = tmp;
+ tmp = std::min(val44,val48); val48 = std::max(val44,val48); val44 = tmp; tmp = std::min(val0,val2);
+ val2 = std::max(val0,val2); val0 = tmp; tmp = std::min(val1,val3); val3 = std::max(val1,val3); val1 = tmp;
+ tmp = std::min(val4,val6); val6 = std::max(val4,val6); val4 = tmp; tmp = std::min(val5,val7);
+ val7 = std::max(val5,val7); val5 = tmp; tmp = std::min(val8,val10); val10 = std::max(val8,val10); val8 = tmp;
+ tmp = std::min(val9,val11); val11 = std::max(val9,val11); val9 = tmp; tmp = std::min(val12,val14);
+ val14 = std::max(val12,val14); val12 = tmp; tmp = std::min(val13,val15); val15 = std::max(val13,val15);
+ val13 = tmp; tmp = std::min(val16,val18); val18 = std::max(val16,val18); val16 = tmp;
+ tmp = std::min(val17,val19); val19 = std::max(val17,val19); val17 = tmp; tmp = std::min(val20,val22);
+ val22 = std::max(val20,val22); val20 = tmp; tmp = std::min(val21,val23); val23 = std::max(val21,val23);
+ val21 = tmp; tmp = std::min(val24,val26); val26 = std::max(val24,val26); val24 = tmp;
+ tmp = std::min(val25,val27); val27 = std::max(val25,val27); val25 = tmp; tmp = std::min(val28,val30);
+ val30 = std::max(val28,val30); val28 = tmp; tmp = std::min(val29,val31); val31 = std::max(val29,val31);
+ val29 = tmp; tmp = std::min(val32,val34); val34 = std::max(val32,val34); val32 = tmp;
+ tmp = std::min(val33,val35); val35 = std::max(val33,val35); val33 = tmp; tmp = std::min(val36,val38);
+ val38 = std::max(val36,val38); val36 = tmp; tmp = std::min(val37,val39); val39 = std::max(val37,val39);
+ val37 = tmp; tmp = std::min(val40,val42); val42 = std::max(val40,val42); val40 = tmp;
+ tmp = std::min(val41,val43); val43 = std::max(val41,val43); val41 = tmp; tmp = std::min(val44,val46);
+ val46 = std::max(val44,val46); val44 = tmp; tmp = std::min(val45,val47); val47 = std::max(val45,val47);
+ val45 = tmp; tmp = std::min(val2,val32); val32 = std::max(val2,val32); val2 = tmp; tmp = std::min(val3,val33);
+ val33 = std::max(val3,val33); val3 = tmp; tmp = std::min(val6,val36); val36 = std::max(val6,val36); val6 = tmp;
+ tmp = std::min(val7,val37); val37 = std::max(val7,val37); val7 = tmp; tmp = std::min(val10,val40);
+ val40 = std::max(val10,val40); val10 = tmp; tmp = std::min(val11,val41); val41 = std::max(val11,val41);
+ val11 = tmp; tmp = std::min(val14,val44); val44 = std::max(val14,val44); val14 = tmp;
+ tmp = std::min(val15,val45); val45 = std::max(val15,val45); val15 = tmp; tmp = std::min(val18,val48);
+ val48 = std::max(val18,val48); val18 = tmp; tmp = std::min(val2,val16); val16 = std::max(val2,val16);
+ val2 = tmp; tmp = std::min(val3,val17); val17 = std::max(val3,val17); val3 = tmp;
+ tmp = std::min(val6,val20); val20 = std::max(val6,val20); val6 = tmp; tmp = std::min(val7,val21);
+ val21 = std::max(val7,val21); val7 = tmp; tmp = std::min(val10,val24); val24 = std::max(val10,val24);
+ val10 = tmp; tmp = std::min(val11,val25); val25 = std::max(val11,val25); val11 = tmp;
+ tmp = std::min(val14,val28); val28 = std::max(val14,val28); val14 = tmp; tmp = std::min(val15,val29);
+ val29 = std::max(val15,val29); val15 = tmp; tmp = std::min(val18,val32); val32 = std::max(val18,val32);
+ val18 = tmp; tmp = std::min(val19,val33); val33 = std::max(val19,val33); val19 = tmp;
+ tmp = std::min(val22,val36); val36 = std::max(val22,val36); val22 = tmp; tmp = std::min(val23,val37);
+ val37 = std::max(val23,val37); val23 = tmp; tmp = std::min(val26,val40); val40 = std::max(val26,val40);
+ val26 = tmp; tmp = std::min(val27,val41); val41 = std::max(val27,val41); val27 = tmp;
+ tmp = std::min(val30,val44); val44 = std::max(val30,val44); val30 = tmp; tmp = std::min(val31,val45);
+ val45 = std::max(val31,val45); val31 = tmp; tmp = std::min(val34,val48); val48 = std::max(val34,val48);
+ val34 = tmp; tmp = std::min(val2,val8); val8 = std::max(val2,val8); val2 = tmp; tmp = std::min(val3,val9);
+ val9 = std::max(val3,val9); val3 = tmp; tmp = std::min(val6,val12); val12 = std::max(val6,val12); val6 = tmp;
+ tmp = std::min(val7,val13); val13 = std::max(val7,val13); val7 = tmp; tmp = std::min(val10,val16);
+ val16 = std::max(val10,val16); val10 = tmp; tmp = std::min(val11,val17); val17 = std::max(val11,val17);
+ val11 = tmp; tmp = std::min(val14,val20); val20 = std::max(val14,val20); val14 = tmp;
+ tmp = std::min(val15,val21); val21 = std::max(val15,val21); val15 = tmp; tmp = std::min(val18,val24);
+ val24 = std::max(val18,val24); val18 = tmp; tmp = std::min(val19,val25); val25 = std::max(val19,val25);
+ val19 = tmp; tmp = std::min(val22,val28); val28 = std::max(val22,val28); val22 = tmp;
+ tmp = std::min(val23,val29); val29 = std::max(val23,val29); val23 = tmp; tmp = std::min(val26,val32);
+ val32 = std::max(val26,val32); val26 = tmp; tmp = std::min(val27,val33); val33 = std::max(val27,val33);
+ val27 = tmp; tmp = std::min(val30,val36); val36 = std::max(val30,val36); val30 = tmp;
+ tmp = std::min(val31,val37); val37 = std::max(val31,val37); val31 = tmp; tmp = std::min(val34,val40);
+ val40 = std::max(val34,val40); val34 = tmp; tmp = std::min(val35,val41); val41 = std::max(val35,val41);
+ val35 = tmp; tmp = std::min(val38,val44); val44 = std::max(val38,val44); val38 = tmp;
+ tmp = std::min(val39,val45); val45 = std::max(val39,val45); val39 = tmp; tmp = std::min(val42,val48);
+ val48 = std::max(val42,val48); val42 = tmp; tmp = std::min(val2,val4); val4 = std::max(val2,val4);
+ val2 = tmp; tmp = std::min(val3,val5); val5 = std::max(val3,val5); val3 = tmp; tmp = std::min(val6,val8);
+ val8 = std::max(val6,val8); val6 = tmp; tmp = std::min(val7,val9); val9 = std::max(val7,val9); val7 = tmp;
+ tmp = std::min(val10,val12); val12 = std::max(val10,val12); val10 = tmp; tmp = std::min(val11,val13);
+ val13 = std::max(val11,val13); val11 = tmp; tmp = std::min(val14,val16); val16 = std::max(val14,val16);
+ val14 = tmp; tmp = std::min(val15,val17); val17 = std::max(val15,val17); val15 = tmp;
+ tmp = std::min(val18,val20); val20 = std::max(val18,val20); val18 = tmp; tmp = std::min(val19,val21);
+ val21 = std::max(val19,val21); val19 = tmp; tmp = std::min(val22,val24); val24 = std::max(val22,val24);
+ val22 = tmp; tmp = std::min(val23,val25); val25 = std::max(val23,val25); val23 = tmp;
+ tmp = std::min(val26,val28); val28 = std::max(val26,val28); val26 = tmp; tmp = std::min(val27,val29);
+ val29 = std::max(val27,val29); val27 = tmp; tmp = std::min(val30,val32); val32 = std::max(val30,val32);
+ val30 = tmp; tmp = std::min(val31,val33); val33 = std::max(val31,val33); val31 = tmp;
+ tmp = std::min(val34,val36); val36 = std::max(val34,val36); val34 = tmp; tmp = std::min(val35,val37);
+ val37 = std::max(val35,val37); val35 = tmp; tmp = std::min(val38,val40); val40 = std::max(val38,val40);
+ val38 = tmp; tmp = std::min(val39,val41); val41 = std::max(val39,val41); val39 = tmp;
+ tmp = std::min(val42,val44); val44 = std::max(val42,val44); val42 = tmp; tmp = std::min(val43,val45);
+ val45 = std::max(val43,val45); val43 = tmp; tmp = std::min(val46,val48); val48 = std::max(val46,val48);
+ val46 = tmp; val1 = std::max(val0,val1); val3 = std::max(val2,val3); val5 = std::max(val4,val5);
+ val7 = std::max(val6,val7); val9 = std::max(val8,val9); val11 = std::max(val10,val11);
+ val13 = std::max(val12,val13); val15 = std::max(val14,val15); val17 = std::max(val16,val17);
+ val19 = std::max(val18,val19); val21 = std::max(val20,val21); val23 = std::max(val22,val23);
+ val24 = std::min(val24,val25); val26 = std::min(val26,val27); val28 = std::min(val28,val29);
+ val30 = std::min(val30,val31); val32 = std::min(val32,val33); val34 = std::min(val34,val35);
+ val36 = std::min(val36,val37); val38 = std::min(val38,val39); val40 = std::min(val40,val41);
+ val42 = std::min(val42,val43); val44 = std::min(val44,val45); val46 = std::min(val46,val47);
+ val32 = std::max(val1,val32); val34 = std::max(val3,val34); val36 = std::max(val5,val36);
+ val38 = std::max(val7,val38); val9 = std::min(val9,val40); val11 = std::min(val11,val42);
+ val13 = std::min(val13,val44); val15 = std::min(val15,val46); val17 = std::min(val17,val48);
+ val24 = std::max(val9,val24); val26 = std::max(val11,val26); val28 = std::max(val13,val28);
+ val30 = std::max(val15,val30); val17 = std::min(val17,val32); val19 = std::min(val19,val34);
+ val21 = std::min(val21,val36); val23 = std::min(val23,val38); val24 = std::max(val17,val24);
+ val26 = std::max(val19,val26); val21 = std::min(val21,val28); val23 = std::min(val23,val30);
+ val24 = std::max(val21,val24); val23 = std::min(val23,val26);
+ return std::max(val23,val24);
+ }
+
+ //! Return sqrt(x^2 + y^2).
+ template<typename T>
+ inline T hypot(const T x, const T y) {
+ return std::sqrt(x*x + y*y);
+ }
+
+ template<typename T>
+ inline T hypot(const T x, const T y, const T z) {
+ return std::sqrt(x*x + y*y + z*z);
+ }
+
+ template<typename T>
+ inline T _hypot(const T x, const T y) { // Slower but more precise version
+ T nx = cimg::abs(x), ny = cimg::abs(y), t;
+ if (nx<ny) { t = nx; nx = ny; } else t = ny;
+ if (nx>0) { t/=nx; return nx*std::sqrt(1 + t*t); }
+ return 0;
+ }
+
+ //! Return the factorial of n
+ inline double factorial(const int n) {
+ if (n<0) return cimg::type<double>::nan();
+ if (n<2) return 1;
+ double res = 2;
+ for (int i = 3; i<=n; ++i) res*=i;
+ return res;
+ }
+
+ //! Return the number of permutations of k objects in a set of n objects.
+ inline double permutations(const int k, const int n, const bool with_order) {
+ if (n<0 || k<0) return cimg::type<double>::nan();
+ if (k>n) return 0;
+ double res = 1;
+ for (int i = n; i>=n - k + 1; --i) res*=i;
+ return with_order?res:res/cimg::factorial(k);
+ }
+
+ inline double _fibonacci(int exp) {
+ double
+ base = (1 + std::sqrt(5.0))/2,
+ result = 1/std::sqrt(5.0);
+ while (exp) {
+ if (exp&1) result*=base;
+ exp>>=1;
+ base*=base;
+ }
+ return result;
+ }
+
+ //! Calculate fibonacci number.
+ // (Precise up to n = 78, less precise for n>78).
+ inline double fibonacci(const int n) {
+ if (n<0) return cimg::type<double>::nan();
+ if (n<3) return 1;
+ if (n<11) {
+ cimg_uint64 fn1 = 1, fn2 = 1, fn = 0;
+ for (int i = 3; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; }
+ return (double)fn;
+ }
+ if (n<75) // precise up to n = 74, faster than the integer calculation above for n>10
+ return (double)((cimg_uint64)(_fibonacci(n) + 0.5));
+
+ if (n<94) { // precise up to n = 78, less precise for n>78 up to n = 93, overflows for n>93
+ cimg_uint64
+ fn1 = (cimg_uint64)1304969544928657U,
+ fn2 = (cimg_uint64)806515533049393U,
+ fn = 0;
+ for (int i = 75; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; }
+ return (double)fn;
+ }
+ return _fibonacci(n); // Not precise, but better than the wrong overflowing calculation
+ }
+
+ //! Convert ascii character to lower case.
+ inline char lowercase(const char x) {
+ return (char)((x<'A'||x>'Z')?x:x - 'A' + 'a');
+ }
+ inline double lowercase(const double x) {
+ return (double)((x<'A'||x>'Z')?x:x - 'A' + 'a');
+ }
+
+ //! Convert C-string to lower case.
+ inline void lowercase(char *const str) {
+ if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = lowercase(*ptr);
+ }
+
+ //! Convert ascii character to upper case.
+ inline char uppercase(const char x) {
+ return (char)((x<'a'||x>'z')?x:x - 'a' + 'A');
+ }
+
+ inline double uppercase(const double x) {
+ return (double)((x<'a'||x>'z')?x:x - 'a' + 'A');
+ }
+
+ //! Convert C-string to upper case.
+ inline void uppercase(char *const str) {
+ if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uppercase(*ptr);
+ }
+
+ //! Read value in a C-string.
+ /**
+ \param str C-string containing the float value to read.
+ \return Read value.
+ \note Same as <tt>std::atof()</tt> extended to manage the retrieval of fractions from C-strings,
+ as in <em>"1/2"</em>.
+ **/
+ inline double atof(const char *const str) {
+ double x = 0, y = 1;
+ return str && cimg_sscanf(str,"%lf/%lf",&x,&y)>0?x/y:0;
+ }
+
+ //! Compare the first \p l characters of two C-strings, ignoring the case.
+ /**
+ \param str1 C-string.
+ \param str2 C-string.
+ \param l Number of characters to compare.
+ \return \c 0 if the two strings are equal, something else otherwise.
+ \note This function has to be defined since it is not provided by all C++-compilers (not ANSI).
+ **/
+ inline int strncasecmp(const char *const str1, const char *const str2, const int l) {
+ if (!l) return 0;
+ if (!str1) return str2?-1:0;
+ const char *nstr1 = str1, *nstr2 = str2;
+ int k, diff = 0; for (k = 0; k<l && !(diff = lowercase(*nstr1) - lowercase(*nstr2)); ++k) { ++nstr1; ++nstr2; }
+ return k!=l?diff:0;
+ }
+
+ //! Compare two C-strings, ignoring the case.
+ /**
+ \param str1 C-string.
+ \param str2 C-string.
+ \return \c 0 if the two strings are equal, something else otherwise.
+ \note This function has to be defined since it is not provided by all C++-compilers (not ANSI).
+ **/
+ inline int strcasecmp(const char *const str1, const char *const str2) {
+ if (!str1) return str2?-1:0;
+ const int
+ l1 = (int)std::strlen(str1),
+ l2 = (int)std::strlen(str2);
+ return cimg::strncasecmp(str1,str2,1 + (l1<l2?l1:l2));
+ }
+
+ //! Ellipsize a string.
+ /**
+ \param str C-string.
+ \param l Max number of characters.
+ \param is_ending Tell if the dots are placed at the end or at the center of the ellipsized string.
+ **/
+ inline char *strellipsize(char *const str, const unsigned int l=64,
+ const bool is_ending=true) {
+ if (!str) return str;
+ const unsigned int nl = l<5?5:l, ls = (unsigned int)std::strlen(str);
+ if (ls<=nl) return str;
+ if (is_ending) std::strcpy(str + nl - 5,"(...)");
+ else {
+ const unsigned int ll = (nl - 5)/2 + 1 - (nl%2), lr = nl - ll - 5;
+ std::strcpy(str + ll,"(...)");
+ std::memmove(str + ll + 5,str + ls - lr,lr);
+ }
+ str[nl] = 0;
+ return str;
+ }
+
+ //! Ellipsize a string.
+ /**
+ \param str C-string.
+ \param res output C-string.
+ \param l Max number of characters.
+ \param is_ending Tell if the dots are placed at the end or at the center of the ellipsized string.
+ **/
+ inline char *strellipsize(const char *const str, char *const res, const unsigned int l=64,
+ const bool is_ending=true) {
+ const unsigned int nl = l<5?5:l, ls = (unsigned int)std::strlen(str);
+ if (ls<=nl) { std::strcpy(res,str); return res; }
+ if (is_ending) {
+ std::strncpy(res,str,nl - 5);
+ std::strcpy(res + nl -5,"(...)");
+ } else {
+ const unsigned int ll = (nl - 5)/2 + 1 - (nl%2), lr = nl - ll - 5;
+ std::strncpy(res,str,ll);
+ std::strcpy(res + ll,"(...)");
+ std::strncpy(res + ll + 5,str + ls - lr,lr);
+ }
+ res[nl] = 0;
+ return res;
+ }
+
+ //! Remove delimiters on the start and/or end of a C-string.
+ /**
+ \param[in,out] str C-string to work with (modified at output).
+ \param delimiter Delimiter character code to remove.
+ \param is_symmetric Tells if the removal is done only if delimiters are symmetric
+ (both at the beginning and the end of \c s).
+ \param is_iterative Tells if the removal is done if several iterations are possible.
+ \return \c true if delimiters have been removed, \c false otherwise.
+ **/
+ inline bool strpare(char *const str, const char delimiter,
+ const bool is_symmetric, const bool is_iterative) {
+ if (!str) return false;
+ const int l = (int)std::strlen(str);
+ int p, q;
+ if (is_symmetric) for (p = 0, q = l - 1; p<q && str[p]==delimiter && str[q]==delimiter; ) {
+ --q; ++p; if (!is_iterative) break;
+ } else {
+ for (p = 0; p<l && str[p]==delimiter; ) { ++p; if (!is_iterative) break; }
+ for (q = l - 1; q>p && str[q]==delimiter; ) { --q; if (!is_iterative) break; }
+ }
+ const int n = q - p + 1;
+ if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; }
+ return false;
+ }
+
+ //! Remove white spaces on the start and/or end of a C-string.
+ inline bool strpare(char *const str, const bool is_symmetric, const bool is_iterative) {
+ if (!str) return false;
+ const int l = (int)std::strlen(str);
+ int p, q;
+ if (is_symmetric) for (p = 0, q = l - 1; p<q && (signed char)str[p]<=' ' && (signed char)str[q]<=' '; ) {
+ --q; ++p; if (!is_iterative) break;
+ } else {
+ for (p = 0; p<l && (signed char)str[p]<=' '; ) { ++p; if (!is_iterative) break; }
+ for (q = l - 1; q>p && (signed char)str[q]<=' '; ) { --q; if (!is_iterative) break; }
+ }
+ const int n = q - p + 1;
+ if (n!=l) { std::memmove(str,str + p,(unsigned int)n); str[n] = 0; return true; }
+ return false;
+ }
+
+ //! Replace reserved characters (for Windows filename) by another character.
+ /**
+ \param[in,out] str C-string to work with (modified at output).
+ \param[in] c Replacement character.
+ **/
+ inline void strwindows_reserved(char *const str, const char c='_') {
+ for (char *s = str; *s; ++s) {
+ const char i = *s;
+ if (i=='<' || i=='>' || i==':' || i=='\"' || i=='/' || i=='\\' || i=='|' || i=='?' || i=='*') *s = c;
+ }
+ }
+
+ //! Replace escape sequences in C-strings by their binary ascii values.
+ /**
+ \param[in,out] str C-string to work with (modified at output).
+ **/
+ inline void strunescape(char *const str) {
+#define cimg_strunescape(ci,co) case ci : *nd = co; ++ns; break;
+ unsigned int val = 0;
+ for (char *ns = str, *nd = str; *ns || (bool)(*nd=0); ++nd) if (*ns=='\\') switch (*(++ns)) {
+ cimg_strunescape('a','\a');
+ cimg_strunescape('b','\b');
+ cimg_strunescape('e',0x1B);
+ cimg_strunescape('f','\f');
+ cimg_strunescape('n','\n');
+ cimg_strunescape('r','\r');
+ cimg_strunescape('t','\t');
+ cimg_strunescape('v','\v');
+ cimg_strunescape('\\','\\');
+ cimg_strunescape('\'','\'');
+ cimg_strunescape('\"','\"');
+ cimg_strunescape('\?','\?');
+ case 0 : *nd = 0; break;
+ case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' :
+ cimg_sscanf(ns,"%o",&val); while (*ns>='0' && *ns<='7') ++ns;
+ *nd = (char)val; break;
+ case 'x' :
+ cimg_sscanf(++ns,"%x",&val);
+ while ((*ns>='0' && *ns<='9') || (*ns>='a' && *ns<='f') || (*ns>='A' && *ns<='F')) ++ns;
+ *nd = (char)val; break;
+ default : *nd = *(ns++);
+ } else *nd = *(ns++);
+ }
+
+ // Return a temporary string describing the size of a memory buffer.
+ inline const char *strbuffersize(const cimg_ulong size);
+
+ // Return string that identifies the running OS.
+ inline const char *stros() {
+#if defined(linux) || defined(__linux) || defined(__linux__)
+ static const char *const str = "Linux";
+#elif defined(sun) || defined(__sun)
+ static const char *const str = "Sun OS";
+#elif defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined (__DragonFly__)
+ static const char *const str = "BSD";
+#elif defined(sgi) || defined(__sgi)
+ static const char *const str = "Irix";
+#elif defined(__MACOSX__) || defined(__APPLE__)
+ static const char *const str = "Mac OS";
+#elif defined(unix) || defined(__unix) || defined(__unix__)
+ static const char *const str = "Generic Unix";
+#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || \
+ defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
+ static const char *const str = "Windows";
+#else
+ const char
+ *const _str1 = std::getenv("OSTYPE"),
+ *const _str2 = _str1?_str1:std::getenv("OS"),
+ *const str = _str2?_str2:"Unknown OS";
+#endif
+ return str;
+ }
+
+ //! Return the basename of a filename.
+ inline const char* basename(const char *const s, const char separator=cimg_file_separator) {
+ const char *p = 0, *np = s;
+ while (np>=s && (p=np)) np = std::strchr(np,separator) + 1;
+ return p;
+ }
+
+ // Return a random filename.
+ inline const char* filenamerand() {
+ cimg::mutex(6);
+ static char randomid[9];
+ cimg::srand();
+ for (unsigned int k = 0; k<8; ++k) {
+ const int v = (int)cimg::rand(65535)%3;
+ randomid[k] = (char)(v==0?('0' + ((int)cimg::rand(65535)%10)):
+ (v==1?('a' + ((int)cimg::rand(65535)%26)):
+ ('A' + ((int)cimg::rand(65535)%26))));
+ }
+ cimg::mutex(6,0);
+ return randomid;
+ }
+
+ // Convert filename as a Windows-style filename (short path name).
+ inline void winformat_string(char *const str) {
+ if (str && *str) {
+#if cimg_OS==2
+ char *const nstr = new char[MAX_PATH];
+ if (GetShortPathNameA(str,nstr,MAX_PATH)) std::strcpy(str,nstr);
+ delete[] nstr;
+#endif
+ }
+ }
+
+ // Open a file (with wide character support on Windows).
+ inline std::FILE *win_fopen(const char *const path, const char *const mode);
+
+ //! Open a file.
+ /**
+ \param path Path of the filename to open.
+ \param mode C-string describing the opening mode.
+ \return Opened file.
+ \note Same as <tt>std::fopen()</tt> but throw a \c CImgIOException when
+ the specified file cannot be opened, instead of returning \c 0.
+ **/
+ inline std::FILE *fopen(const char *const path, const char *const mode) {
+ if (!path)
+ throw CImgArgumentException("cimg::fopen(): Specified file path is (null).");
+ if (!mode)
+ throw CImgArgumentException("cimg::fopen(): File '%s', specified mode is (null).",
+ path);
+ std::FILE *res = 0;
+ if (*path=='-' && (!path[1] || path[1]=='.')) {
+ res = (*mode=='r')?cimg::_stdin():cimg::_stdout();
+#if cimg_OS==2
+ if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode.
+#ifdef __BORLANDC__
+ if (setmode(_fileno(res),0x8000)==-1) res = 0;
+#else
+ if (_setmode(_fileno(res),0x8000)==-1) res = 0;
+#endif
+ }
+#endif
+ } else res = std_fopen(path,mode);
+ if (!res) throw CImgIOException("cimg::fopen(): Failed to open file '%s' with mode '%s'.",
+ path,mode);
+ return res;
+ }
+
+ //! Close a file.
+ /**
+ \param file File to close.
+ \return \c 0 if file has been closed properly, something else otherwise.
+ \note Same as <tt>std::fclose()</tt> but display a warning message if
+ the file has not been closed properly.
+ **/
+ inline int fclose(std::FILE *file) {
+ if (!file) { warn("cimg::fclose(): Specified file is (null)."); return 0; }
+ if (file==cimg::_stdin(false) || file==cimg::_stdout(false)) return 0;
+ const int errn = std::fclose(file);
+ if (errn!=0) warn("cimg::fclose(): Error code %d returned during file closing.",
+ errn);
+ return errn;
+ }
+
+ //! Version of 'fseek()' that supports >=64bits offsets everywhere (for Windows).
+ inline int fseek(FILE *stream, cimg_long offset, int origin) {
+#if cimg_OS==2
+ return _fseeki64(stream,(__int64)offset,origin);
+#else
+ return std::fseek(stream,offset,origin);
+#endif
+ }
+
+ //! Version of 'ftell()' that supports >=64bits offsets everywhere (for Windows).
+ inline cimg_long ftell(FILE *stream) {
+#if cimg_OS==2
+ return (cimg_long)_ftelli64(stream);
+#else
+ return (cimg_long)std::ftell(stream);
+#endif
+ }
+
+ //! Check if a path is a directory.
+ /**
+ \param path Specified path to test.
+ **/
+ inline bool is_directory(const char *const path) {
+ if (!path || !*path) return false;
+#if cimg_OS==1
+ struct stat st_buf;
+ return (!stat(path,&st_buf) && S_ISDIR(st_buf.st_mode));
+#elif cimg_OS==2
+ const unsigned int res = (unsigned int)GetFileAttributesA(path);
+ return res==INVALID_FILE_ATTRIBUTES?false:(res&16);
+#else
+ return false;
+#endif
+ }
+
+ //! Check if a path is a file.
+ /**
+ \param path Specified path to test.
+ **/
+ inline bool is_file(const char *const path) {
+ if (!path || !*path) return false;
+ std::FILE *const file = std_fopen(path,"rb");
+ if (!file) return false;
+ std::fclose(file);
+ return !is_directory(path);
+ }
+
+ //! Get last write time of a given file or directory.
+ /**
+ \param path Specified path to get attributes from.
+ \param attr Type of requested time attribute.
+ Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second }
+ \return -1 if requested attribute could not be read.
+ **/
+ inline int fdate(const char *const path, const unsigned int attr) {
+ int res = -1;
+ if (!path || !*path || attr>6) return -1;
+ cimg::mutex(6);
+#if cimg_OS==2
+ HANDLE file = CreateFileA(path,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
+ if (file!=INVALID_HANDLE_VALUE) {
+ FILETIME _ft;
+ SYSTEMTIME ft;
+ if (GetFileTime(file,0,0,&_ft) && FileTimeToSystemTime(&_ft,&ft))
+ res = (int)(attr==0?ft.wYear:attr==1?ft.wMonth:attr==2?ft.wDay:attr==3?ft.wDayOfWeek:
+ attr==4?ft.wHour:attr==5?ft.wMinute:ft.wSecond);
+ CloseHandle(file);
+ }
+#elif cimg_OS==1
+ struct stat st_buf;
+ if (!stat(path,&st_buf)) {
+ const time_t _ft = st_buf.st_mtime;
+ const struct tm& ft = *std::localtime(&_ft);
+ res = (int)(attr==0?ft.tm_year + 1900:attr==1?ft.tm_mon + 1:attr==2?ft.tm_mday:attr==3?ft.tm_wday:
+ attr==4?ft.tm_hour:attr==5?ft.tm_min:ft.tm_sec);
+ }
+#endif
+ cimg::mutex(6,0);
+ return res;
+ }
+
+ //! Get current local time.
+ /**
+ \param attr Type of requested time attribute.
+ Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second }
+ **/
+ inline int date(const unsigned int attr) {
+ int res;
+ cimg::mutex(6);
+#if cimg_OS==2
+ SYSTEMTIME st;
+ GetLocalTime(&st);
+ res = (int)(attr==0?st.wYear:attr==1?st.wMonth:attr==2?st.wDay:attr==3?st.wDayOfWeek:
+ attr==4?st.wHour:attr==5?st.wMinute:st.wSecond);
+#else
+ time_t _st;
+ std::time(&_st);
+ struct tm *st = std::localtime(&_st);
+ res = (int)(attr==0?st->tm_year + 1900:attr==1?st->tm_mon + 1:attr==2?st->tm_mday:attr==3?st->tm_wday:
+ attr==4?st->tm_hour:attr==5?st->tm_min:st->tm_sec);
+#endif
+ cimg::mutex(6,0);
+ return res;
+ }
+
+ // Get/set path to store temporary files.
+ inline const char* temporary_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the <i>Program Files/</i> directory (Windows only).
+#if cimg_OS==2
+ inline const char* programfiles_path(const char *const user_path=0, const bool reinit_path=false);
+#endif
+
+ // Get/set path to the ImageMagick's \c convert binary.
+ inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the GraphicsMagick's \c gm binary.
+ inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the XMedcon's \c medcon binary.
+ inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the FFMPEG's \c ffmpeg binary.
+ inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the \c gzip binary.
+ inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the \c gunzip binary.
+ inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the \c dcraw binary.
+ inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the \c wget binary.
+ inline const char *wget_path(const char *const user_path=0, const bool reinit_path=false);
+
+ // Get/set path to the \c curl binary.
+ inline const char *curl_path(const char *const user_path=0, const bool reinit_path=false);
+
+ //! Split filename into two C-strings \c body and \c extension.
+ /**
+ filename and body must not overlap!
+ **/
+ 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 = (unsigned int)(p - filename - 1);
+ if (body) { if (l) std::memcpy(body,filename,l); body[l] = 0; }
+ return p;
+ }
+
+ //! Generate a numbered version of a filename.
+ inline char* number_filename(const char *const filename, const int number,
+ const unsigned int digits, char *const str) {
+ if (!filename) { if (str) *str = 0; return 0; }
+ char *const format = new char[1024], *const body = new char[1024];
+ const char *const ext = cimg::split_filename(filename,body);
+ if (*ext) cimg_snprintf(format,1024,"%%s_%%.%ud.%%s",digits);
+ else cimg_snprintf(format,1024,"%%s_%%.%ud",digits);
+ cimg_sprintf(str,format,body,number,ext);
+ delete[] format; delete[] body;
+ return str;
+ }
+
+ //! Read data from file.
+ /**
+ \param[out] ptr Pointer to memory buffer that will contain the binary data read from file.
+ \param nmemb Number of elements to read.
+ \param stream File to read data from.
+ \return Number of read elements.
+ \note Same as <tt>std::fread()</tt> but may display warning message if all elements could not be read.
+ **/
+ template<typename T>
+ inline size_t fread(T *const ptr, const size_t nmemb, std::FILE *stream) {
+ if (!ptr || !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);
+ if (!nmemb) return 0;
+ const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+ size_t to_read = nmemb, al_read = 0, l_to_read = 0, l_al_read = 0;
+ do {
+ l_to_read = (to_read*sizeof(T))<wlimitT?to_read:wlimit;
+ l_al_read = std::fread((void*)(ptr + al_read),sizeof(T),l_to_read,stream);
+ al_read+=l_al_read;
+ to_read-=l_al_read;
+ } while (l_to_read==l_al_read && to_read>0);
+ if (to_read>0)
+ warn("cimg::fread(): Only %lu/%lu elements could be read from file.",
+ (unsigned long)al_read,(unsigned long)nmemb);
+ return al_read;
+ }
+
+ //! Write data to file.
+ /**
+ \param ptr Pointer to memory buffer containing the binary data to write on file.
+ \param nmemb Number of elements to write.
+ \param[out] stream File to write data on.
+ \return Number of written elements.
+ \note Similar to <tt>std::fwrite</tt> but may display warning messages if all elements could not be written.
+ **/
+ template<typename T>
+ inline size_t fwrite(const T *ptr, const size_t nmemb, std::FILE *stream) {
+ if (!ptr || !stream)
+ throw CImgArgumentException("cimg::fwrite(): Invalid writing request of %u %s%s from buffer %p to file %p.",
+ nmemb,cimg::type<T>::string(),nmemb>1?"s":"",ptr,stream);
+ if (!nmemb) return 0;
+ const size_t wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
+ size_t to_write = nmemb, al_write = 0, l_to_write = 0, l_al_write = 0;
+ do {
+ l_to_write = (to_write*sizeof(T))<wlimitT?to_write:wlimit;
+ l_al_write = std::fwrite((void*)(ptr + al_write),sizeof(T),l_to_write,stream);
+ al_write+=l_al_write;
+ to_write-=l_al_write;
+ } while (l_to_write==l_al_write && to_write>0);
+ if (to_write>0)
+ warn("cimg::fwrite(): Only %lu/%lu elements could be written in file.",
+ (unsigned long)al_write,(unsigned long)nmemb);
+ return al_write;
+ }
+
+ //! Create an empty file.
+ /**
+ \param file Input file (can be \c 0 if \c filename is set).
+ \param filename Filename, as a C-string (can be \c 0 if \c file is set).
+ **/
+ inline void fempty(std::FILE *const file, const char *const filename) {
+ if (!file && !filename)
+ throw CImgArgumentException("cimg::fempty(): Specified filename is (null).");
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ if (!file) cimg::fclose(nfile);
+ }
+
+ // Try to guess format from an image file.
+ inline const char *ftype(std::FILE *const file, const char *const filename);
+
+ // Load file from network as a local temporary file.
+ inline char *load_network(const char *const url, char *const filename_local,
+ const unsigned int timeout=0, const bool try_fallback=false,
+ const char *const referer=0);
+
+ //! Return options specified on the command line.
+ 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",cimg_date,cimg_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 *const tmp = new char[256];
+ cimg_snprintf(tmp,256,"%d",res);
+ cimg::option(name,0,0,tmp,usage);
+ delete[] tmp;
+ 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];
+ *tmp = res; tmp[1] = 0;
+ cimg::option(name,0,0,tmp,usage);
+ return res;
+ }
+
+ inline float option(const char *const name, const int argc, const char *const *const argv,
+ const float defaut, const char *const usage=0) {
+ const char *const s = cimg::option(name,argc,argv,(char*)0);
+ const float res = s?(float)cimg::atof(s):defaut;
+ char *const tmp = new char[256];
+ cimg_snprintf(tmp,256,"%g",res);
+ cimg::option(name,0,0,tmp,usage);
+ delete[] tmp;
+ 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 *const tmp = new char[256];
+ cimg_snprintf(tmp,256,"%g",res);
+ cimg::option(name,0,0,tmp,usage);
+ delete[] tmp;
+ return res;
+ }
+
+ //! Print information about \CImg environement variables.
+ /**
+ \note Output is done on the default output stream.
+ **/
+ inline void info() {
+ std::fprintf(cimg::output(),"\n %s%sCImg Library %u.%u.%u%s, compiled %s ( %s ) with the following flags:\n\n",
+ cimg::t_red,cimg::t_bold,cimg_version/100,(cimg_version/10)%10,cimg_version%10,
+ cimg::t_normal,cimg_date,cimg_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()," > Support for C++11: %s%-13s%s %s('cimg_use_cpp11'=%d)%s\n",
+ cimg::t_bold,
+ cimg_use_cpp11?"Yes":"No",
+ cimg::t_normal,cimg::t_green,
+ (int)cimg_use_cpp11,
+ 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,
+ (int)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);
+
+ char *const tmp = new char[1024];
+ cimg_snprintf(tmp,1024,"\"%.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,1024,"\"%.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,1024,"\"%.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,1024,"\"%.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");
+ delete[] tmp;
+ }
+
+ // Declare LAPACK function signatures if LAPACK support is enabled.
+#ifdef cimg_use_lapack
+ template<typename T>
+ inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) {
+ dgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
+ }
+
+ inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) {
+ sgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
+ }
+
+ template<typename T>
+ inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) {
+ dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
+ }
+
+ inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) {
+ sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
+ }
+
+ template<typename T>
+ inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN,
+ T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) {
+ dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
+ }
+
+ inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN,
+ float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) {
+ sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
+ }
+
+ template<typename T>
+ inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) {
+ int one = 1;
+ dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
+ }
+
+ inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) {
+ int one = 1;
+ sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
+ }
+
+ template<typename T>
+ inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) {
+ dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
+ }
+
+ inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) {
+ ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
+ }
+
+ template<typename T>
+ inline void sgels(char & TRANS, int &M, int &N, int &NRHS, T* lapA, int &LDA,
+ T* lapB, int &LDB, T* WORK, int &LWORK, int &INFO){
+ dgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO);
+ }
+
+ inline void sgels(char & TRANS, int &M, int &N, int &NRHS, float* lapA, int &LDA,
+ float* lapB, int &LDB, float* WORK, int &LWORK, int &INFO){
+ sgels_(&TRANS, &M, &N, &NRHS, lapA, &LDA, lapB, &LDB, WORK, &LWORK, &INFO);
+ }
+
+#endif
+
+ // End of the 'cimg' namespace
+ }
+
+ /*------------------------------------------------
+ #
+ #
+ # Definition of mathematical operators and
+ # external functions.
+ #
+ #
+ -------------------------------------------------*/
+
+#define _cimg_create_ext_operators(typ) \
+ template<typename T> \
+ inline CImg<typename cimg::superset<T,typ>::type> operator+(const typ val, const CImg<T>& img) { \
+ return img + val; \
+ } \
+ template<typename T> \
+ inline CImg<typename cimg::superset<T,typ>::type> operator-(const typ val, const CImg<T>& img) { \
+ typedef typename cimg::superset<T,typ>::type Tt; \
+ return CImg<Tt>(img._width,img._height,img._depth,img._spectrum,val)-=img; \
+ } \
+ template<typename T> \
+ inline CImg<typename cimg::superset<T,typ>::type> operator*(const typ val, const CImg<T>& img) { \
+ return img*val; \
+ } \
+ template<typename T> \
+ inline CImg<typename cimg::superset<T,typ>::type> operator/(const typ val, const CImg<T>& img) { \
+ return val*img.get_invert(); \
+ } \
+ template<typename T> \
+ inline CImg<typename cimg::superset<T,typ>::type> operator&(const typ val, const CImg<T>& img) { \
+ return img & val; \
+ } \
+ template<typename T> \
+ inline CImg<typename cimg::superset<T,typ>::type> operator|(const typ val, const CImg<T>& img) { \
+ return img | val; \
+ } \
+ template<typename T> \
+ inline CImg<typename cimg::superset<T,typ>::type> operator^(const typ val, const CImg<T>& img) { \
+ return img ^ val; \
+ } \
+ template<typename T> \
+ inline bool operator==(const typ val, const CImg<T>& img) { \
+ return img == val; \
+ } \
+ template<typename T> \
+ inline bool operator!=(const typ val, const CImg<T>& img) { \
+ return img != val; \
+ }
+
+ _cimg_create_ext_operators(bool)
+ _cimg_create_ext_operators(unsigned char)
+ _cimg_create_ext_operators(char)
+ _cimg_create_ext_operators(signed char)
+ _cimg_create_ext_operators(unsigned short)
+ _cimg_create_ext_operators(short)
+ _cimg_create_ext_operators(unsigned int)
+ _cimg_create_ext_operators(int)
+ _cimg_create_ext_operators(cimg_uint64)
+ _cimg_create_ext_operators(cimg_int64)
+ _cimg_create_ext_operators(float)
+ _cimg_create_ext_operators(double)
+ _cimg_create_ext_operators(long 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,false).fill(expression,true)-=img;
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> operator*(const char *const expression, const CImg<T>& img) {
+ return img*expression;
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> operator/(const char *const expression, const CImg<T>& img) {
+ return expression*img.get_invert();
+ }
+
+ template<typename T>
+ inline CImg<T> operator&(const char *const expression, const CImg<T>& img) {
+ return img & expression;
+ }
+
+ template<typename T>
+ inline CImg<T> operator|(const char *const expression, const CImg<T>& img) {
+ return img | expression;
+ }
+
+ template<typename T>
+ inline CImg<T> operator^(const char *const expression, const CImg<T>& img) {
+ return img ^ expression;
+ }
+
+ template<typename T>
+ inline bool operator==(const char *const expression, const CImg<T>& img) {
+ return img==expression;
+ }
+
+ template<typename T>
+ inline bool operator!=(const char *const expression, const CImg<T>& img) {
+ return img!=expression;
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sqr(const CImg<T>& instance) {
+ return instance.get_sqr();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sqrt(const CImg<T>& instance) {
+ return instance.get_sqrt();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> exp(const CImg<T>& instance) {
+ return instance.get_exp();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> log(const CImg<T>& instance) {
+ return instance.get_log();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> log2(const CImg<T>& instance) {
+ return instance.get_log2();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> log10(const CImg<T>& instance) {
+ return instance.get_log10();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> abs(const CImg<T>& instance) {
+ return instance.get_abs();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sign(const CImg<T>& instance) {
+ return instance.get_sign();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> cos(const CImg<T>& instance) {
+ return instance.get_cos();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sin(const CImg<T>& instance) {
+ return instance.get_sin();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sinc(const CImg<T>& instance) {
+ return instance.get_sinc();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> tan(const CImg<T>& instance) {
+ return instance.get_tan();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> acos(const CImg<T>& instance) {
+ return instance.get_acos();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> asin(const CImg<T>& instance) {
+ return instance.get_asin();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> atan(const CImg<T>& instance) {
+ return instance.get_atan();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> cosh(const CImg<T>& instance) {
+ return instance.get_cosh();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> sinh(const CImg<T>& instance) {
+ return instance.get_sinh();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> tanh(const CImg<T>& instance) {
+ return instance.get_tanh();
+ }
+
+ template<typename T>
+ inline CImg<T> transpose(const CImg<T>& instance) {
+ return instance.get_transpose();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> invert(const CImg<T>& instance) {
+ return instance.get_invert();
+ }
+
+ template<typename T>
+ inline CImg<_cimg_Tfloat> pseudoinvert(const CImg<T>& instance) {
+ return instance.get_pseudoinvert();
+ }
+
+ /*-----------------------------------
+ #
+ # Define the CImgDisplay structure
+ #
+ ----------------------------------*/
+ //! Allow the creation of windows, display images on them and manage user events (keyboard, mouse and windows events).
+ /**
+ CImgDisplay methods rely on a low-level graphic library to perform: it can be either \b X-Window
+ (X11, for Unix-based systems) or \b GDI32 (for Windows-based systems).
+ If both libraries are missing, CImgDisplay will not be able to display images on screen, and will enter
+ a minimal mode where warning messages will be outputed each time the program is trying to call one of the
+ CImgDisplay method.
+
+ The configuration variable \c cimg_display tells about the graphic library used.
+ It is set automatically by \CImg when one of these graphic libraries has been detected.
+ But, you can override its value if necessary. Valid choices are:
+ - 0: Disable display capabilities.
+ - 1: Use \b X-Window (X11) library.
+ - 2: Use \b GDI32 library.
+
+ Remember to link your program against \b X11 or \b GDI32 libraries if you use CImgDisplay.
+ **/
+ struct CImgDisplay {
+ cimg_ulong _timer, _fps_frames, _fps_timer;
+ unsigned int _width, _height, _normalization;
+ float _fps_fps, _min, _max;
+ bool _is_fullscreen;
+ char *_title;
+ unsigned int _window_width, _window_height, _button, *_keys, *_released_keys;
+ int _window_x, _window_y, _mouse_x, _mouse_y, _wheel;
+ bool _is_closed, _is_resized, _is_moved, _is_event,
+ _is_keyESC, _is_keyF1, _is_keyF2, _is_keyF3, _is_keyF4, _is_keyF5, _is_keyF6, _is_keyF7,
+ _is_keyF8, _is_keyF9, _is_keyF10, _is_keyF11, _is_keyF12, _is_keyPAUSE, _is_key1, _is_key2,
+ _is_key3, _is_key4, _is_key5, _is_key6, _is_key7, _is_key8, _is_key9, _is_key0,
+ _is_keyBACKSPACE, _is_keyINSERT, _is_keyHOME, _is_keyPAGEUP, _is_keyTAB, _is_keyQ, _is_keyW, _is_keyE,
+ _is_keyR, _is_keyT, _is_keyY, _is_keyU, _is_keyI, _is_keyO, _is_keyP, _is_keyDELETE,
+ _is_keyEND, _is_keyPAGEDOWN, _is_keyCAPSLOCK, _is_keyA, _is_keyS, _is_keyD, _is_keyF, _is_keyG,
+ _is_keyH, _is_keyJ, _is_keyK, _is_keyL, _is_keyENTER, _is_keySHIFTLEFT, _is_keyZ, _is_keyX,
+ _is_keyC, _is_keyV, _is_keyB, _is_keyN, _is_keyM, _is_keySHIFTRIGHT, _is_keyARROWUP, _is_keyCTRLLEFT,
+ _is_keyAPPLEFT, _is_keyALT, _is_keySPACE, _is_keyALTGR, _is_keyAPPRIGHT, _is_keyMENU, _is_keyCTRLRIGHT,
+ _is_keyARROWLEFT, _is_keyARROWDOWN, _is_keyARROWRIGHT, _is_keyPAD0, _is_keyPAD1, _is_keyPAD2, _is_keyPAD3,
+ _is_keyPAD4, _is_keyPAD5, _is_keyPAD6, _is_keyPAD7, _is_keyPAD8, _is_keyPAD9, _is_keyPADADD, _is_keyPADSUB,
+ _is_keyPADMUL, _is_keyPADDIV;
+
+ //@}
+ //---------------------------
+ //
+ //! \name Plugins
+ //@{
+ //---------------------------
+
+#ifdef cimgdisplay_plugin
+#include cimgdisplay_plugin
+#endif
+#ifdef cimgdisplay_plugin1
+#include cimgdisplay_plugin1
+#endif
+#ifdef cimgdisplay_plugin2
+#include cimgdisplay_plugin2
+#endif
+#ifdef cimgdisplay_plugin3
+#include cimgdisplay_plugin3
+#endif
+#ifdef cimgdisplay_plugin4
+#include cimgdisplay_plugin4
+#endif
+#ifdef cimgdisplay_plugin5
+#include cimgdisplay_plugin5
+#endif
+#ifdef cimgdisplay_plugin6
+#include cimgdisplay_plugin6
+#endif
+#ifdef cimgdisplay_plugin7
+#include cimgdisplay_plugin7
+#endif
+#ifdef cimgdisplay_plugin8
+#include cimgdisplay_plugin8
+#endif
+
+ //@}
+ //--------------------------------------------------------
+ //
+ //! \name Constructors / Destructor / Instance Management
+ //@{
+ //--------------------------------------------------------
+
+ //! Destructor.
+ /**
+ \note If the associated window is visible on the screen, it is closed by the call to the destructor.
+ **/
+ ~CImgDisplay() {
+ assign();
+ delete[] _keys;
+ delete[] _released_keys;
+ }
+
+ //! Construct an empty display.
+ /**
+ \note Constructing an empty CImgDisplay instance does not make a window appearing on the screen, until
+ display of valid data is performed.
+ \par Example
+ \code
+ CImgDisplay disp; // Does actually nothing.
+ ...
+ disp.display(img); // Construct new window and display image in it.
+ \endcode
+ **/
+ CImgDisplay():
+ _width(0),_height(0),_normalization(0),
+ _min(0),_max(0),
+ _is_fullscreen(false),
+ _title(0),
+ _window_width(0),_window_height(0),_button(0),
+ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]),
+ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+ assign();
+ }
+
+ //! Construct a display with specified dimensions.
+ /** \param width Window width.
+ \param height Window height.
+ \param title Window title.
+ \param normalization Normalization type
+ (<tt>0</tt>=none, <tt>1</tt>=always, <tt>2</tt>=once, <tt>3</tt>=pixel type-dependent, see normalization()).
+ \param is_fullscreen Tells if fullscreen mode is enabled.
+ \param is_closed Tells if associated window is initially visible or not.
+ \note A black background is initially displayed on the associated 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),_normalization(0),
+ _min(0),_max(0),
+ _is_fullscreen(false),
+ _title(0),
+ _window_width(0),_window_height(0),_button(0),
+ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]),
+ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+ assign(width,height,title,normalization,is_fullscreen,is_closed);
+ }
+
+ //! Construct a display from an image.
+ /** \param img Image used as a model to create the window.
+ \param title Window title.
+ \param normalization Normalization type
+ (<tt>0</tt>=none, <tt>1</tt>=always, <tt>2</tt>=once, <tt>3</tt>=pixel type-dependent, see normalization()).
+ \param is_fullscreen Tells if fullscreen mode is enabled.
+ \param is_closed Tells if associated window is initially visible or not.
+ \note The pixels of the input image are initially displayed on the associated window.
+ **/
+ template<typename T>
+ explicit CImgDisplay(const CImg<T>& img,
+ const char *const title=0, const unsigned int normalization=3,
+ const bool is_fullscreen=false, const bool is_closed=false):
+ _width(0),_height(0),_normalization(0),
+ _min(0),_max(0),
+ _is_fullscreen(false),
+ _title(0),
+ _window_width(0),_window_height(0),_button(0),
+ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]),
+ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+ assign(img,title,normalization,is_fullscreen,is_closed);
+ }
+
+ //! Construct a display from an image list.
+ /** \param list The images list to display.
+ \param title Window title.
+ \param normalization Normalization type
+ (<tt>0</tt>=none, <tt>1</tt>=always, <tt>2</tt>=once, <tt>3</tt>=pixel type-dependent, see normalization()).
+ \param is_fullscreen Tells if fullscreen mode is enabled.
+ \param is_closed Tells if associated window is initially visible or not.
+ \note All images of the list, appended along the X-axis, are initially displayed on the associated window.
+ **/
+ template<typename T>
+ explicit CImgDisplay(const CImgList<T>& list,
+ const char *const title=0, const unsigned int normalization=3,
+ const bool is_fullscreen=false, const bool is_closed=false):
+ _width(0),_height(0),_normalization(0),
+ _min(0),_max(0),
+ _is_fullscreen(false),
+ _title(0),
+ _window_width(0),_window_height(0),_button(0),
+ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]),
+ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+ assign(list,title,normalization,is_fullscreen,is_closed);
+ }
+
+ //! Construct a display as a copy of an existing one.
+ /**
+ \param disp Display instance to copy.
+ \note The pixel buffer of the input window is initially displayed on the associated window.
+ **/
+ CImgDisplay(const CImgDisplay& disp):
+ _width(0),_height(0),_normalization(0),
+ _min(0),_max(0),
+ _is_fullscreen(false),
+ _title(0),
+ _window_width(0),_window_height(0),_button(0),
+ _keys(new unsigned int[128]),_released_keys(new unsigned int[128]),
+ _window_x(0),_window_y(0),_mouse_x(-1),_mouse_y(-1),_wheel(0),
+ _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
+ assign(disp);
+ }
+
+ //! Take a screenshot.
+ /**
+ \param[out] img Output screenshot. Can be empty on input
+ **/
+ template<typename T>
+ static void screenshot(CImg<T>& img) {
+ return screenshot(0,0,cimg::type<int>::max(),cimg::type<int>::max(),img);
+ }
+
+#if cimg_display==0
+
+ static void _no_display_exception() {
+ throw CImgDisplayException("CImgDisplay(): No display available.");
+ }
+
+ //! Destructor - Empty constructor \inplace.
+ /**
+ \note Replace the current instance by an empty display.
+ **/
+ CImgDisplay& assign() {
+ return flush();
+ }
+
+ //! Construct a display with specified dimensions \inplace.
+ /**
+ **/
+ CImgDisplay& assign(const unsigned int width, const unsigned int height,
+ const char *const title=0, const unsigned int normalization=3,
+ const bool is_fullscreen=false, const bool is_closed=false) {
+ cimg::unused(width,height,title,normalization,is_fullscreen,is_closed);
+ _no_display_exception();
+ return assign();
+ }
+
+ //! Construct a display from an image \inplace.
+ /**
+ **/
+ template<typename T>
+ CImgDisplay& assign(const CImg<T>& img,
+ const char *const title=0, const unsigned int normalization=3,
+ const bool is_fullscreen=false, const bool is_closed=false) {
+ _no_display_exception();
+ return assign(img._width,img._height,title,normalization,is_fullscreen,is_closed);
+ }
+
+ //! Construct a display from an image list \inplace.
+ /**
+ **/
+ template<typename T>
+ CImgDisplay& assign(const CImgList<T>& list,
+ const char *const title=0, const unsigned int normalization=3,
+ const bool is_fullscreen=false, const bool is_closed=false) {
+ _no_display_exception();
+ return assign(list._width,list._width,title,normalization,is_fullscreen,is_closed);
+ }
+
+ //! Construct a display as a copy of another one \inplace.
+ /**
+ **/
+ CImgDisplay& assign(const CImgDisplay &disp) {
+ _no_display_exception();
+ return assign(disp._width,disp._height);
+ }
+
+#endif
+
+ //! Return a reference to an empty display.
+ /**
+ \note Can be useful for writing function prototypes where one of the argument (of type CImgDisplay&)
+ must have a default value.
+ \par Example
+ \code
+ void foo(CImgDisplay& disp=CImgDisplay::empty());
+ \endcode
+ **/
+ static CImgDisplay& empty() {
+ static CImgDisplay _empty;
+ return _empty.assign();
+ }
+
+ //! Return a reference to an empty display \const.
+ static const CImgDisplay& const_empty() {
+ static const CImgDisplay _empty;
+ return _empty;
+ }
+
+#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,128,-85,false), \
+ CImgDisplay::_fitscreen(dx,dy,dz,128,-85,true)
+ static unsigned int _fitscreen(const unsigned int dx, const unsigned int dy, const unsigned int dz,
+ const int dmin, const int dmax,const bool return_y) {
+ const unsigned int _nw = dx + (dz>1?dz:0), _nh = dy + (dz>1?dz:0);
+ unsigned int nw = _nw?_nw:1, nh = _nh?_nh:1;
+ const unsigned int
+ sw = (unsigned int)CImgDisplay::screen_width(),
+ sh = (unsigned int)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); nw = mw; }
+ if (nh<mh) { nw = nw*mh/nh; nw+=(nw==0); nh = mh; }
+ if (nw>Mw) { nh = nh*Mw/nw; nh+=(nh==0); nw = Mw; }
+ if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0); nh = Mh; }
+ if (nw<mw) nw = mw;
+ if (nh<mh) nh = mh;
+ return return_y?nh:nw;
+ }
+
+ //@}
+ //------------------------------------------
+ //
+ //! \name Overloaded Operators
+ //@{
+ //------------------------------------------
+
+ //! Display image on associated window.
+ /**
+ \note <tt>disp = img</tt> is equivalent to <tt>disp.display(img)</tt>.
+ **/
+ template<typename t>
+ CImgDisplay& operator=(const CImg<t>& img) {
+ return display(img);
+ }
+
+ //! Display list of images on associated window.
+ /**
+ \note <tt>disp = list</tt> is equivalent to <tt>disp.display(list)</tt>.
+ **/
+ template<typename t>
+ CImgDisplay& operator=(const CImgList<t>& list) {
+ return display(list);
+ }
+
+ //! Construct a display as a copy of another one \inplace.
+ /**
+ \note Equivalent to assign(const CImgDisplay&).
+ **/
+ CImgDisplay& operator=(const CImgDisplay& disp) {
+ return assign(disp);
+ }
+
+ //! Return \c false if display is empty, \c true otherwise.
+ /**
+ \note <tt>if (disp) { ... }</tt> is equivalent to <tt>if (!disp.is_empty()) { ... }</tt>.
+ **/
+ operator bool() const {
+ return !is_empty();
+ }
+
+ //@}
+ //------------------------------------------
+ //
+ //! \name Instance Checking
+ //@{
+ //------------------------------------------
+
+ //! Return \c true if display is empty, \c false otherwise.
+ /**
+ **/
+ bool is_empty() const {
+ return !(_width && _height);
+ }
+
+ //! Return \c true if display is closed (i.e. not visible on the screen), \c false otherwise.
+ /**
+ \note
+ - When a user physically closes the associated window, the display is set to closed.
+ - A closed display is not destroyed. Its associated window can be show again on the screen using show().
+ **/
+ bool is_closed() const {
+ return _is_closed;
+ }
+
+ //! Return \c true if associated window has been resized on the screen, \c false otherwise.
+ /**
+ **/
+ bool is_resized() const {
+ return _is_resized;
+ }
+
+ //! Return \c true if associated window has been moved on the screen, \c false otherwise.
+ /**
+ **/
+ bool is_moved() const {
+ return _is_moved;
+ }
+
+ //! Return \c true if any event has occured on the associated window, \c false otherwise.
+ /**
+ **/
+ bool is_event() const {
+ return _is_event;
+ }
+
+ //! Return \c true if current display is in fullscreen mode, \c false otherwise.
+ /**
+ **/
+ bool is_fullscreen() const {
+ return _is_fullscreen;
+ }
+
+ //! Return \c true if any key is being pressed on the associated window, \c false otherwise.
+ /**
+ \note The methods below do the same only for specific keys.
+ **/
+ bool is_key() const {
+ return _is_keyESC || _is_keyF1 || _is_keyF2 || _is_keyF3 ||
+ _is_keyF4 || _is_keyF5 || _is_keyF6 || _is_keyF7 ||
+ _is_keyF8 || _is_keyF9 || _is_keyF10 || _is_keyF11 ||
+ _is_keyF12 || _is_keyPAUSE || _is_key1 || _is_key2 ||
+ _is_key3 || _is_key4 || _is_key5 || _is_key6 ||
+ _is_key7 || _is_key8 || _is_key9 || _is_key0 ||
+ _is_keyBACKSPACE || _is_keyINSERT || _is_keyHOME ||
+ _is_keyPAGEUP || _is_keyTAB || _is_keyQ || _is_keyW ||
+ _is_keyE || _is_keyR || _is_keyT || _is_keyY ||
+ _is_keyU || _is_keyI || _is_keyO || _is_keyP ||
+ _is_keyDELETE || _is_keyEND || _is_keyPAGEDOWN ||
+ _is_keyCAPSLOCK || _is_keyA || _is_keyS || _is_keyD ||
+ _is_keyF || _is_keyG || _is_keyH || _is_keyJ ||
+ _is_keyK || _is_keyL || _is_keyENTER ||
+ _is_keySHIFTLEFT || _is_keyZ || _is_keyX || _is_keyC ||
+ _is_keyV || _is_keyB || _is_keyN || _is_keyM ||
+ _is_keySHIFTRIGHT || _is_keyARROWUP || _is_keyCTRLLEFT ||
+ _is_keyAPPLEFT || _is_keyALT || _is_keySPACE || _is_keyALTGR ||
+ _is_keyAPPRIGHT || _is_keyMENU || _is_keyCTRLRIGHT ||
+ _is_keyARROWLEFT || _is_keyARROWDOWN || _is_keyARROWRIGHT ||
+ _is_keyPAD0 || _is_keyPAD1 || _is_keyPAD2 ||
+ _is_keyPAD3 || _is_keyPAD4 || _is_keyPAD5 ||
+ _is_keyPAD6 || _is_keyPAD7 || _is_keyPAD8 ||
+ _is_keyPAD9 || _is_keyPADADD || _is_keyPADSUB ||
+ _is_keyPADMUL || _is_keyPADDIV;
+ }
+
+ //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise.
+ /**
+ \param keycode Keycode to test.
+ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+ your code stay portable (see cimg::keyESC).
+ \par Example
+ \code
+ CImgDisplay disp(400,400);
+ while (!disp.is_closed()) {
+ if (disp.key(cimg::keyTAB)) { ... } // Equivalent to 'if (disp.is_keyTAB())'.
+ disp.wait();
+ }
+ \endcode
+ **/
+ bool is_key(const unsigned int keycode) const {
+#define _cimg_iskey_test(k) if (keycode==cimg::key##k) return _is_key##k;
+ _cimg_iskey_test(ESC); _cimg_iskey_test(F1); _cimg_iskey_test(F2); _cimg_iskey_test(F3);
+ _cimg_iskey_test(F4); _cimg_iskey_test(F5); _cimg_iskey_test(F6); _cimg_iskey_test(F7);
+ _cimg_iskey_test(F8); _cimg_iskey_test(F9); _cimg_iskey_test(F10); _cimg_iskey_test(F11);
+ _cimg_iskey_test(F12); _cimg_iskey_test(PAUSE); _cimg_iskey_test(1); _cimg_iskey_test(2);
+ _cimg_iskey_test(3); _cimg_iskey_test(4); _cimg_iskey_test(5); _cimg_iskey_test(6);
+ _cimg_iskey_test(7); _cimg_iskey_test(8); _cimg_iskey_test(9); _cimg_iskey_test(0);
+ _cimg_iskey_test(BACKSPACE); _cimg_iskey_test(INSERT); _cimg_iskey_test(HOME);
+ _cimg_iskey_test(PAGEUP); _cimg_iskey_test(TAB); _cimg_iskey_test(Q); _cimg_iskey_test(W);
+ _cimg_iskey_test(E); _cimg_iskey_test(R); _cimg_iskey_test(T); _cimg_iskey_test(Y);
+ _cimg_iskey_test(U); _cimg_iskey_test(I); _cimg_iskey_test(O); _cimg_iskey_test(P);
+ _cimg_iskey_test(DELETE); _cimg_iskey_test(END); _cimg_iskey_test(PAGEDOWN);
+ _cimg_iskey_test(CAPSLOCK); _cimg_iskey_test(A); _cimg_iskey_test(S); _cimg_iskey_test(D);
+ _cimg_iskey_test(F); _cimg_iskey_test(G); _cimg_iskey_test(H); _cimg_iskey_test(J);
+ _cimg_iskey_test(K); _cimg_iskey_test(L); _cimg_iskey_test(ENTER);
+ _cimg_iskey_test(SHIFTLEFT); _cimg_iskey_test(Z); _cimg_iskey_test(X); _cimg_iskey_test(C);
+ _cimg_iskey_test(V); _cimg_iskey_test(B); _cimg_iskey_test(N); _cimg_iskey_test(M);
+ _cimg_iskey_test(SHIFTRIGHT); _cimg_iskey_test(ARROWUP); _cimg_iskey_test(CTRLLEFT);
+ _cimg_iskey_test(APPLEFT); _cimg_iskey_test(ALT); _cimg_iskey_test(SPACE); _cimg_iskey_test(ALTGR);
+ _cimg_iskey_test(APPRIGHT); _cimg_iskey_test(MENU); _cimg_iskey_test(CTRLRIGHT);
+ _cimg_iskey_test(ARROWLEFT); _cimg_iskey_test(ARROWDOWN); _cimg_iskey_test(ARROWRIGHT);
+ _cimg_iskey_test(PAD0); _cimg_iskey_test(PAD1); _cimg_iskey_test(PAD2);
+ _cimg_iskey_test(PAD3); _cimg_iskey_test(PAD4); _cimg_iskey_test(PAD5);
+ _cimg_iskey_test(PAD6); _cimg_iskey_test(PAD7); _cimg_iskey_test(PAD8);
+ _cimg_iskey_test(PAD9); _cimg_iskey_test(PADADD); _cimg_iskey_test(PADSUB);
+ _cimg_iskey_test(PADMUL); _cimg_iskey_test(PADDIV);
+ return false;
+ }
+
+ //! Return \c true if key specified by given keycode is being pressed on the associated window, \c false otherwise.
+ /**
+ \param keycode C-string containing the keycode label of the key to test.
+ \note Use it when the key you want to test can be dynamically set by the user.
+ \par Example
+ \code
+ CImgDisplay disp(400,400);
+ const char *const keycode = "TAB";
+ while (!disp.is_closed()) {
+ if (disp.is_key(keycode)) { ... } // Equivalent to 'if (disp.is_keyTAB())'.
+ disp.wait();
+ }
+ \endcode
+ **/
+ bool& is_key(const char *const keycode) {
+ static bool f = false;
+ f = false;
+#define _cimg_iskey_test2(k) if (!cimg::strcasecmp(keycode,#k)) return _is_key##k;
+ _cimg_iskey_test2(ESC); _cimg_iskey_test2(F1); _cimg_iskey_test2(F2); _cimg_iskey_test2(F3);
+ _cimg_iskey_test2(F4); _cimg_iskey_test2(F5); _cimg_iskey_test2(F6); _cimg_iskey_test2(F7);
+ _cimg_iskey_test2(F8); _cimg_iskey_test2(F9); _cimg_iskey_test2(F10); _cimg_iskey_test2(F11);
+ _cimg_iskey_test2(F12); _cimg_iskey_test2(PAUSE); _cimg_iskey_test2(1); _cimg_iskey_test2(2);
+ _cimg_iskey_test2(3); _cimg_iskey_test2(4); _cimg_iskey_test2(5); _cimg_iskey_test2(6);
+ _cimg_iskey_test2(7); _cimg_iskey_test2(8); _cimg_iskey_test2(9); _cimg_iskey_test2(0);
+ _cimg_iskey_test2(BACKSPACE); _cimg_iskey_test2(INSERT); _cimg_iskey_test2(HOME);
+ _cimg_iskey_test2(PAGEUP); _cimg_iskey_test2(TAB); _cimg_iskey_test2(Q); _cimg_iskey_test2(W);
+ _cimg_iskey_test2(E); _cimg_iskey_test2(R); _cimg_iskey_test2(T); _cimg_iskey_test2(Y);
+ _cimg_iskey_test2(U); _cimg_iskey_test2(I); _cimg_iskey_test2(O); _cimg_iskey_test2(P);
+ _cimg_iskey_test2(DELETE); _cimg_iskey_test2(END); _cimg_iskey_test2(PAGEDOWN);
+ _cimg_iskey_test2(CAPSLOCK); _cimg_iskey_test2(A); _cimg_iskey_test2(S); _cimg_iskey_test2(D);
+ _cimg_iskey_test2(F); _cimg_iskey_test2(G); _cimg_iskey_test2(H); _cimg_iskey_test2(J);
+ _cimg_iskey_test2(K); _cimg_iskey_test2(L); _cimg_iskey_test2(ENTER);
+ _cimg_iskey_test2(SHIFTLEFT); _cimg_iskey_test2(Z); _cimg_iskey_test2(X); _cimg_iskey_test2(C);
+ _cimg_iskey_test2(V); _cimg_iskey_test2(B); _cimg_iskey_test2(N); _cimg_iskey_test2(M);
+ _cimg_iskey_test2(SHIFTRIGHT); _cimg_iskey_test2(ARROWUP); _cimg_iskey_test2(CTRLLEFT);
+ _cimg_iskey_test2(APPLEFT); _cimg_iskey_test2(ALT); _cimg_iskey_test2(SPACE); _cimg_iskey_test2(ALTGR);
+ _cimg_iskey_test2(APPRIGHT); _cimg_iskey_test2(MENU); _cimg_iskey_test2(CTRLRIGHT);
+ _cimg_iskey_test2(ARROWLEFT); _cimg_iskey_test2(ARROWDOWN); _cimg_iskey_test2(ARROWRIGHT);
+ _cimg_iskey_test2(PAD0); _cimg_iskey_test2(PAD1); _cimg_iskey_test2(PAD2);
+ _cimg_iskey_test2(PAD3); _cimg_iskey_test2(PAD4); _cimg_iskey_test2(PAD5);
+ _cimg_iskey_test2(PAD6); _cimg_iskey_test2(PAD7); _cimg_iskey_test2(PAD8);
+ _cimg_iskey_test2(PAD9); _cimg_iskey_test2(PADADD); _cimg_iskey_test2(PADSUB);
+ _cimg_iskey_test2(PADMUL); _cimg_iskey_test2(PADDIV);
+ return f;
+ }
+
+ //! Return \c true if specified key sequence has been typed on the associated window, \c false otherwise.
+ /**
+ \param keycodes_sequence Buffer of keycodes to test.
+ \param length Number of keys in the \c keycodes_sequence buffer.
+ \param remove_sequence Tells if the key sequence must be removed from the key history, if found.
+ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+ your code stay portable (see cimg::keyESC).
+ \par Example
+ \code
+ CImgDisplay disp(400,400);
+ const unsigned int key_seq[] = { cimg::keyCTRLLEFT, cimg::keyD };
+ while (!disp.is_closed()) {
+ if (disp.is_key_sequence(key_seq,2)) { ... } // Test for the 'CTRL+D' keyboard event.
+ disp.wait();
+ }
+ \endcode
+ **/
+ bool is_key_sequence(const unsigned int *const keycodes_sequence, const unsigned int length,
+ const bool remove_sequence=false) {
+ if (keycodes_sequence && length) {
+ const unsigned int
+ *const ps_end = keycodes_sequence + length - 1,
+ *const pk_end = (unsigned int*)_keys + 1 + 128 - length,
+ k = *ps_end;
+ for (unsigned int *pk = (unsigned int*)_keys; pk<pk_end; ) {
+ if (*(pk++)==k) {
+ bool res = true;
+ const unsigned int *ps = ps_end, *pk2 = pk;
+ for (unsigned int i = 1; i<length; ++i) res = (*(--ps)==*(pk2++));
+ if (res) {
+ if (remove_sequence) std::memset((void*)(pk - 1),0,sizeof(unsigned int)*length);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+#define _cimg_iskey_def(k) \
+ bool is_key##k() const { \
+ return _is_key##k; \
+ }
+
+ //! Return \c true if the \c ESC key is being pressed on the associated window, \c false otherwise.
+ /**
+ \note Similar methods exist for all keys managed by \CImg (see cimg::keyESC).
+ **/
+ _cimg_iskey_def(ESC); _cimg_iskey_def(F1); _cimg_iskey_def(F2); _cimg_iskey_def(F3);
+ _cimg_iskey_def(F4); _cimg_iskey_def(F5); _cimg_iskey_def(F6); _cimg_iskey_def(F7);
+ _cimg_iskey_def(F8); _cimg_iskey_def(F9); _cimg_iskey_def(F10); _cimg_iskey_def(F11);
+ _cimg_iskey_def(F12); _cimg_iskey_def(PAUSE); _cimg_iskey_def(1); _cimg_iskey_def(2);
+ _cimg_iskey_def(3); _cimg_iskey_def(4); _cimg_iskey_def(5); _cimg_iskey_def(6);
+ _cimg_iskey_def(7); _cimg_iskey_def(8); _cimg_iskey_def(9); _cimg_iskey_def(0);
+ _cimg_iskey_def(BACKSPACE); _cimg_iskey_def(INSERT); _cimg_iskey_def(HOME);
+ _cimg_iskey_def(PAGEUP); _cimg_iskey_def(TAB); _cimg_iskey_def(Q); _cimg_iskey_def(W);
+ _cimg_iskey_def(E); _cimg_iskey_def(R); _cimg_iskey_def(T); _cimg_iskey_def(Y);
+ _cimg_iskey_def(U); _cimg_iskey_def(I); _cimg_iskey_def(O); _cimg_iskey_def(P);
+ _cimg_iskey_def(DELETE); _cimg_iskey_def(END); _cimg_iskey_def(PAGEDOWN);
+ _cimg_iskey_def(CAPSLOCK); _cimg_iskey_def(A); _cimg_iskey_def(S); _cimg_iskey_def(D);
+ _cimg_iskey_def(F); _cimg_iskey_def(G); _cimg_iskey_def(H); _cimg_iskey_def(J);
+ _cimg_iskey_def(K); _cimg_iskey_def(L); _cimg_iskey_def(ENTER);
+ _cimg_iskey_def(SHIFTLEFT); _cimg_iskey_def(Z); _cimg_iskey_def(X); _cimg_iskey_def(C);
+ _cimg_iskey_def(V); _cimg_iskey_def(B); _cimg_iskey_def(N); _cimg_iskey_def(M);
+ _cimg_iskey_def(SHIFTRIGHT); _cimg_iskey_def(ARROWUP); _cimg_iskey_def(CTRLLEFT);
+ _cimg_iskey_def(APPLEFT); _cimg_iskey_def(ALT); _cimg_iskey_def(SPACE); _cimg_iskey_def(ALTGR);
+ _cimg_iskey_def(APPRIGHT); _cimg_iskey_def(MENU); _cimg_iskey_def(CTRLRIGHT);
+ _cimg_iskey_def(ARROWLEFT); _cimg_iskey_def(ARROWDOWN); _cimg_iskey_def(ARROWRIGHT);
+ _cimg_iskey_def(PAD0); _cimg_iskey_def(PAD1); _cimg_iskey_def(PAD2);
+ _cimg_iskey_def(PAD3); _cimg_iskey_def(PAD4); _cimg_iskey_def(PAD5);
+ _cimg_iskey_def(PAD6); _cimg_iskey_def(PAD7); _cimg_iskey_def(PAD8);
+ _cimg_iskey_def(PAD9); _cimg_iskey_def(PADADD); _cimg_iskey_def(PADSUB);
+ _cimg_iskey_def(PADMUL); _cimg_iskey_def(PADDIV);
+
+ //@}
+ //------------------------------------------
+ //
+ //! \name Instance Characteristics
+ //@{
+ //------------------------------------------
+
+#if cimg_display==0
+
+ //! Return width of the screen (current resolution along the X-axis).
+ /**
+ **/
+ static int screen_width() {
+ _no_display_exception();
+ return 0;
+ }
+
+ //! Return height of the screen (current resolution along the Y-axis).
+ /**
+ **/
+ static int screen_height() {
+ _no_display_exception();
+ return 0;
+ }
+
+#endif
+
+ //! Return display width.
+ /**
+ \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance)
+ may be different from the actual width of the associated window.
+ **/
+ int width() const {
+ return (int)_width;
+ }
+
+ //! Return display height.
+ /**
+ \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance)
+ may be different from the actual height of the associated window.
+ **/
+ int height() const {
+ return (int)_height;
+ }
+
+ //! Return normalization type of the display.
+ /**
+ The normalization type tells about how the values of an input image are normalized by the CImgDisplay to be
+ correctly displayed. The range of values for pixels displayed on screen is <tt>[0,255]</tt>.
+ If the range of values of the data to display is different, a normalization may be required for displaying
+ the data in a correct way. The normalization type can be one of:
+ - \c 0: Value normalization is disabled. It is then assumed that all input data to be displayed by the
+ CImgDisplay instance have values in range <tt>[0,255]</tt>.
+ - \c 1: Value normalization is always performed (this is the default behavior).
+ Before displaying an input image, its values will be (virtually) stretched
+ in range <tt>[0,255]</tt>, so that the contrast of the displayed pixels will be maximum.
+ Use this mode for images whose minimum and maximum values are not prescribed to known values
+ (e.g. float-valued images).
+ Note that when normalized versions of images are computed for display purposes, the actual values of these
+ images are not modified.
+ - \c 2: Value normalization is performed once (on the first image display), then the same normalization
+ coefficients are kept for next displayed frames.
+ - \c 3: Value normalization depends on the pixel type of the data to display. For integer pixel types,
+ the normalization is done regarding the minimum/maximum values of the type (no normalization occurs then
+ for <tt>unsigned char</tt>).
+ For float-valued pixel types, the normalization is done regarding the minimum/maximum value of the image
+ data instead.
+ **/
+ unsigned int normalization() const {
+ return _normalization;
+ }
+
+ //! Return title of the associated window as a C-string.
+ /**
+ \note Window title may be not visible, depending on the used window manager or if the current display is
+ in fullscreen mode.
+ **/
+ const char *title() const {
+ return _title?_title:"";
+ }
+
+ //! Return width of the associated window.
+ /**
+ \note The width of the display (i.e. the width of the pixel data buffer associated to the CImgDisplay instance)
+ may be different from the actual width of the associated window.
+ **/
+ int window_width() const {
+ return (int)_window_width;
+ }
+
+ //! Return height of the associated window.
+ /**
+ \note The height of the display (i.e. the height of the pixel data buffer associated to the CImgDisplay instance)
+ may be different from the actual height of the associated window.
+ **/
+ int window_height() const {
+ return (int)_window_height;
+ }
+
+ //! Return X-coordinate of the associated window.
+ /**
+ \note The returned coordinate corresponds to the location of the upper-left corner of the associated window.
+ **/
+ int window_x() const {
+ return _window_x;
+ }
+
+ //! Return Y-coordinate of the associated window.
+ /**
+ \note The returned coordinate corresponds to the location of the upper-left corner of the associated window.
+ **/
+ int window_y() const {
+ return _window_y;
+ }
+
+ //! Return X-coordinate of the mouse pointer.
+ /**
+ \note
+ - If the mouse pointer is outside window area, \c -1 is returned.
+ - Otherwise, the returned value is in the range [0,width()-1].
+ **/
+ int mouse_x() const {
+ return _mouse_x;
+ }
+
+ //! Return Y-coordinate of the mouse pointer.
+ /**
+ \note
+ - If the mouse pointer is outside window area, \c -1 is returned.
+ - Otherwise, the returned value is in the range [0,height()-1].
+ **/
+ int mouse_y() const {
+ return _mouse_y;
+ }
+
+ //! Return current state of the mouse buttons.
+ /**
+ \note Three mouse buttons can be managed. If one button is pressed, its corresponding bit in the returned
+ value is set:
+ - bit \c 0 (value \c 0x1): State of the left mouse button.
+ - bit \c 1 (value \c 0x2): State of the right mouse button.
+ - bit \c 2 (value \c 0x4): State of the middle mouse button.
+
+ Several bits can be activated if more than one button are pressed at the same time.
+ \par Example
+ \code
+ CImgDisplay disp(400,400);
+ while (!disp.is_closed()) {
+ if (disp.button()&1) { // Left button clicked.
+ ...
+ }
+ if (disp.button()&2) { // Right button clicked.
+ ...
+ }
+ if (disp.button()&4) { // Middle button clicked.
+ ...
+ }
+ disp.wait();
+ }
+ \endcode
+ **/
+ unsigned int button() const {
+ return _button;
+ }
+
+ //! Return current state of the mouse wheel.
+ /**
+ \note
+ - The returned value can be positive or negative depending on whether the mouse wheel has been scrolled
+ forward or backward.
+ - Scrolling the wheel forward add \c 1 to the wheel value.
+ - Scrolling the wheel backward substract \c 1 to the wheel value.
+ - The returned value cumulates the number of forward of backward scrolls since the creation of the display,
+ or since the last reset of the wheel value (using set_wheel()). It is strongly recommended to quickly reset
+ the wheel counter when an action has been performed regarding the current wheel value.
+ Otherwise, the returned wheel value may be for instance \c 0 despite the fact that many scrolls have been done
+ (as many in forward as in backward directions).
+ \par Example
+ \code
+ CImgDisplay disp(400,400);
+ while (!disp.is_closed()) {
+ if (disp.wheel()) {
+ int counter = disp.wheel(); // Read the state of the mouse wheel.
+ ... // Do what you want with 'counter'.
+ disp.set_wheel(); // Reset the wheel value to 0.
+ }
+ disp.wait();
+ }
+ \endcode
+ **/
+ int wheel() const {
+ return _wheel;
+ }
+
+ //! Return one entry from the pressed keys history.
+ /**
+ \param pos Indice to read from the pressed keys history (indice \c 0 corresponds to latest entry).
+ \return Keycode of a pressed key or \c 0 for a released key.
+ \note
+ - Each CImgDisplay stores a history of the pressed keys in a buffer of size \c 128. When a new key is pressed,
+ its keycode is stored in the pressed keys history. When a key is released, \c 0 is put instead.
+ This means that up to the 64 last pressed keys may be read from the pressed keys history.
+ When a new value is stored, the pressed keys history is shifted so that the latest entry is always
+ stored at position \c 0.
+ - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+ your code stay portable (see cimg::keyESC).
+ **/
+ unsigned int key(const unsigned int pos=0) const {
+ return pos<128?_keys[pos]:0;
+ }
+
+ //! Return one entry from the released keys history.
+ /**
+ \param pos Indice to read from the released keys history (indice \c 0 corresponds to latest entry).
+ \return Keycode of a released key or \c 0 for a pressed key.
+ \note
+ - Each CImgDisplay stores a history of the released keys in a buffer of size \c 128. When a new key is released,
+ its keycode is stored in the pressed keys history. When a key is pressed, \c 0 is put instead.
+ This means that up to the 64 last released keys may be read from the released keys history.
+ When a new value is stored, the released keys history is shifted so that the latest entry is always
+ stored at position \c 0.
+ - Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+ your code stay portable (see cimg::keyESC).
+ **/
+ unsigned int released_key(const unsigned int pos=0) const {
+ return pos<128?_released_keys[pos]:0;
+ }
+
+ //! Return keycode corresponding to the specified string.
+ /**
+ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+ your code stay portable (see cimg::keyESC).
+ \par Example
+ \code
+ const unsigned int keyTAB = CImgDisplay::keycode("TAB"); // Return cimg::keyTAB.
+ \endcode
+ **/
+ static unsigned int keycode(const char *const keycode) {
+#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k;
+ _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3);
+ _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7);
+ _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11);
+ _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2);
+ _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6);
+ _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0);
+ _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME);
+ _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W);
+ _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y);
+ _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P);
+ _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN);
+ _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D);
+ _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J);
+ _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER);
+ _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C);
+ _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M);
+ _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT);
+ _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR);
+ _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT);
+ _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT);
+ _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2);
+ _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5);
+ _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8);
+ _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB);
+ _cimg_keycode(PADMUL); _cimg_keycode(PADDIV);
+ return 0;
+ }
+
+ //! Return the current refresh rate, in frames per second.
+ /**
+ \note Returns a significant value when the current instance is used to display successive frames.
+ It measures the delay between successive calls to frames_per_second().
+ **/
+ float frames_per_second() {
+ if (!_fps_timer) _fps_timer = cimg::time();
+ const float delta = (cimg::time() - _fps_timer)/1000.0f;
+ ++_fps_frames;
+ if (delta>=1) {
+ _fps_fps = _fps_frames/delta;
+ _fps_frames = 0;
+ _fps_timer = cimg::time();
+ }
+ return _fps_fps;
+ }
+
+ //@}
+ //---------------------------------------
+ //
+ //! \name Window Manipulation
+ //@{
+ //---------------------------------------
+
+#if cimg_display==0
+
+ //! Display image on associated window.
+ /**
+ \param img Input image to display.
+ \note This method returns immediately.
+ **/
+ template<typename T>
+ CImgDisplay& display(const CImg<T>& img) {
+ return assign(img);
+ }
+
+#endif
+
+ //! Display list of images on associated window.
+ /**
+ \param list List of images to display.
+ \param axis Axis used to append the images along, for the visualization (can be \c x, \c y, \c z or \c c).
+ \param align Relative position of aligned images when displaying lists with images of different sizes
+ (\c 0 for upper-left, \c 0.5 for centering and \c 1 for lower-right).
+ \note This method returns immediately.
+ **/
+ template<typename T>
+ CImgDisplay& display(const CImgList<T>& list, const char axis='x', const float align=0) {
+ if (list._width==1) {
+ const CImg<T>& img = list[0];
+ if (img._depth==1 && (img._spectrum==1 || img._spectrum>=3) && _normalization!=1) return display(img);
+ }
+ CImgList<typename CImg<T>::ucharT> visu(list._width);
+ unsigned int dims = 0;
+ cimglist_for(list,l) {
+ const CImg<T>& img = list._data[l];
+ img.__get_select(*this,_normalization,(img._width - 1)/2,(img._height - 1)/2,
+ (img._depth - 1)/2).move_to(visu[l]);
+ dims = std::max(dims,visu[l]._spectrum);
+ }
+ cimglist_for(list,l) if (visu[l]._spectrum<dims) visu[l].resize(-100,-100,-100,dims,1);
+ visu.get_append(axis,align).display(*this);
+ return *this;
+ }
+
+#if cimg_display==0
+
+ //! Show (closed) associated window on the screen.
+ /**
+ \note
+ - Force the associated window of a display to be visible on the screen, even if it has been closed before.
+ - Using show() on a visible display does nothing.
+ **/
+ CImgDisplay& show() {
+ return assign();
+ }
+
+ //! Close (visible) associated window and make it disappear from the screen.
+ /**
+ \note
+ - A closed display only means the associated window is not visible anymore. This does not mean the display has
+ been destroyed.
+ Use show() to make the associated window reappear.
+ - Using close() on a closed display does nothing.
+ **/
+ CImgDisplay& close() {
+ return assign();
+ }
+
+ //! Move associated window to a new location.
+ /**
+ \param pos_x X-coordinate of the new window location.
+ \param pos_y Y-coordinate of the new window location.
+ \note Depending on the window manager behavior, this method may not succeed (no exceptions are thrown
+ nevertheless).
+ **/
+ CImgDisplay& move(const int pos_x, const int pos_y) {
+ return assign(pos_x,pos_y);
+ }
+
+#endif
+
+ //! Resize display to the size of the associated window.
+ /**
+ \param force_redraw Tells if the previous window content must be updated and refreshed as well.
+ \note
+ - Calling this method ensures that width() and window_width() become equal, as well as height() and
+ window_height().
+ - The associated window is also resized to specified dimensions.
+ **/
+ CImgDisplay& resize(const bool force_redraw=true) {
+ resize(window_width(),window_height(),force_redraw);
+ return *this;
+ }
+
+#if cimg_display==0
+
+ //! Resize display to the specified size.
+ /**
+ \param width Requested display width.
+ \param height Requested display height.
+ \param force_redraw Tells if the previous window content must be updated and refreshed as well.
+ \note The associated window is also resized to specified dimensions.
+ **/
+ CImgDisplay& resize(const int width, const int height, const bool force_redraw=true) {
+ return assign(width,height,0,3,force_redraw);
+ }
+
+#endif
+
+ //! Resize display to the size of an input image.
+ /**
+ \param img Input image to take size from.
+ \param force_redraw Tells if the previous window content must be resized and updated as well.
+ \note
+ - Calling this method ensures that width() and <tt>img.width()</tt> become equal, as well as height() and
+ <tt>img.height()</tt>.
+ - The associated window is also resized to specified dimensions.
+ **/
+ template<typename T>
+ CImgDisplay& resize(const CImg<T>& img, const bool force_redraw=true) {
+ return resize(img._width,img._height,force_redraw);
+ }
+
+ //! Resize display to the size of another CImgDisplay instance.
+ /**
+ \param disp Input display to take size from.
+ \param force_redraw Tells if the previous window content must be resized and updated as well.
+ \note
+ - Calling this method ensures that width() and <tt>disp.width()</tt> become equal, as well as height() and
+ <tt>disp.height()</tt>.
+ - The associated window is also resized to specified dimensions.
+ **/
+ CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) {
+ return resize(disp.width(),disp.height(),force_redraw);
+ }
+
+ // [internal] 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 normalization type.
+ /**
+ \param normalization New normalization mode.
+ **/
+ CImgDisplay& set_normalization(const unsigned int normalization) {
+ _normalization = normalization;
+ _min = _max = 0;
+ return *this;
+ }
+
+#if cimg_display==0
+
+ //! Set title of the associated window.
+ /**
+ \param format C-string containing the format of the title, as with <tt>std::printf()</tt>.
+ \warning As the first argument is a format string, it is highly recommended to write
+ \code
+ disp.set_title("%s",window_title);
+ \endcode
+ instead of
+ \code
+ disp.set_title(window_title);
+ \endcode
+ if \c window_title can be arbitrary, to prevent nasty memory access.
+ **/
+ CImgDisplay& set_title(const char *const format, ...) {
+ return assign(0,0,format);
+ }
+
+#endif
+
+ //! Enable or disable fullscreen mode.
+ /**
+ \param is_fullscreen Tells is the fullscreen mode must be activated or not.
+ \param force_redraw Tells if the previous window content must be displayed as well.
+ \note
+ - When the fullscreen mode is enabled, the associated window fills the entire screen but the size of the
+ current display is not modified.
+ - The screen resolution may be switched to fit the associated window size and ensure it appears the largest
+ as possible.
+ For X-Window (X11) users, the configuration flag \c cimg_use_xrandr has to be set to allow the screen
+ resolution change (requires the X11 extensions to be enabled).
+ **/
+ CImgDisplay& set_fullscreen(const bool is_fullscreen, const bool force_redraw=true) {
+ if (is_empty() || _is_fullscreen==is_fullscreen) return *this;
+ return toggle_fullscreen(force_redraw);
+ }
+
+#if cimg_display==0
+
+ //! Toggle fullscreen mode.
+ /**
+ \param force_redraw Tells if the previous window content must be displayed as well.
+ \note Enable fullscreen mode if it was not enabled, and disable it otherwise.
+ **/
+ CImgDisplay& toggle_fullscreen(const bool force_redraw=true) {
+ return assign(_width,_height,0,3,force_redraw);
+ }
+
+ //! Show mouse pointer.
+ /**
+ \note Depending on the window manager behavior, this method may not succeed
+ (no exceptions are thrown nevertheless).
+ **/
+ CImgDisplay& show_mouse() {
+ return assign();
+ }
+
+ //! Hide mouse pointer.
+ /**
+ \note Depending on the window manager behavior, this method may not succeed
+ (no exceptions are thrown nevertheless).
+ **/
+ CImgDisplay& hide_mouse() {
+ return assign();
+ }
+
+ //! Move mouse pointer to a specified location.
+ /**
+ \note Depending on the window manager behavior, this method may not succeed
+ (no exceptions are thrown nevertheless).
+ **/
+ CImgDisplay& set_mouse(const int pos_x, const int pos_y) {
+ return assign(pos_x,pos_y);
+ }
+
+#endif
+
+ //! Simulate a mouse button release event.
+ /**
+ \note All mouse buttons are considered released at the same time.
+ **/
+ CImgDisplay& set_button() {
+ _button = 0;
+ _is_event = true;
+#if cimg_display==1
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+#elif cimg_display==2
+ SetEvent(cimg::Win32_attr().wait_event);
+#endif
+ return *this;
+ }
+
+ //! Simulate a mouse button press or release event.
+ /**
+ \param button Buttons event code, where each button is associated to a single bit.
+ \param is_pressed Tells if the mouse button is considered as pressed or released.
+ **/
+ CImgDisplay& set_button(const unsigned int button, const bool is_pressed=true) {
+ const unsigned int buttoncode = button==1U?1U:button==2U?2U:button==3U?4U:0U;
+ if (is_pressed) _button |= buttoncode; else _button &= ~buttoncode;
+ _is_event = buttoncode?true:false;
+ if (buttoncode) {
+#if cimg_display==1
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+#elif cimg_display==2
+ SetEvent(cimg::Win32_attr().wait_event);
+#endif
+ }
+ return *this;
+ }
+
+ //! Flush all mouse wheel events.
+ /**
+ \note Make wheel() to return \c 0, if called afterwards.
+ **/
+ CImgDisplay& set_wheel() {
+ _wheel = 0;
+ _is_event = true;
+#if cimg_display==1
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+#elif cimg_display==2
+ SetEvent(cimg::Win32_attr().wait_event);
+#endif
+ return *this;
+ }
+
+ //! Simulate a wheel event.
+ /**
+ \param amplitude Amplitude of the wheel scrolling to simulate.
+ \note Make wheel() to return \c amplitude, if called afterwards.
+ **/
+ CImgDisplay& set_wheel(const int amplitude) {
+ _wheel+=amplitude;
+ _is_event = amplitude?true:false;
+ if (amplitude) {
+#if cimg_display==1
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+#elif cimg_display==2
+ SetEvent(cimg::Win32_attr().wait_event);
+#endif
+ }
+ return *this;
+ }
+
+ //! Flush all key events.
+ /**
+ \note Make key() to return \c 0, if called afterwards.
+ **/
+ CImgDisplay& set_key() {
+ std::memset((void*)_keys,0,128*sizeof(unsigned int));
+ std::memset((void*)_released_keys,0,128*sizeof(unsigned int));
+ _is_keyESC = _is_keyF1 = _is_keyF2 = _is_keyF3 = _is_keyF4 = _is_keyF5 = _is_keyF6 = _is_keyF7 = _is_keyF8 =
+ _is_keyF9 = _is_keyF10 = _is_keyF11 = _is_keyF12 = _is_keyPAUSE = _is_key1 = _is_key2 = _is_key3 = _is_key4 =
+ _is_key5 = _is_key6 = _is_key7 = _is_key8 = _is_key9 = _is_key0 = _is_keyBACKSPACE = _is_keyINSERT =
+ _is_keyHOME = _is_keyPAGEUP = _is_keyTAB = _is_keyQ = _is_keyW = _is_keyE = _is_keyR = _is_keyT = _is_keyY =
+ _is_keyU = _is_keyI = _is_keyO = _is_keyP = _is_keyDELETE = _is_keyEND = _is_keyPAGEDOWN = _is_keyCAPSLOCK =
+ _is_keyA = _is_keyS = _is_keyD = _is_keyF = _is_keyG = _is_keyH = _is_keyJ = _is_keyK = _is_keyL =
+ _is_keyENTER = _is_keySHIFTLEFT = _is_keyZ = _is_keyX = _is_keyC = _is_keyV = _is_keyB = _is_keyN =
+ _is_keyM = _is_keySHIFTRIGHT = _is_keyARROWUP = _is_keyCTRLLEFT = _is_keyAPPLEFT = _is_keyALT = _is_keySPACE =
+ _is_keyALTGR = _is_keyAPPRIGHT = _is_keyMENU = _is_keyCTRLRIGHT = _is_keyARROWLEFT = _is_keyARROWDOWN =
+ _is_keyARROWRIGHT = _is_keyPAD0 = _is_keyPAD1 = _is_keyPAD2 = _is_keyPAD3 = _is_keyPAD4 = _is_keyPAD5 =
+ _is_keyPAD6 = _is_keyPAD7 = _is_keyPAD8 = _is_keyPAD9 = _is_keyPADADD = _is_keyPADSUB = _is_keyPADMUL =
+ _is_keyPADDIV = false;
+ _is_event = true;
+#if cimg_display==1
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+#elif cimg_display==2
+ SetEvent(cimg::Win32_attr().wait_event);
+#endif
+ return *this;
+ }
+
+ //! Simulate a keyboard press/release event.
+ /**
+ \param keycode Keycode of the associated key.
+ \param is_pressed Tells if the key is considered as pressed or released.
+ \note Keycode constants are defined in the cimg namespace and are architecture-dependent. Use them to ensure
+ your code stay portable (see cimg::keyESC).
+ **/
+ CImgDisplay& set_key(const unsigned int keycode, const bool is_pressed=true) {
+#define _cimg_set_key(k) if (keycode==cimg::key##k) _is_key##k = is_pressed;
+ _cimg_set_key(ESC); _cimg_set_key(F1); _cimg_set_key(F2); _cimg_set_key(F3);
+ _cimg_set_key(F4); _cimg_set_key(F5); _cimg_set_key(F6); _cimg_set_key(F7);
+ _cimg_set_key(F8); _cimg_set_key(F9); _cimg_set_key(F10); _cimg_set_key(F11);
+ _cimg_set_key(F12); _cimg_set_key(PAUSE); _cimg_set_key(1); _cimg_set_key(2);
+ _cimg_set_key(3); _cimg_set_key(4); _cimg_set_key(5); _cimg_set_key(6);
+ _cimg_set_key(7); _cimg_set_key(8); _cimg_set_key(9); _cimg_set_key(0);
+ _cimg_set_key(BACKSPACE); _cimg_set_key(INSERT); _cimg_set_key(HOME);
+ _cimg_set_key(PAGEUP); _cimg_set_key(TAB); _cimg_set_key(Q); _cimg_set_key(W);
+ _cimg_set_key(E); _cimg_set_key(R); _cimg_set_key(T); _cimg_set_key(Y);
+ _cimg_set_key(U); _cimg_set_key(I); _cimg_set_key(O); _cimg_set_key(P);
+ _cimg_set_key(DELETE); _cimg_set_key(END); _cimg_set_key(PAGEDOWN);
+ _cimg_set_key(CAPSLOCK); _cimg_set_key(A); _cimg_set_key(S); _cimg_set_key(D);
+ _cimg_set_key(F); _cimg_set_key(G); _cimg_set_key(H); _cimg_set_key(J);
+ _cimg_set_key(K); _cimg_set_key(L); _cimg_set_key(ENTER);
+ _cimg_set_key(SHIFTLEFT); _cimg_set_key(Z); _cimg_set_key(X); _cimg_set_key(C);
+ _cimg_set_key(V); _cimg_set_key(B); _cimg_set_key(N); _cimg_set_key(M);
+ _cimg_set_key(SHIFTRIGHT); _cimg_set_key(ARROWUP); _cimg_set_key(CTRLLEFT);
+ _cimg_set_key(APPLEFT); _cimg_set_key(ALT); _cimg_set_key(SPACE); _cimg_set_key(ALTGR);
+ _cimg_set_key(APPRIGHT); _cimg_set_key(MENU); _cimg_set_key(CTRLRIGHT);
+ _cimg_set_key(ARROWLEFT); _cimg_set_key(ARROWDOWN); _cimg_set_key(ARROWRIGHT);
+ _cimg_set_key(PAD0); _cimg_set_key(PAD1); _cimg_set_key(PAD2);
+ _cimg_set_key(PAD3); _cimg_set_key(PAD4); _cimg_set_key(PAD5);
+ _cimg_set_key(PAD6); _cimg_set_key(PAD7); _cimg_set_key(PAD8);
+ _cimg_set_key(PAD9); _cimg_set_key(PADADD); _cimg_set_key(PADSUB);
+ _cimg_set_key(PADMUL); _cimg_set_key(PADDIV);
+ if (is_pressed) {
+ if (*_keys)
+ std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int));
+ *_keys = keycode;
+ if (*_released_keys) {
+ std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int));
+ *_released_keys = 0;
+ }
+ } else {
+ if (*_keys) {
+ std::memmove((void*)(_keys + 1),(void*)_keys,127*sizeof(unsigned int));
+ *_keys = 0;
+ }
+ if (*_released_keys)
+ std::memmove((void*)(_released_keys + 1),(void*)_released_keys,127*sizeof(unsigned int));
+ *_released_keys = keycode;
+ }
+ _is_event = keycode?true:false;
+ if (keycode) {
+#if cimg_display==1
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+#elif cimg_display==2
+ SetEvent(cimg::Win32_attr().wait_event);
+#endif
+ }
+ return *this;
+ }
+
+ //! Flush all display events.
+ /**
+ \note Remove all passed events from the current display.
+ **/
+ CImgDisplay& flush() {
+ set_key().set_button().set_wheel();
+ _is_resized = _is_moved = _is_event = false;
+ _fps_timer = _fps_frames = _timer = 0;
+ _fps_fps = 0;
+ return *this;
+ }
+
+ //! Wait for any user event occuring on the current display.
+ CImgDisplay& wait() {
+ wait(*this);
+ return *this;
+ }
+
+ //! Wait for a given number of milliseconds since the last call to wait().
+ /**
+ \param milliseconds Number of milliseconds to wait for.
+ \note Similar to cimg::wait().
+ **/
+ CImgDisplay& wait(const unsigned int milliseconds) {
+ cimg::_wait(milliseconds,_timer);
+ return *this;
+ }
+
+ //! Wait for any event occuring on the display \c disp1.
+ static void wait(CImgDisplay& disp1) {
+ disp1._is_event = false;
+ 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 = false;
+ 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 = false;
+ 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 = false;
+ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) &&
+ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all();
+ }
+
+ //! 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 = false;
+ 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 = false;
+ 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 = false;
+ 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 = false;
+ 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 = false;
+ 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 = false;
+ while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed || !disp5._is_closed ||
+ !disp6._is_closed || !disp7._is_closed || !disp8._is_closed || !disp9._is_closed || !disp10._is_closed) &&
+ !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event && !disp5._is_event &&
+ !disp6._is_event && !disp7._is_event && !disp8._is_event && !disp9._is_event && !disp10._is_event)
+ wait_all();
+ }
+
+#if cimg_display==0
+
+ //! Wait for any window event occuring in any opened CImgDisplay.
+ static void wait_all() {
+ return _no_display_exception();
+ }
+
+ //! Render image into internal display buffer.
+ /**
+ \param img Input image data to render.
+ \note
+ - Convert image data representation into the internal display buffer (architecture-dependent structure).
+ - The content of the associated window is not modified, until paint() is called.
+ - Should not be used for common CImgDisplay uses, since display() is more useful.
+ **/
+ template<typename T>
+ CImgDisplay& render(const CImg<T>& img) {
+ return assign(img);
+ }
+
+ //! Paint internal display buffer on associated window.
+ /**
+ \note
+ - Update the content of the associated window with the internal display buffer, e.g. after a render() call.
+ - Should not be used for common CImgDisplay uses, since display() is more useful.
+ **/
+ CImgDisplay& paint() {
+ return assign();
+ }
+
+
+ //! Take a snapshot of the current screen content.
+ /**
+ \param x0 X-coordinate of the upper left corner.
+ \param y0 Y-coordinate of the upper left corner.
+ \param x1 X-coordinate of the lower right corner.
+ \param y1 Y-coordinate of the lower right corner.
+ \param[out] img Output screenshot. Can be empty on input
+ **/
+ template<typename T>
+ static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg<T>& img) {
+ cimg::unused(x0,y0,x1,y1,&img);
+ _no_display_exception();
+ }
+
+ //! Take a snapshot of the associated window content.
+ /**
+ \param[out] img Output snapshot. Can be empty on input.
+ **/
+ template<typename T>
+ const CImgDisplay& snapshot(CImg<T>& img) const {
+ cimg::unused(img);
+ _no_display_exception();
+ return *this;
+ }
+#endif
+
+ // X11-based implementation
+ //--------------------------
+#if cimg_display==1
+
+ Atom _wm_window_atom, _wm_protocol_atom;
+ Window _window, _background_window;
+ Colormap _colormap;
+ XImage *_image;
+ void *_data;
+#ifdef cimg_use_xshm
+ XShmSegmentInfo *_shminfo;
+#endif
+
+ static int screen_width() {
+ Display *const dpy = cimg::X11_attr().display;
+ int res = 0;
+ if (!dpy) {
+ Display *const _dpy = XOpenDisplay(0);
+ if (!_dpy)
+ throw CImgDisplayException("CImgDisplay::screen_width(): Failed to open X11 display.");
+ res = DisplayWidth(_dpy,DefaultScreen(_dpy));
+ XCloseDisplay(_dpy);
+ } else {
+#ifdef cimg_use_xrandr
+ if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution)
+ res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width;
+ else res = DisplayWidth(dpy,DefaultScreen(dpy));
+#else
+ res = DisplayWidth(dpy,DefaultScreen(dpy));
+#endif
+ }
+ return res;
+ }
+
+ static int screen_height() {
+ Display *const dpy = cimg::X11_attr().display;
+ int res = 0;
+ if (!dpy) {
+ Display *const _dpy = XOpenDisplay(0);
+ if (!_dpy)
+ throw CImgDisplayException("CImgDisplay::screen_height(): Failed to open X11 display.");
+ res = DisplayHeight(_dpy,DefaultScreen(_dpy));
+ XCloseDisplay(_dpy);
+ } else {
+#ifdef cimg_use_xrandr
+ if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution)
+ res = cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height;
+ else res = DisplayHeight(dpy,DefaultScreen(dpy));
+#else
+ res = DisplayHeight(dpy,DefaultScreen(dpy));
+#endif
+ }
+ return res;
+ }
+
+ static void wait_all() {
+ if (!cimg::X11_attr().display) return;
+ pthread_mutex_lock(&cimg::X11_attr().wait_event_mutex);
+ pthread_cond_wait(&cimg::X11_attr().wait_event,&cimg::X11_attr().wait_event_mutex);
+ pthread_mutex_unlock(&cimg::X11_attr().wait_event_mutex);
+ }
+
+ void _handle_events(const XEvent *const pevent) {
+ Display *const dpy = cimg::X11_attr().display;
+ XEvent event = *pevent;
+ switch (event.type) {
+ case ClientMessage : {
+ if ((int)event.xclient.message_type==(int)_wm_protocol_atom &&
+ (int)event.xclient.data.l[0]==(int)_wm_window_atom) {
+ XUnmapWindow(cimg::X11_attr().display,_window);
+ _is_closed = _is_event = true;
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+ }
+ } break;
+ case ConfigureNotify : {
+ while (XCheckWindowEvent(dpy,_window,StructureNotifyMask,&event)) {}
+ const unsigned int nw = event.xconfigure.width, nh = event.xconfigure.height;
+ const int nx = event.xconfigure.x, ny = event.xconfigure.y;
+ if (nw && nh && (nw!=_window_width || nh!=_window_height)) {
+ _window_width = nw; _window_height = nh; _mouse_x = _mouse_y = -1;
+ XResizeWindow(dpy,_window,_window_width,_window_height);
+ _is_resized = _is_event = true;
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+ }
+ if (nx!=_window_x || ny!=_window_y) {
+ _window_x = nx; _window_y = ny; _is_moved = _is_event = true;
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+ }
+ } break;
+ case Expose : {
+ while (XCheckWindowEvent(dpy,_window,ExposureMask,&event)) {}
+ _paint(false);
+ if (_is_fullscreen) {
+ XWindowAttributes attr;
+ XGetWindowAttributes(dpy,_window,&attr);
+ while (attr.map_state!=IsViewable) XSync(dpy,0);
+ XSetInputFocus(dpy,_window,RevertToParent,CurrentTime);
+ }
+ } break;
+ case ButtonPress : {
+ do {
+ _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y;
+ if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+ switch (event.xbutton.button) {
+ case 1 : set_button(1); break;
+ case 3 : set_button(2); break;
+ case 2 : set_button(3); break;
+ }
+ } while (XCheckWindowEvent(dpy,_window,ButtonPressMask,&event));
+ } break;
+ case ButtonRelease : {
+ do {
+ _mouse_x = event.xmotion.x; _mouse_y = event.xmotion.y;
+ if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+ switch (event.xbutton.button) {
+ case 1 : set_button(1,false); break;
+ case 3 : set_button(2,false); break;
+ case 2 : set_button(3,false); break;
+ case 4 : set_wheel(1); break;
+ case 5 : set_wheel(-1); break;
+ }
+ } while (XCheckWindowEvent(dpy,_window,ButtonReleaseMask,&event));
+ } break;
+ case KeyPress : {
+ char tmp = 0; KeySym ksym;
+ XLookupString(&event.xkey,&tmp,1,&ksym,0);
+ set_key((unsigned int)ksym,true);
+ } break;
+ case KeyRelease : {
+ char keys_return[32]; // Check that the key has been physically unpressed.
+ XQueryKeymap(dpy,keys_return);
+ const unsigned int kc = event.xkey.keycode, kc1 = kc/8, kc2 = kc%8;
+ const bool is_key_pressed = kc1>=32?false:(keys_return[kc1]>>kc2)&1;
+ if (!is_key_pressed) {
+ char tmp = 0; KeySym ksym;
+ XLookupString(&event.xkey,&tmp,1,&ksym,0);
+ set_key((unsigned int)ksym,false);
+ }
+ } break;
+ case EnterNotify: {
+ while (XCheckWindowEvent(dpy,_window,EnterWindowMask,&event)) {}
+ _mouse_x = event.xmotion.x;
+ _mouse_y = event.xmotion.y;
+ if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+ } break;
+ case LeaveNotify : {
+ while (XCheckWindowEvent(dpy,_window,LeaveWindowMask,&event)) {}
+ _mouse_x = _mouse_y = -1; _is_event = true;
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+ } break;
+ case MotionNotify : {
+ while (XCheckWindowEvent(dpy,_window,PointerMotionMask,&event)) {}
+ _mouse_x = event.xmotion.x;
+ _mouse_y = event.xmotion.y;
+ if (_mouse_x<0 || _mouse_y<0 || _mouse_x>=width() || _mouse_y>=height()) _mouse_x = _mouse_y = -1;
+ _is_event = true;
+ pthread_cond_broadcast(&cimg::X11_attr().wait_event);
+ } break;
+ }
+ }
+
+ static void* _events_thread(void *arg) { // Thread to manage events for all opened display windows.
+ Display *const dpy = cimg::X11_attr().display;
+ XEvent event;
+ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
+ if (!arg) for ( ; ; ) {
+ cimg_lock_display();
+ bool event_flag = XCheckTypedEvent(dpy,ClientMessage,&event);
+ if (!event_flag) event_flag = XCheckMaskEvent(dpy,
+ ExposureMask | StructureNotifyMask | ButtonPressMask |
+ KeyPressMask | PointerMotionMask | EnterWindowMask |
+ LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask,&event);
+ if (event_flag)
+ for (unsigned int i = 0; i<cimg::X11_attr().nb_wins; ++i)
+ if (!cimg::X11_attr().wins[i]->_is_closed && event.xany.window==cimg::X11_attr().wins[i]->_window)
+ cimg::X11_attr().wins[i]->_handle_events(&event);
+ cimg_unlock_display();
+ pthread_testcancel();
+ cimg::sleep(8);
+ }
+ return 0;
+ }
+
+ void _set_colormap(Colormap& _colormap, const unsigned int dim) {
+ XColor *const colormap = new XColor[256];
+ switch (dim) {
+ case 1 : { // colormap for greyscale images
+ for (unsigned int index = 0; index<256; ++index) {
+ colormap[index].pixel = index;
+ colormap[index].red = colormap[index].green = colormap[index].blue = (unsigned short)(index<<8);
+ colormap[index].flags = DoRed | DoGreen | DoBlue;
+ }
+ } break;
+ case 2 : { // colormap for RG images
+ for (unsigned int index = 0, r = 8; r<256; r+=16)
+ for (unsigned int g = 8; g<256; g+=16) {
+ colormap[index].pixel = index;
+ colormap[index].red = colormap[index].blue = (unsigned short)(r<<8);
+ colormap[index].green = (unsigned short)(g<<8);
+ colormap[index++].flags = DoRed | DoGreen | DoBlue;
+ }
+ } break;
+ default : { // colormap 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) {
+ colormap[index].pixel = index;
+ colormap[index].red = (unsigned short)(r<<8);
+ colormap[index].green = (unsigned short)(g<<8);
+ colormap[index].blue = (unsigned short)(b<<8);
+ colormap[index++].flags = DoRed | DoGreen | DoBlue;
+ }
+ }
+ }
+ XStoreColors(cimg::X11_attr().display,_colormap,colormap,256);
+ delete[] colormap;
+ }
+
+ void _map_window() {
+ Display *const dpy = cimg::X11_attr().display;
+ bool is_exposed = false, is_mapped = false;
+ XWindowAttributes attr;
+ XEvent event;
+ XMapRaised(dpy,_window);
+ do { // Wait for the window to be mapped.
+ XWindowEvent(dpy,_window,StructureNotifyMask | ExposureMask,&event);
+ switch (event.type) {
+ case MapNotify : is_mapped = true; break;
+ case Expose : is_exposed = true; break;
+ }
+ } while (!is_exposed || !is_mapped);
+ do { // Wait for the window to be visible.
+ XGetWindowAttributes(dpy,_window,&attr);
+ if (attr.map_state!=IsViewable) { XSync(dpy,0); cimg::sleep(10); }
+ } while (attr.map_state!=IsViewable);
+ _window_x = attr.x;
+ _window_y = attr.y;
+ }
+
+ void _paint(const bool wait_expose=true) {
+ if (_is_closed || !_image) return;
+ Display *const dpy = cimg::X11_attr().display;
+ if (wait_expose) { // Send an expose event sticked to display window to force repaint.
+ XEvent event;
+ event.xexpose.type = Expose;
+ event.xexpose.serial = 0;
+ event.xexpose.send_event = 1;
+ event.xexpose.display = dpy;
+ event.xexpose.window = _window;
+ event.xexpose.x = 0;
+ event.xexpose.y = 0;
+ event.xexpose.width = width();
+ event.xexpose.height = height();
+ event.xexpose.count = 0;
+ XSendEvent(dpy,_window,0,0,&event);
+ } else { // Repaint directly (may be called from the expose event).
+ GC gc = DefaultGC(dpy,DefaultScreen(dpy));
+#ifdef cimg_use_xshm
+ if (_shminfo) XShmPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height,1);
+ else XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height);
+#else
+ XPutImage(dpy,_window,gc,_image,0,0,0,0,_width,_height);
+#endif
+ }
+ }
+
+ template<typename T>
+ void _resize(T pixel_type, const unsigned int ndimx, const unsigned int ndimy, const bool force_redraw) {
+ Display *const dpy = cimg::X11_attr().display;
+ cimg::unused(pixel_type);
+
+#ifdef cimg_use_xshm
+ if (_shminfo) {
+ XShmSegmentInfo *const nshminfo = new XShmSegmentInfo;
+ XImage *const nimage = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),
+ cimg::X11_attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy);
+ if (!nimage) { delete nshminfo; return; }
+ else {
+ nshminfo->shmid = shmget(IPC_PRIVATE,ndimx*ndimy*sizeof(T),IPC_CREAT | 0777);
+ if (nshminfo->shmid==-1) { XDestroyImage(nimage); delete nshminfo; return; }
+ else {
+ nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0);
+ if (nshminfo->shmaddr==(char*)-1) {
+ shmctl(nshminfo->shmid,IPC_RMID,0); XDestroyImage(nimage); delete nshminfo; return;
+ } else {
+ nshminfo->readOnly = 0;
+ cimg::X11_attr().is_shm_enabled = true;
+ XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+ XShmAttach(dpy,nshminfo);
+ XFlush(dpy);
+ XSetErrorHandler(oldXErrorHandler);
+ if (!cimg::X11_attr().is_shm_enabled) {
+ shmdt(nshminfo->shmaddr);
+ shmctl(nshminfo->shmid,IPC_RMID,0);
+ XDestroyImage(nimage);
+ delete nshminfo;
+ return;
+ } else {
+ T *const ndata = (T*)nimage->data;
+ if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy);
+ else std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
+ XShmDetach(dpy,_shminfo);
+ XDestroyImage(_image);
+ shmdt(_shminfo->shmaddr);
+ shmctl(_shminfo->shmid,IPC_RMID,0);
+ delete _shminfo;
+ _shminfo = nshminfo;
+ _image = nimage;
+ _data = (void*)ndata;
+ }
+ }
+ }
+ }
+ } else
+#endif
+ {
+ T *ndata = (T*)std::malloc(ndimx*ndimy*sizeof(T));
+ if (force_redraw) _render_resize((T*)_data,_width,_height,ndata,ndimx,ndimy);
+ else std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
+ _data = (void*)ndata;
+ XDestroyImage(_image);
+ _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),
+ cimg::X11_attr().nb_bits,ZPixmap,0,(char*)_data,ndimx,ndimy,8,0);
+ }
+ }
+
+ void _init_fullscreen() {
+ if (!_is_fullscreen || _is_closed) return;
+ Display *const dpy = cimg::X11_attr().display;
+ _background_window = 0;
+
+#ifdef cimg_use_xrandr
+ int foo;
+ if (XRRQueryExtension(dpy,&foo,&foo)) {
+ XRRRotations(dpy,DefaultScreen(dpy),&cimg::X11_attr().curr_rotation);
+ if (!cimg::X11_attr().resolutions) {
+ cimg::X11_attr().resolutions = XRRSizes(dpy,DefaultScreen(dpy),&foo);
+ cimg::X11_attr().nb_resolutions = (unsigned int)foo;
+ }
+ if (cimg::X11_attr().resolutions) {
+ cimg::X11_attr().curr_resolution = 0;
+ for (unsigned int i = 0; i<cimg::X11_attr().nb_resolutions; ++i) {
+ const unsigned int
+ nw = (unsigned int)(cimg::X11_attr().resolutions[i].width),
+ nh = (unsigned int)(cimg::X11_attr().resolutions[i].height);
+ if (nw>=_width && nh>=_height &&
+ nw<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].width) &&
+ nh<=(unsigned int)(cimg::X11_attr().resolutions[cimg::X11_attr().curr_resolution].height))
+ cimg::X11_attr().curr_resolution = i;
+ }
+ if (cimg::X11_attr().curr_resolution>0) {
+ XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy));
+ XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),
+ cimg::X11_attr().curr_resolution,cimg::X11_attr().curr_rotation,CurrentTime);
+ XRRFreeScreenConfigInfo(config);
+ XSync(dpy,0);
+ }
+ }
+ }
+ if (!cimg::X11_attr().resolutions)
+ cimg::warn(_cimgdisplay_instance
+ "init_fullscreen(): Xrandr extension not supported by the X server.",
+ cimgdisplay_instance);
+#endif
+
+ const unsigned int sx = screen_width(), sy = screen_height();
+ if (sx==_width && sy==_height) return;
+ XSetWindowAttributes winattr;
+ winattr.override_redirect = 1;
+ _background_window = XCreateWindow(dpy,DefaultRootWindow(dpy),0,0,sx,sy,0,0,
+ InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+ const cimg_ulong buf_size = (cimg_ulong)sx*sy*(cimg::X11_attr().nb_bits==8?1:
+ (cimg::X11_attr().nb_bits==16?2:4));
+ void *background_data = std::malloc(buf_size);
+ std::memset(background_data,0,buf_size);
+ XImage *background_image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits,
+ ZPixmap,0,(char*)background_data,sx,sy,8,0);
+ XEvent event;
+ XSelectInput(dpy,_background_window,StructureNotifyMask);
+ XMapRaised(dpy,_background_window);
+ do XWindowEvent(dpy,_background_window,StructureNotifyMask,&event);
+ while (event.type!=MapNotify);
+ GC gc = DefaultGC(dpy,DefaultScreen(dpy));
+#ifdef cimg_use_xshm
+ if (_shminfo) XShmPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy,0);
+ else XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy);
+#else
+ XPutImage(dpy,_background_window,gc,background_image,0,0,0,0,sx,sy);
+#endif
+ XWindowAttributes attr;
+ XGetWindowAttributes(dpy,_background_window,&attr);
+ while (attr.map_state!=IsViewable) XSync(dpy,0);
+ XDestroyImage(background_image);
+ }
+
+ void _desinit_fullscreen() {
+ if (!_is_fullscreen) return;
+ Display *const dpy = cimg::X11_attr().display;
+ XUngrabKeyboard(dpy,CurrentTime);
+#ifdef cimg_use_xrandr
+ if (cimg::X11_attr().resolutions && cimg::X11_attr().curr_resolution) {
+ XRRScreenConfiguration *config = XRRGetScreenInfo(dpy,DefaultRootWindow(dpy));
+ XRRSetScreenConfig(dpy,config,DefaultRootWindow(dpy),0,cimg::X11_attr().curr_rotation,CurrentTime);
+ XRRFreeScreenConfigInfo(config);
+ XSync(dpy,0);
+ cimg::X11_attr().curr_resolution = 0;
+ }
+#endif
+ if (_background_window) XDestroyWindow(dpy,_background_window);
+ _background_window = 0;
+ _is_fullscreen = false;
+ }
+
+ static int _assign_xshm(Display *dpy, XErrorEvent *error) {
+ cimg::unused(dpy,error);
+ cimg::X11_attr().is_shm_enabled = false;
+ return 0;
+ }
+
+ void _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ cimg::mutex(14);
+
+ // Allocate space for window title
+ const char *const nptitle = ptitle?ptitle:"";
+ const unsigned int s = (unsigned int)std::strlen(nptitle) + 1;
+ char *const tmp_title = s?new char[s]:0;
+ if (s) std::memcpy(tmp_title,nptitle,s*sizeof(char));
+
+ // Destroy previous display window if existing
+ if (!is_empty()) assign();
+
+ // Open X11 display and retrieve graphical properties.
+ Display* &dpy = cimg::X11_attr().display;
+ if (!dpy) {
+ dpy = XOpenDisplay(0);
+ if (!dpy)
+ throw CImgDisplayException(_cimgdisplay_instance
+ "assign(): Failed to open X11 display.",
+ cimgdisplay_instance);
+
+ cimg::X11_attr().nb_bits = DefaultDepth(dpy,DefaultScreen(dpy));
+ if (cimg::X11_attr().nb_bits!=8 && cimg::X11_attr().nb_bits!=16 &&
+ cimg::X11_attr().nb_bits!=24 && cimg::X11_attr().nb_bits!=32)
+ throw CImgDisplayException(_cimgdisplay_instance
+ "assign(): Invalid %u bits screen mode detected "
+ "(only 8, 16, 24 and 32 bits modes are managed).",
+ cimgdisplay_instance,
+ cimg::X11_attr().nb_bits);
+ XVisualInfo vtemplate;
+ vtemplate.visualid = XVisualIDFromVisual(DefaultVisual(dpy,DefaultScreen(dpy)));
+ int nb_visuals;
+ XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals);
+ if (vinfo && vinfo->red_mask<vinfo->blue_mask) cimg::X11_attr().is_blue_first = true;
+ cimg::X11_attr().byte_order = ImageByteOrder(dpy);
+ XFree(vinfo);
+
+ cimg_lock_display();
+ cimg::X11_attr().events_thread = new pthread_t;
+ pthread_create(cimg::X11_attr().events_thread,0,_events_thread,0);
+ } else cimg_lock_display();
+
+ // Set display variables.
+ _width = std::min(dimw,(unsigned int)screen_width());
+ _height = std::min(dimh,(unsigned int)screen_height());
+ _normalization = normalization_type<4?normalization_type:3;
+ _is_fullscreen = fullscreen_flag;
+ _window_x = _window_y = 0;
+ _is_closed = closed_flag;
+ _title = tmp_title;
+ flush();
+
+ // Create X11 window (and LUT, if 8bits display)
+ if (_is_fullscreen) {
+ if (!_is_closed) _init_fullscreen();
+ const unsigned int sx = screen_width(), sy = screen_height();
+ XSetWindowAttributes winattr;
+ winattr.override_redirect = 1;
+ _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx - _width)/2,(sy - _height)/2,_width,_height,0,0,
+ InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
+ } else
+ _window = XCreateSimpleWindow(dpy,DefaultRootWindow(dpy),0,0,_width,_height,0,0L,0L);
+
+ XSelectInput(dpy,_window,
+ ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask |
+ EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask);
+
+ XStoreName(dpy,_window,_title?_title:" ");
+ if (cimg::X11_attr().nb_bits==8) {
+ _colormap = XCreateColormap(dpy,_window,DefaultVisual(dpy,DefaultScreen(dpy)),AllocAll);
+ _set_colormap(_colormap,3);
+ XSetWindowColormap(dpy,_window,_colormap);
+ }
+
+ static const char *const _window_class = cimg_appname;
+ XClassHint *const window_class = XAllocClassHint();
+ window_class->res_name = (char*)_window_class;
+ window_class->res_class = (char*)_window_class;
+ XSetClassHint(dpy,_window,window_class);
+ XFree(window_class);
+
+ _window_width = _width;
+ _window_height = _height;
+
+ // Create XImage
+#ifdef cimg_use_xshm
+ _shminfo = 0;
+ if (XShmQueryExtension(dpy)) {
+ _shminfo = new XShmSegmentInfo;
+ _image = XShmCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits,
+ ZPixmap,0,_shminfo,_width,_height);
+ if (!_image) { delete _shminfo; _shminfo = 0; }
+ else {
+ _shminfo->shmid = shmget(IPC_PRIVATE,_image->bytes_per_line*_image->height,IPC_CREAT|0777);
+ if (_shminfo->shmid==-1) { XDestroyImage(_image); delete _shminfo; _shminfo = 0; }
+ else {
+ _shminfo->shmaddr = _image->data = (char*)(_data = shmat(_shminfo->shmid,0,0));
+ if (_shminfo->shmaddr==(char*)-1) {
+ shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image); delete _shminfo; _shminfo = 0;
+ } else {
+ _shminfo->readOnly = 0;
+ cimg::X11_attr().is_shm_enabled = true;
+ XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
+ XShmAttach(dpy,_shminfo);
+ XSync(dpy,0);
+ XSetErrorHandler(oldXErrorHandler);
+ if (!cimg::X11_attr().is_shm_enabled) {
+ shmdt(_shminfo->shmaddr); shmctl(_shminfo->shmid,IPC_RMID,0); XDestroyImage(_image);
+ delete _shminfo; _shminfo = 0;
+ }
+ }
+ }
+ }
+ }
+ if (!_shminfo)
+#endif
+ {
+ const cimg_ulong buf_size = (cimg_ulong)_width*_height*(cimg::X11_attr().nb_bits==8?1:
+ (cimg::X11_attr().nb_bits==16?2:4));
+ _data = std::malloc(buf_size);
+ _image = XCreateImage(dpy,DefaultVisual(dpy,DefaultScreen(dpy)),cimg::X11_attr().nb_bits,
+ ZPixmap,0,(char*)_data,_width,_height,8,0);
+ }
+
+ _wm_window_atom = XInternAtom(dpy,"WM_DELETE_WINDOW",0);
+ _wm_protocol_atom = XInternAtom(dpy,"WM_PROTOCOLS",0);
+ XSetWMProtocols(dpy,_window,&_wm_window_atom,1);
+
+ if (_is_fullscreen) XGrabKeyboard(dpy,_window,1,GrabModeAsync,GrabModeAsync,CurrentTime);
+ cimg::X11_attr().wins[cimg::X11_attr().nb_wins++]=this;
+ if (!_is_closed) _map_window(); else { _window_x = _window_y = cimg::type<int>::min(); }
+ cimg_unlock_display();
+ cimg::mutex(14,0);
+ }
+
+ CImgDisplay& assign() {
+ if (is_empty()) return flush();
+ Display *const dpy = cimg::X11_attr().display;
+ cimg_lock_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(dpy,_window);
+ _window = 0;
+#ifdef cimg_use_xshm
+ if (_shminfo) {
+ XShmDetach(dpy,_shminfo);
+ XDestroyImage(_image);
+ shmdt(_shminfo->shmaddr);
+ shmctl(_shminfo->shmid,IPC_RMID,0);
+ delete _shminfo;
+ _shminfo = 0;
+ } else
+#endif
+ XDestroyImage(_image);
+ _data = 0; _image = 0;
+ if (cimg::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap);
+ _colormap = 0;
+ XSync(dpy,0);
+
+ // Reset display variables.
+ delete[] _title;
+ _width = _height = _normalization = _window_width = _window_height = 0;
+ _window_x = _window_y = 0;
+ _is_fullscreen = false;
+ _is_closed = true;
+ _min = _max = 0;
+ _title = 0;
+ flush();
+
+ cimg_unlock_display();
+ 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)))*
+ (size_t)_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 - 1)/2,
+ (img._height - 1)/2,
+ (img._depth - 1)/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 - 1)/2,
+ (img._height - 1)/2,
+ (img._depth - 1)/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))*(size_t)_width*_height);
+ return paint();
+ }
+
+ CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) {
+ if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+ if (is_empty()) return assign(nwidth,nheight);
+ Display *const dpy = cimg::X11_attr().display;
+ const unsigned int
+ tmpdimx = (nwidth>0)?nwidth:(-nwidth*width()/100),
+ tmpdimy = (nheight>0)?nheight:(-nheight*height()/100),
+ dimx = tmpdimx?tmpdimx:1,
+ dimy = tmpdimy?tmpdimy:1;
+ if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) {
+ show();
+ cimg_lock_display();
+ if (_window_width!=dimx || _window_height!=dimy) {
+ XWindowAttributes attr;
+ for (unsigned int i = 0; i<10; ++i) {
+ XResizeWindow(dpy,_window,dimx,dimy);
+ XGetWindowAttributes(dpy,_window,&attr);
+ if (attr.width==(int)dimx && attr.height==(int)dimy) break;
+ cimg::wait(5);
+ }
+ }
+ if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) {
+ case 8 : { unsigned char pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break;
+ case 16 : { unsigned short pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); } break;
+ default : { unsigned int pixel_type = 0; _resize(pixel_type,dimx,dimy,force_redraw); }
+ }
+ _window_width = _width = dimx; _window_height = _height = dimy;
+ cimg_unlock_display();
+ }
+ _is_resized = false;
+ if (_is_fullscreen) move((screen_width() - _width)/2,(screen_height() - _height)/2);
+ if (force_redraw) return paint();
+ return *this;
+ }
+
+ CImgDisplay& toggle_fullscreen(const bool force_redraw=true) {
+ if (is_empty()) return *this;
+ if (force_redraw) {
+ const cimg_ulong buf_size = (cimg_ulong)_width*_height*
+ (cimg::X11_attr().nb_bits==8?1:(cimg::X11_attr().nb_bits==16?2:4));
+ void *image_data = std::malloc(buf_size);
+ std::memcpy(image_data,_data,buf_size);
+ assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+ std::memcpy(_data,image_data,buf_size);
+ std::free(image_data);
+ return paint();
+ }
+ return assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+ }
+
+ CImgDisplay& show() {
+ if (is_empty() || !_is_closed) return *this;
+ cimg_lock_display();
+ if (_is_fullscreen) _init_fullscreen();
+ _map_window();
+ _is_closed = false;
+ cimg_unlock_display();
+ return paint();
+ }
+
+ CImgDisplay& close() {
+ if (is_empty() || _is_closed) return *this;
+ Display *const dpy = cimg::X11_attr().display;
+ cimg_lock_display();
+ if (_is_fullscreen) _desinit_fullscreen();
+ XUnmapWindow(dpy,_window);
+ _window_x = _window_y = -1;
+ _is_closed = true;
+ cimg_unlock_display();
+ return *this;
+ }
+
+ CImgDisplay& move(const int posx, const int posy) {
+ if (is_empty()) return *this;
+ if (_window_x!=posx || _window_y!=posy) {
+ show();
+ Display *const dpy = cimg::X11_attr().display;
+ cimg_lock_display();
+ XMoveWindow(dpy,_window,posx,posy);
+ _window_x = posx; _window_y = posy;
+ cimg_unlock_display();
+ }
+ _is_moved = false;
+ return paint();
+ }
+
+ CImgDisplay& show_mouse() {
+ if (is_empty()) return *this;
+ Display *const dpy = cimg::X11_attr().display;
+ cimg_lock_display();
+ XUndefineCursor(dpy,_window);
+ cimg_unlock_display();
+ return *this;
+ }
+
+ CImgDisplay& hide_mouse() {
+ if (is_empty()) return *this;
+ Display *const dpy = cimg::X11_attr().display;
+ cimg_lock_display();
+ static const char pix_data[8] = { 0 };
+ XColor col;
+ col.red = col.green = col.blue = 0;
+ Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8);
+ Cursor cur = XCreatePixmapCursor(dpy,pix,pix,&col,&col,0,0);
+ XFreePixmap(dpy,pix);
+ XDefineCursor(dpy,_window,cur);
+ cimg_unlock_display();
+ return *this;
+ }
+
+ CImgDisplay& set_mouse(const int posx, const int posy) {
+ if (is_empty() || _is_closed) return *this;
+ Display *const dpy = cimg::X11_attr().display;
+ cimg_lock_display();
+ XWarpPointer(dpy,0L,_window,0,0,0,0,posx,posy);
+ _mouse_x = posx; _mouse_y = posy;
+ _is_moved = false;
+ XSync(dpy,0);
+ cimg_unlock_display();
+ return *this;
+ }
+
+ CImgDisplay& set_title(const char *const format, ...) {
+ if (is_empty()) return *this;
+ char *const tmp = new char[1024];
+ va_list ap;
+ va_start(ap, format);
+ cimg_vsnprintf(tmp,1024,format,ap);
+ va_end(ap);
+ if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; }
+ delete[] _title;
+ const unsigned int s = (unsigned int)std::strlen(tmp) + 1;
+ _title = new char[s];
+ std::memcpy(_title,tmp,s*sizeof(char));
+ Display *const dpy = cimg::X11_attr().display;
+ cimg_lock_display();
+ XStoreName(dpy,_window,tmp);
+ cimg_unlock_display();
+ delete[] tmp;
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& display(const CImg<T>& img) {
+ if (!img)
+ throw CImgArgumentException(_cimgdisplay_instance
+ "display(): Empty specified image.",
+ cimgdisplay_instance);
+ if (is_empty()) return assign(img);
+ return render(img).paint(false);
+ }
+
+ CImgDisplay& paint(const bool wait_expose=true) {
+ if (is_empty()) return *this;
+ cimg_lock_display();
+ _paint(wait_expose);
+ cimg_unlock_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 - 1)/2,(img._height - 1)/2,
+ (img._depth - 1)/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_colormap = CImg<typename CImg<T>::ucharT>::default_LUT256();
+ return render(img.get_index(default_colormap,1,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);
+ cimg_lock_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 colormap, no normalization
+ _set_colormap(_colormap,img._spectrum);
+ unsigned char
+ *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data:
+ new unsigned char[(size_t)img._width*img._height],
+ *ptrd = (unsigned char*)ndata;
+ switch (img._spectrum) {
+ case 1 :
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy)
+ (*ptrd++) = (unsigned char)*(data1++);
+ break;
+ case 2 : for (cimg_ulong xy = (cimg_ulong)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 (cimg_ulong xy = (cimg_ulong)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[(size_t)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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++), G = val>>2;
+ ptrd[0] = (val&M) | (G>>3);
+ ptrd[1] = (G<<5) | (G>>1);
+ ptrd+=2;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++), G = val>>2;
+ ptrd[0] = (G<<5) | (G>>1);
+ ptrd[1] = (val&M) | (G>>3);
+ ptrd+=2;
+ }
+ break;
+ case 2 :
+ if (cimg::X11_attr().byte_order)
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3);
+ ptrd[1] = (G<<5);
+ ptrd+=2;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ ptrd[0] = (G<<5);
+ ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3);
+ ptrd+=2;
+ }
+ break;
+ default :
+ if (cimg::X11_attr().byte_order)
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ ptrd[0] = ((unsigned char)*(data1++)&M) | (G>>3);
+ ptrd[1] = (G<<5) | ((unsigned char)*(data3++)>>3);
+ ptrd+=2;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)*(data2++)>>2;
+ ptrd[0] = (G<<5) | ((unsigned char)*(data3++)>>3);
+ ptrd[1] = ((unsigned char)*(data1++)&M) | (G>>3);
+ ptrd+=2;
+ }
+ }
+ 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[(size_t)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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++);
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }
+ else
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++);
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }
+ break;
+ case 2 :
+ if (cimg::X11_attr().byte_order==cimg::endianness())
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
+ else
+ for (cimg_ulong xy = (cimg_ulong)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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy)
+ *(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) |
+ (unsigned char)*(data3++);
+ else
+ for (cimg_ulong xy = (cimg_ulong)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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = 0;
+ ptrd[1] = (unsigned char)*(data1++);
+ ptrd[2] = 0;
+ ptrd[3] = 0;
+ ptrd+=4;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = 0;
+ ptrd[1] = 0;
+ ptrd[2] = (unsigned char)*(data1++);
+ ptrd[3] = 0;
+ ptrd+=4;
+ }
+ break;
+ case 2 :
+ if (cimg::X11_attr().byte_order) cimg::swap(data1,data2);
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = 0;
+ ptrd[1] = (unsigned char)*(data2++);
+ ptrd[2] = (unsigned char)*(data1++);
+ ptrd[3] = 0;
+ ptrd+=4;
+ }
+ break;
+ default :
+ if (cimg::X11_attr().byte_order)
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = 0;
+ ptrd[1] = (unsigned char)*(data1++);
+ ptrd[2] = (unsigned char)*(data2++);
+ ptrd[3] = (unsigned char)*(data3++);
+ ptrd+=4;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = (unsigned char)*(data3++);
+ ptrd[1] = (unsigned char)*(data2++);
+ ptrd[2] = (unsigned char)*(data1++);
+ ptrd[3] = 0;
+ ptrd+=4;
+ }
+ }
+ }
+ if (ndata!=_data) {
+ _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height);
+ delete[] ndata;
+ }
+ }
+ }
+ } else {
+ if (_normalization==3) {
+ if (cimg::type<T>::is_float()) _min = (float)img.min_max(_max);
+ else { _min = (float)cimg::type<T>::min(); _max = (float)cimg::type<T>::max(); }
+ } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max);
+ const float delta = _max - _min, mm = 255/(delta?delta:1.0f);
+ switch (cimg::X11_attr().nb_bits) {
+ case 8 : { // 256 colormap, with normalization
+ _set_colormap(_colormap,img._spectrum);
+ unsigned char *const ndata = (img._width==_width && img._height==_height)?(unsigned char*)_data:
+ new unsigned char[(size_t)img._width*img._height];
+ unsigned char *ptrd = (unsigned char*)ndata;
+ switch (img._spectrum) {
+ case 1 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char R = (unsigned char)((*(data1++) - _min)*mm);
+ *(ptrd++) = R;
+ } break;
+ case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)((*(data1++) - _min)*mm),
+ G = (unsigned char)((*(data2++) - _min)*mm);
+ (*ptrd++) = (R&0xf0) | (G>>4);
+ } break;
+ default :
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)((*(data1++) - _min)*mm),
+ G = (unsigned char)((*(data2++) - _min)*mm),
+ B = (unsigned char)((*(data3++) - _min)*mm);
+ *(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
+ }
+ }
+ if (ndata!=_data) {
+ _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height);
+ delete[] ndata;
+ }
+ } break;
+ case 16 : { // 16 bits colors, with normalization
+ unsigned short *const ndata = (img._width==_width && img._height==_height)?(unsigned short*)_data:
+ new unsigned short[(size_t)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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2;
+ ptrd[0] = (val&M) | (G>>3);
+ ptrd[1] = (G<<5) | (val>>3);
+ ptrd+=2;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm), G = val>>2;
+ ptrd[0] = (G<<5) | (val>>3);
+ ptrd[1] = (val&M) | (G>>3);
+ ptrd+=2;
+ }
+ break;
+ case 2 :
+ if (cimg::X11_attr().byte_order)
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2;
+ ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3);
+ ptrd[1] = (G<<5);
+ ptrd+=2;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2;
+ ptrd[0] = (G<<5);
+ ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3);
+ ptrd+=2;
+ }
+ break;
+ default :
+ if (cimg::X11_attr().byte_order)
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2;
+ ptrd[0] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3);
+ ptrd[1] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3);
+ ptrd+=2;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char G = (unsigned char)((*(data2++) - _min)*mm)>>2;
+ ptrd[0] = (G<<5) | ((unsigned char)((*(data3++) - _min)*mm)>>3);
+ ptrd[1] = ((unsigned char)((*(data1++) - _min)*mm)&M) | (G>>3);
+ ptrd+=2;
+ }
+ }
+ 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[(size_t)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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm);
+ *(ptrd++) = (val<<16) | (val<<8) | val;
+ }
+ else
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm);
+ *(ptrd++) = (val<<24) | (val<<16) | (val<<8);
+ }
+ break;
+ case 2 :
+ if (cimg::X11_attr().byte_order==cimg::endianness())
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)((*(data1++) - _min)*mm)<<16) |
+ ((unsigned char)((*(data2++) - _min)*mm)<<8);
+ else
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)((*(data2++) - _min)*mm)<<16) |
+ ((unsigned char)((*(data1++) - _min)*mm)<<8);
+ break;
+ default :
+ if (cimg::X11_attr().byte_order==cimg::endianness())
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)((*(data1++) - _min)*mm)<<16) |
+ ((unsigned char)((*(data2++) - _min)*mm)<<8) |
+ (unsigned char)((*(data3++) - _min)*mm);
+ else
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy)
+ *(ptrd++) =
+ ((unsigned char)((*(data3++) - _min)*mm)<<24) |
+ ((unsigned char)((*(data2++) - _min)*mm)<<16) |
+ ((unsigned char)((*(data1++) - _min)*mm)<<8);
+ }
+ } else {
+ unsigned char *ptrd = (unsigned char*)ndata;
+ switch (img._spectrum) {
+ case 1 :
+ if (cimg::X11_attr().byte_order)
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm);
+ ptrd[0] = 0;
+ ptrd[1] = val;
+ ptrd[2] = val;
+ ptrd[3] = val;
+ ptrd+=4;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm);
+ ptrd[0] = val;
+ ptrd[1] = val;
+ ptrd[2] = val;
+ ptrd[3] = 0;
+ ptrd+=4;
+ }
+ break;
+ case 2 :
+ if (cimg::X11_attr().byte_order) cimg::swap(data1,data2);
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = 0;
+ ptrd[1] = (unsigned char)((*(data2++) - _min)*mm);
+ ptrd[2] = (unsigned char)((*(data1++) - _min)*mm);
+ ptrd[3] = 0;
+ ptrd+=4;
+ }
+ break;
+ default :
+ if (cimg::X11_attr().byte_order)
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = 0;
+ ptrd[1] = (unsigned char)((*(data1++) - _min)*mm);
+ ptrd[2] = (unsigned char)((*(data2++) - _min)*mm);
+ ptrd[3] = (unsigned char)((*(data3++) - _min)*mm);
+ ptrd+=4;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ptrd[0] = (unsigned char)((*(data3++) - _min)*mm);
+ ptrd[1] = (unsigned char)((*(data2++) - _min)*mm);
+ ptrd[2] = (unsigned char)((*(data1++) - _min)*mm);
+ ptrd[3] = 0;
+ ptrd+=4;
+ }
+ }
+ }
+ if (ndata!=_data) {
+ _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height);
+ delete[] ndata;
+ }
+ }
+ }
+ }
+ cimg_unlock_display();
+ return *this;
+ }
+
+ template<typename T>
+ static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg<T>& img) {
+ img.assign();
+ Display *dpy = cimg::X11_attr().display;
+ cimg_lock_display();
+ if (!dpy) {
+ dpy = XOpenDisplay(0);
+ if (!dpy)
+ throw CImgDisplayException("CImgDisplay::screenshot(): Failed to open X11 display.");
+ }
+ Window root = DefaultRootWindow(dpy);
+ XWindowAttributes gwa;
+ XGetWindowAttributes(dpy,root,&gwa);
+ const int width = gwa.width, height = gwa.height;
+ int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1;
+ if (_x0>_x1) cimg::swap(_x0,_x1);
+ if (_y0>_y1) cimg::swap(_y0,_y1);
+
+ XImage *image = 0;
+ if (_x1>=0 && _x0<width && _y1>=0 && _y0<height) {
+ _x0 = std::max(_x0,0);
+ _y0 = std::max(_y0,0);
+ _x1 = std::min(_x1,width - 1);
+ _y1 = std::min(_y1,height - 1);
+ image = XGetImage(dpy,root,_x0,_y0,_x1 - _x0 + 1,_y1 - _y0 + 1,AllPlanes,ZPixmap);
+
+ if (image) {
+ const unsigned long
+ red_mask = image->red_mask,
+ green_mask = image->green_mask,
+ blue_mask = image->blue_mask;
+ img.assign(image->width,image->height,1,3);
+ T *pR = img.data(0,0,0,0), *pG = img.data(0,0,0,1), *pB = img.data(0,0,0,2);
+ cimg_forXY(img,x,y) {
+ const unsigned long pixel = XGetPixel(image,x,y);
+ *(pR++) = (T)((pixel & red_mask)>>16);
+ *(pG++) = (T)((pixel & green_mask)>>8);
+ *(pB++) = (T)(pixel & blue_mask);
+ }
+ XDestroyImage(image);
+ }
+ }
+ if (!cimg::X11_attr().display) XCloseDisplay(dpy);
+ cimg_unlock_display();
+ if (img.is_empty())
+ throw CImgDisplayException("CImgDisplay::screenshot(): Failed to take screenshot "
+ "with coordinates (%d,%d)-(%d,%d).",
+ x0,y0,x1,y1);
+ }
+
+ 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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = *(ptrs++);
+ *(data1++) = (T)(val&0xe0);
+ *(data2++) = (T)((val&0x1c)<<3);
+ *(data3++) = (T)(val<<6);
+ }
+ } break;
+ case 16 : {
+ if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char
+ val0 = ptrs[0],
+ val1 = ptrs[1];
+ ptrs+=2;
+ *(data1++) = (T)(val0&0xf8);
+ *(data2++) = (T)((val0<<5) | ((val1&0xe0)>>5));
+ *(data3++) = (T)(val1<<3);
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned short
+ val0 = ptrs[0],
+ val1 = ptrs[1];
+ ptrs+=2;
+ *(data1++) = (T)(val1&0xf8);
+ *(data2++) = (T)((val1<<5) | ((val0&0xe0)>>5));
+ *(data3++) = (T)(val0<<3);
+ }
+ } break;
+ default : {
+ if (cimg::X11_attr().byte_order) for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ ++ptrs;
+ *(data1++) = (T)ptrs[0];
+ *(data2++) = (T)ptrs[1];
+ *(data3++) = (T)ptrs[2];
+ ptrs+=3;
+ } else for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ *(data3++) = (T)ptrs[0];
+ *(data2++) = (T)ptrs[1];
+ *(data1++) = (T)ptrs[2];
+ ptrs+=3;
+ ++ptrs;
+ }
+ }
+ }
+ return *this;
+ }
+
+ // Windows-based implementation.
+ //-------------------------------
+#elif cimg_display==2
+
+ bool _is_mouse_tracked, _is_cursor_visible;
+ HANDLE _thread, _is_created, _mutex;
+ HWND _window, _background_window;
+ CLIENTCREATESTRUCT _ccs;
+ unsigned int *_data;
+ DEVMODE _curr_mode;
+ BITMAPINFO _bmi;
+ HDC _hdc;
+
+ static int screen_width() {
+ DEVMODE mode;
+ mode.dmSize = sizeof(DEVMODE);
+ mode.dmDriverExtra = 0;
+ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+ return (int)mode.dmPelsWidth;
+ }
+
+ static int screen_height() {
+ DEVMODE mode;
+ mode.dmSize = sizeof(DEVMODE);
+ mode.dmDriverExtra = 0;
+ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
+ return (int)mode.dmPelsHeight;
+ }
+
+ static void wait_all() {
+ WaitForSingleObject(cimg::Win32_attr().wait_event,INFINITE);
+ }
+
+ static LRESULT APIENTRY _handle_events(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
+#ifdef _WIN64
+ CImgDisplay *const disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA);
+#else
+ CImgDisplay *const disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA);
+#endif
+ MSG st_msg;
+ switch (msg) {
+ case WM_CLOSE :
+ disp->_mouse_x = disp->_mouse_y = -1;
+ disp->_window_x = disp->_window_y = 0;
+ disp->set_button().set_key(0).set_key(0,false)._is_closed = true;
+ ReleaseMutex(disp->_mutex);
+ ShowWindow(disp->_window,SW_HIDE);
+ disp->_is_event = true;
+ SetEvent(cimg::Win32_attr().wait_event);
+ return 0;
+ case WM_SIZE : {
+ while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
+ WaitForSingleObject(disp->_mutex,INFINITE);
+ const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam);
+ if (nw && nh && (nw!=disp->_width || nh!=disp->_height)) {
+ disp->_window_width = nw;
+ disp->_window_height = nh;
+ disp->_mouse_x = disp->_mouse_y = -1;
+ disp->_is_resized = disp->_is_event = true;
+ SetEvent(cimg::Win32_attr().wait_event);
+ }
+ ReleaseMutex(disp->_mutex);
+ } break;
+ case WM_MOVE : {
+ while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
+ WaitForSingleObject(disp->_mutex,INFINITE);
+ const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam));
+ if (nx!=disp->_window_x || ny!=disp->_window_y) {
+ disp->_window_x = nx;
+ disp->_window_y = ny;
+ disp->_is_moved = disp->_is_event = true;
+ SetEvent(cimg::Win32_attr().wait_event);
+ }
+ ReleaseMutex(disp->_mutex);
+ } break;
+ case WM_PAINT :
+ disp->paint();
+ cimg::mutex(15);
+ if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0);
+ cimg::mutex(15,0);
+ break;
+ case WM_ERASEBKGND :
+ // return 0;
+ 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);
+ cimg::mutex(15);
+ if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE)>=0);
+ cimg::mutex(15,0);
+ } break;
+ case WM_MOUSELEAVE : {
+ disp->_mouse_x = disp->_mouse_y = -1;
+ disp->_is_mouse_tracked = false;
+ cimg::mutex(15);
+ while (ShowCursor(TRUE)<0) {}
+ cimg::mutex(15,0);
+ } 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);
+ }
+ return DefWindowProc(window,msg,wParam,lParam);
+ }
+
+ static DWORD WINAPI _events_thread(void* arg) {
+ CImgDisplay *const disp = (CImgDisplay*)(((void**)arg)[0]);
+ const char *const title = (const char*)(((void**)arg)[1]);
+ MSG msg;
+ delete[] (void**)arg;
+ disp->_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ disp->_bmi.bmiHeader.biWidth = disp->width();
+ disp->_bmi.bmiHeader.biHeight = -disp->height();
+ disp->_bmi.bmiHeader.biPlanes = 1;
+ disp->_bmi.bmiHeader.biBitCount = 32;
+ disp->_bmi.bmiHeader.biCompression = BI_RGB;
+ disp->_bmi.bmiHeader.biSizeImage = 0;
+ disp->_bmi.bmiHeader.biXPelsPerMeter = 1;
+ disp->_bmi.bmiHeader.biYPelsPerMeter = 1;
+ disp->_bmi.bmiHeader.biClrUsed = 0;
+ disp->_bmi.bmiHeader.biClrImportant = 0;
+ disp->_data = new unsigned int[(size_t)disp->_width*disp->_height];
+ if (!disp->_is_fullscreen) { // Normal window
+ RECT rect;
+ rect.left = rect.top = 0; rect.right = (LONG)disp->_width - 1; rect.bottom = (LONG)disp->_height - 1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int
+ border1 = (int)((rect.right - rect.left + 1 - disp->_width)/2),
+ border2 = (int)(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 = (unsigned int)screen_width(),
+ sy = (unsigned int)screen_height();
+ disp->_window = CreateWindowA("MDICLIENT",title?title:" ",
+ WS_POPUP | (disp->_is_closed?0:WS_VISIBLE),
+ (sx - disp->_width)/2,
+ (sy - disp->_height)/2,
+ disp->_width,disp->_height,0,0,0,&(disp->_ccs));
+ disp->_window_x = disp->_window_y = 0;
+ }
+ SetForegroundWindow(disp->_window);
+ disp->_hdc = GetDC(disp->_window);
+ disp->_window_width = disp->_width;
+ disp->_window_height = disp->_height;
+ disp->flush();
+#ifdef _WIN64
+ SetWindowLongPtr(disp->_window,GWLP_USERDATA,(LONG_PTR)disp);
+ SetWindowLongPtr(disp->_window,GWLP_WNDPROC,(LONG_PTR)_handle_events);
+#else
+ SetWindowLong(disp->_window,GWL_USERDATA,(LONG)disp);
+ SetWindowLong(disp->_window,GWL_WNDPROC,(LONG)_handle_events);
+#endif
+ SetEvent(disp->_is_created);
+ while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg);
+ return 0;
+ }
+
+ CImgDisplay& _update_window_pos() {
+ if (_is_closed) _window_x = _window_y = -1;
+ else {
+ RECT rect;
+ rect.left = rect.top = 0; rect.right = (LONG)_width - 1; rect.bottom = (LONG)_height - 1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int
+ border1 = (int)((rect.right - rect.left + 1 - _width)/2),
+ border2 = (int)(rect.bottom - rect.top + 1 - _height - border1);
+ GetWindowRect(_window,&rect);
+ _window_x = rect.left + border1;
+ _window_y = rect.top + border2;
+ }
+ return *this;
+ }
+
+ void _init_fullscreen() {
+ _background_window = 0;
+ if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0;
+ else {
+ DEVMODE mode;
+ unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U;
+ for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) {
+ const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight;
+ if (nw>=_width && nh>=_height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) {
+ bestbpp = mode.dmBitsPerPel;
+ ibest = imode;
+ bw = nw; bh = nh;
+ }
+ }
+ if (bestbpp) {
+ _curr_mode.dmSize = sizeof(DEVMODE); _curr_mode.dmDriverExtra = 0;
+ EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&_curr_mode);
+ EnumDisplaySettings(0,ibest,&mode);
+ ChangeDisplaySettings(&mode,0);
+ } else _curr_mode.dmSize = 0;
+
+ const unsigned int
+ sx = (unsigned int)screen_width(),
+ sy = (unsigned int)screen_height();
+ if (sx!=_width || sy!=_height) {
+ CLIENTCREATESTRUCT background_ccs;
+ _background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs);
+ SetForegroundWindow(_background_window);
+ }
+ }
+ }
+
+ void _desinit_fullscreen() {
+ if (!_is_fullscreen) return;
+ if (_background_window) DestroyWindow(_background_window);
+ _background_window = 0;
+ if (_curr_mode.dmSize) ChangeDisplaySettings(&_curr_mode,0);
+ _is_fullscreen = false;
+ }
+
+ CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *const ptitle=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+
+ // Allocate space for window title
+ const char *const nptitle = ptitle?ptitle:"";
+ const unsigned int s = (unsigned int)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 = std::min(dimw,(unsigned int)screen_width());
+ _height = std::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;
+ _mutex = CreateMutex(0,FALSE,0);
+ _is_created = CreateEvent(0,FALSE,FALSE,0);
+ _thread = CreateThread(0,0,_events_thread,arg,0,0);
+ WaitForSingleObject(_is_created,INFINITE);
+ return *this;
+ }
+
+ CImgDisplay& assign() {
+ if (is_empty()) return flush();
+ DestroyWindow(_window);
+ TerminateThread(_thread,0);
+ delete[] _data;
+ delete[] _title;
+ _data = 0;
+ _title = 0;
+ if (_is_fullscreen) _desinit_fullscreen();
+ _width = _height = _normalization = _window_width = _window_height = 0;
+ _window_x = _window_y = 0;
+ _is_fullscreen = false;
+ _is_closed = true;
+ _min = _max = 0;
+ _title = 0;
+ flush();
+ return *this;
+ }
+
+ CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *const title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!dimw || !dimh) return assign();
+ _assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
+ _min = _max = 0;
+ std::memset(_data,0,sizeof(unsigned int)*_width*_height);
+ return paint();
+ }
+
+ template<typename T>
+ CImgDisplay& assign(const CImg<T>& img, const char *const title=0,
+ const unsigned int normalization_type=3,
+ const bool fullscreen_flag=false, const bool closed_flag=false) {
+ if (!img) return assign();
+ CImg<T> tmp;
+ const CImg<T>& nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2,
+ (img._height - 1)/2,
+ (img._depth - 1)/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 - 1)/2,
+ (img._height - 1)/2,
+ (img._depth - 1)/2));
+ _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag);
+ if (_normalization==2) _min = (float)nimg.min_max(_max);
+ return display(nimg);
+ }
+
+ CImgDisplay& assign(const CImgDisplay& disp) {
+ if (!disp) return assign();
+ _assign(disp._width,disp._height,disp._title,disp._normalization,disp._is_fullscreen,disp._is_closed);
+ std::memcpy(_data,disp._data,sizeof(unsigned int)*_width*_height);
+ return paint();
+ }
+
+ CImgDisplay& resize(const int nwidth, const int nheight, const bool force_redraw=true) {
+ if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
+ if (is_empty()) return assign(nwidth,nheight);
+ const unsigned int
+ tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100),
+ tmpdimy = (nheight>0)?nheight:(-nheight*_height/100),
+ dimx = tmpdimx?tmpdimx:1,
+ dimy = tmpdimy?tmpdimy:1;
+ if (_width!=dimx || _height!=dimy || _window_width!=dimx || _window_height!=dimy) {
+ if (_window_width!=dimx || _window_height!=dimy) {
+ RECT rect; rect.left = rect.top = 0; rect.right = (LONG)dimx - 1; rect.bottom = (LONG)dimy - 1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int cwidth = rect.right - rect.left + 1, cheight = rect.bottom - rect.top + 1;
+ SetWindowPos(_window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS);
+ }
+ if (_width!=dimx || _height!=dimy) {
+ unsigned int *const ndata = new unsigned int[dimx*dimy];
+ if (force_redraw) _render_resize(_data,_width,_height,ndata,dimx,dimy);
+ else std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
+ delete[] _data;
+ _data = ndata;
+ _bmi.bmiHeader.biWidth = (LONG)dimx;
+ _bmi.bmiHeader.biHeight = -(int)dimy;
+ _width = dimx;
+ _height = dimy;
+ }
+ _window_width = dimx; _window_height = dimy;
+ show();
+ }
+ _is_resized = false;
+ if (_is_fullscreen) move((screen_width() - width())/2,(screen_height() - height())/2);
+ if (force_redraw) return paint();
+ return *this;
+ }
+
+ CImgDisplay& toggle_fullscreen(const bool force_redraw=true) {
+ if (is_empty()) return *this;
+ if (force_redraw) {
+ const cimg_ulong buf_size = (cimg_ulong)_width*_height*4;
+ void *odata = std::malloc(buf_size);
+ if (odata) {
+ std::memcpy(odata,_data,buf_size);
+ assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+ std::memcpy(_data,odata,buf_size);
+ std::free(odata);
+ }
+ return paint();
+ }
+ return assign(_width,_height,_title,_normalization,!_is_fullscreen,false);
+ }
+
+ CImgDisplay& show() {
+ if (is_empty() || !_is_closed) return *this;
+ _is_closed = false;
+ if (_is_fullscreen) _init_fullscreen();
+ ShowWindow(_window,SW_SHOW);
+ _update_window_pos();
+ return paint();
+ }
+
+ CImgDisplay& close() {
+ if (is_empty() || _is_closed) return *this;
+ _is_closed = true;
+ if (_is_fullscreen) _desinit_fullscreen();
+ ShowWindow(_window,SW_HIDE);
+ _window_x = _window_y = 0;
+ return *this;
+ }
+
+ CImgDisplay& move(const int posx, const int posy) {
+ if (is_empty()) return *this;
+ if (_window_x!=posx || _window_y!=posy) {
+ if (!_is_fullscreen) {
+ RECT rect;
+ rect.left = rect.top = 0; rect.right = (LONG)_window_width - 1; rect.bottom = (LONG)_window_height - 1;
+ AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
+ const int
+ border1 = (int)((rect.right - rect.left + 1 -_width)/2),
+ border2 = (int)(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;
+ show();
+ }
+ _is_moved = false;
+ return *this;
+ }
+
+ CImgDisplay& show_mouse() {
+ if (is_empty()) return *this;
+ _is_cursor_visible = true;
+ return *this;
+ }
+
+ CImgDisplay& hide_mouse() {
+ if (is_empty()) return *this;
+ _is_cursor_visible = false;
+ return *this;
+ }
+
+ CImgDisplay& set_mouse(const int posx, const int posy) {
+ if (is_empty() || _is_closed || posx<0 || posy<0) return *this;
+ _update_window_pos();
+ const int res = (int)SetCursorPos(_window_x + posx,_window_y + posy);
+ if (res) { _mouse_x = posx; _mouse_y = posy; }
+ return *this;
+ }
+
+ CImgDisplay& set_title(const char *const format, ...) {
+ if (is_empty()) return *this;
+ char *const tmp = new char[1024];
+ va_list ap;
+ va_start(ap, format);
+ cimg_vsnprintf(tmp,1024,format,ap);
+ va_end(ap);
+ if (!std::strcmp(_title,tmp)) { delete[] tmp; return *this; }
+ delete[] _title;
+ const unsigned int s = (unsigned int)std::strlen(tmp) + 1;
+ _title = new char[s];
+ std::memcpy(_title,tmp,s*sizeof(char));
+ SetWindowTextA(_window, tmp);
+ delete[] tmp;
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& display(const CImg<T>& img) {
+ if (!img)
+ throw CImgArgumentException(_cimgdisplay_instance
+ "display(): Empty specified image.",
+ cimgdisplay_instance);
+ if (is_empty()) return assign(img);
+ return render(img).paint();
+ }
+
+ CImgDisplay& paint() {
+ if (_is_closed) return *this;
+ WaitForSingleObject(_mutex,INFINITE);
+ SetDIBitsToDevice(_hdc,0,0,_width,_height,0,0,0,_height,_data,&_bmi,DIB_RGB_COLORS);
+ ReleaseMutex(_mutex);
+ return *this;
+ }
+
+ template<typename T>
+ CImgDisplay& render(const CImg<T>& img) {
+ if (!img)
+ throw CImgArgumentException(_cimgdisplay_instance
+ "render(): Empty specified image.",
+ cimgdisplay_instance);
+
+ if (is_empty()) return *this;
+ if (img._depth!=1) return render(img.get_projections2d((img._width - 1)/2,(img._height - 1)/2,
+ (img._depth - 1)/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[(size_t)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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)*(data1++);
+ *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val);
+ }
+ } break;
+ case 2 : {
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)*(data1++),
+ G = (unsigned char)*(data2++);
+ *(ptrd++) = (unsigned int)((R<<16) | (G<<8));
+ }
+ } break;
+ default : {
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)*(data1++),
+ G = (unsigned char)*(data2++),
+ B = (unsigned char)*(data3++);
+ *(ptrd++) = (unsigned int)((R<<16) | (G<<8) | B);
+ }
+ }
+ }
+ } else {
+ if (_normalization==3) {
+ if (cimg::type<T>::is_float()) _min = (float)img.min_max(_max);
+ else { _min = (float)cimg::type<T>::min(); _max = (float)cimg::type<T>::max(); }
+ } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max);
+ const float delta = _max - _min, mm = 255/(delta?delta:1.0f);
+ switch (img._spectrum) {
+ case 1 : {
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char val = (unsigned char)((*(data1++) - _min)*mm);
+ *(ptrd++) = (unsigned int)((val<<16) | (val<<8) | val);
+ }
+ } break;
+ case 2 : {
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)((*(data1++) - _min)*mm),
+ G = (unsigned char)((*(data2++) - _min)*mm);
+ *(ptrd++) = (unsigned int)((R<<16) | (G<<8));
+ }
+ } break;
+ default : {
+ for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned char
+ R = (unsigned char)((*(data1++) - _min)*mm),
+ G = (unsigned char)((*(data2++) - _min)*mm),
+ B = (unsigned char)((*(data3++) - _min)*mm);
+ *(ptrd++) = (unsigned int)((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>
+ static void screenshot(const int x0, const int y0, const int x1, const int y1, CImg<T>& img) {
+ img.assign();
+ HDC hScreen = GetDC(GetDesktopWindow());
+ if (hScreen) {
+ const int
+ width = GetDeviceCaps(hScreen,HORZRES),
+ height = GetDeviceCaps(hScreen,VERTRES);
+ int _x0 = x0, _y0 = y0, _x1 = x1, _y1 = y1;
+ if (_x0>_x1) cimg::swap(_x0,_x1);
+ if (_y0>_y1) cimg::swap(_y0,_y1);
+ if (_x1>=0 && _x0<width && _y1>=0 && _y0<height) {
+ _x0 = std::max(_x0,0);
+ _y0 = std::max(_y0,0);
+ _x1 = std::min(_x1,width - 1);
+ _y1 = std::min(_y1,height - 1);
+ const int bw = _x1 - _x0 + 1, bh = _y1 - _y0 + 1;
+ HDC hdcMem = CreateCompatibleDC(hScreen);
+ if (hdcMem) {
+ HBITMAP hBitmap = CreateCompatibleBitmap(hScreen,bw,bh);
+ if (hBitmap) {
+ HGDIOBJ hOld = SelectObject(hdcMem,hBitmap);
+ if (hOld && BitBlt(hdcMem,0,0,bw,bh,hScreen,_x0,_y0,SRCCOPY) && SelectObject(hdcMem,hOld)) {
+ BITMAPINFOHEADER bmi;
+ bmi.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.biWidth = bw;
+ bmi.biHeight = -bh;
+ bmi.biPlanes = 1;
+ bmi.biBitCount = 32;
+ bmi.biCompression = BI_RGB;
+ bmi.biSizeImage = 0;
+ bmi.biXPelsPerMeter = bmi.biYPelsPerMeter = 0;
+ bmi.biClrUsed = bmi.biClrImportant = 0;
+ unsigned char *buf = new unsigned char[4*bw*bh];
+ if (GetDIBits(hdcMem,hBitmap,0,bh,buf,(BITMAPINFO*)&bmi,DIB_RGB_COLORS)) {
+ img.assign(bw,bh,1,3);
+ const unsigned char *ptrs = buf;
+ T *pR = img.data(0,0,0,0), *pG = img.data(0,0,0,1), *pB = img.data(0,0,0,2);
+ cimg_forXY(img,x,y) {
+ *(pR++) = (T)ptrs[2];
+ *(pG++) = (T)ptrs[1];
+ *(pB++) = (T)ptrs[0];
+ ptrs+=4;
+ }
+ }
+ delete[] buf;
+ }
+ DeleteObject(hBitmap);
+ }
+ DeleteDC(hdcMem);
+ }
+ }
+ ReleaseDC(GetDesktopWindow(),hScreen);
+ }
+ if (img.is_empty())
+ throw CImgDisplayException("CImgDisplay::screenshot(): Failed to take screenshot "
+ "with coordinates (%d,%d)-(%d,%d).",
+ x0,y0,x1,y1);
+ }
+
+ 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 (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) {
+ const unsigned int val = *(ptrs++);
+ *(data1++) = (T)(unsigned char)(val>>16);
+ *(data2++) = (T)(unsigned char)((val>>8)&0xFF);
+ *(data3++) = (T)(unsigned char)(val&0xFF);
+ }
+ return *this;
+ }
+#endif
+
+ //@}
+ };
+
+ /*
+ #--------------------------------------
+ #
+ #
+ #
+ # Definition of the CImg<T> structure
+ #
+ #
+ #
+ #--------------------------------------
+ */
+
+ //! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T.
+ /**
+ This is the main class of the %CImg Library. It declares and constructs
+ an image, allows access to its pixel values, and is able to perform various image operations.
+
+ \par Image representation
+
+ A %CImg image is defined as an instance of the container \c CImg<T>, which contains a regular grid of pixels,
+ each pixel value being of type \c T. The image grid can have up to 4 dimensions: width, height, depth
+ and number of channels.
+ Usually, the three first dimensions are used to describe spatial coordinates <tt>(x,y,z)</tt>,
+ while the number of channels is rather used as a vector-valued dimension
+ (it may describe the R,G,B color channels for instance).
+ If you need a fifth dimension, you can use image lists \c CImgList<T> rather than simple images \c CImg<T>.
+
+ Thus, the \c CImg<T> class is able to represent volumetric images of vector-valued pixels,
+ as well as images with less dimensions (1d scalar signal, 2d color images, ...).
+ Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions.
+
+ Concerning the pixel value type \c T:
+ fully supported template types are the basic C++ types: <tt>unsigned char, char, short, unsigned int, int,
+ unsigned long, long, float, double, ... </tt>.
+ Typically, fast image display can be done using <tt>CImg<unsigned char></tt> images,
+ while complex image processing algorithms may be rather coded using <tt>CImg<float></tt> or <tt>CImg<double></tt>
+ images that have floating-point pixel values. The default value for the template T is \c float.
+ Using your own template types may be possible. However, you will certainly have to define the complete set
+ of arithmetic and logical operators for your class.
+
+ \par Image structure
+
+ The \c CImg<T> structure contains \e six fields:
+ - \c _width defines the number of \a columns of the image (size along the X-axis).
+ - \c _height defines the number of \a rows of the image (size along the Y-axis).
+ - \c _depth defines the number of \a slices of the image (size along the Z-axis).
+ - \c _spectrum defines the number of \a channels of the image (size along the C-axis).
+ - \c _data defines a \a pointer to the \a pixel \a data (of type \c T).
+ - \c _is_shared is a boolean that tells if the memory buffer \c data is shared with
+ another image.
+
+ You can access these fields publicly although it is recommended to use the dedicated functions
+ width(), height(), depth(), spectrum() and ptr() to do so.
+ Image dimensions are not limited to a specific range (as long as you got enough available memory).
+ A value of \e 1 usually means that the corresponding dimension is \a flat.
+ If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty.
+ Empty images should not contain any pixel data and thus, will not be processed by CImg member functions
+ (a CImgInstanceException will be thrown instead).
+ Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage).
+
+ \par Image declaration and construction
+
+ Declaring an image can be done by using one of the several available constructors.
+ Here is a list of the most used:
+
+ - Construct images from arbitrary dimensions:
+ - <tt>CImg<char> img;</tt> declares an empty image.
+ - <tt>CImg<unsigned char> img(128,128);</tt> declares a 128x128 greyscale image with
+ \c unsigned \c char pixel values.
+ - <tt>CImg<double> img(3,3);</tt> declares a 3x3 matrix with \c double coefficients.
+ - <tt>CImg<unsigned char> img(256,256,1,3);</tt> declares a 256x256x1x3 (color) image
+ (colors are stored as an image with three channels).
+ - <tt>CImg<double> img(128,128,128);</tt> declares a 128x128x128 volumetric and greyscale image
+ (with \c double pixel values).
+ - <tt>CImg<> img(128,128,128,3);</tt> declares a 128x128x128 volumetric color image
+ (with \c float pixels, which is the default value of the template parameter \c T).
+ - \b Note: images pixels are <b>not automatically initialized to 0</b>. You may use the function \c fill() to
+ do it, or use the specific constructor taking 5 parameters like this:
+ <tt>CImg<> img(128,128,128,3,0);</tt> declares a 128x128x128 volumetric color image with all pixel values to 0.
+
+ - Construct images from filenames:
+ - <tt>CImg<unsigned char> img("image.jpg");</tt> reads a JPEG color image from the file "image.jpg".
+ - <tt>CImg<float> img("analyze.hdr");</tt> reads a volumetric image (ANALYZE7.5 format) from the
+ file "analyze.hdr".
+ - \b Note: You need to install <a href="http://www.imagemagick.org">ImageMagick</a>
+ to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io).
+
+ - Construct images from C-style arrays:
+ - <tt>CImg<int> img(data_buffer,256,256);</tt> constructs a 256x256 greyscale image from a \c int* buffer
+ \c data_buffer (of size 256x256=65536).
+ - <tt>CImg<unsigned char> img(data_buffer,256,256,1,3);</tt> constructs a 256x256 color image
+ from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others).
+
+ The complete list of constructors can be found <a href="#constructors">here</a>.
+
+ \par Most useful functions
+
+ The \c CImg<T> class contains a lot of functions that operates on images.
+ Some of the most useful are:
+
+ - operator()(): Read or write pixel values.
+ - display(): displays the image in a new window.
+ **/
+ template<typename T>
+ struct CImg {
+
+ unsigned int _width, _height, _depth, _spectrum;
+ bool _is_shared;
+ T *_data;
+
+ //! Simple iterator type, to loop through each pixel value of an image instance.
+ /**
+ \note
+ - The \c CImg<T>::iterator type is defined to be a <tt>T*</tt>.
+ - You will seldom have to use iterators in %CImg, most classical operations
+ being achieved (often in a faster way) using methods of \c CImg<T>.
+ \par Example
+ \code
+ CImg<float> img("reference.jpg"); // Load image from file.
+ // Set all pixels to '0', with a CImg iterator.
+ for (CImg<float>::iterator it = img.begin(), it<img.end(); ++it) *it = 0;
+ img.fill(0); // Do the same with a built-in method.
+ \endcode
+ **/
+ typedef T* iterator;
+
+ //! Simple const iterator type, to loop through each pixel value of a \c const image instance.
+ /**
+ \note
+ - The \c CImg<T>::const_iterator type is defined to be a \c const \c T*.
+ - You will seldom have to use iterators in %CImg, most classical operations
+ being achieved (often in a faster way) using methods of \c CImg<T>.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"); // Load image from file.
+ float sum = 0;
+ // Compute sum of all pixel values, with a CImg iterator.
+ for (CImg<float>::iterator it = img.begin(), it<img.end(); ++it) sum+=*it;
+ const float sum2 = img.sum(); // Do the same with a built-in method.
+ \endcode
+ **/
+ typedef const T* const_iterator;
+
+ //! Pixel value type.
+ /**
+ Refer to the type of the pixel values of an image instance.
+ \note
+ - The \c CImg<T>::value_type type of a \c CImg<T> is defined to be a \c T.
+ - \c CImg<T>::value_type is actually not used in %CImg methods. It has been mainly defined for
+ compatibility with STL naming conventions.
+ **/
+ typedef T value_type;
+
+ // Define common types related to template type T.
+ typedef typename cimg::superset<T,bool>::type Tbool;
+ typedef typename cimg::superset<T,unsigned char>::type Tuchar;
+ typedef typename cimg::superset<T,char>::type Tchar;
+ typedef typename cimg::superset<T,unsigned short>::type Tushort;
+ typedef typename cimg::superset<T,short>::type Tshort;
+ typedef typename cimg::superset<T,unsigned int>::type Tuint;
+ typedef typename cimg::superset<T,int>::type Tint;
+ typedef typename cimg::superset<T,cimg_ulong>::type Tulong;
+ typedef typename cimg::superset<T,cimg_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,cimg_ulong>::type ulongT;
+ typedef typename cimg::last<T,cimg_long>::type longT;
+ typedef typename cimg::last<T,cimg_uint64>::type uint64T;
+ typedef typename cimg::last<T,cimg_int64>::type int64T;
+ 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
+ //@{
+ //---------------------------------------------------------
+
+ //! Destroy image.
+ /**
+ \note
+ - The pixel buffer data() is deallocated if necessary, e.g. for non-empty and non-shared image instances.
+ - Destroying an empty or shared image does nothing actually.
+ \warning
+ - When destroying a non-shared image, make sure that you will \e not operate on a remaining shared image
+ that shares its buffer with the destroyed instance, in order to avoid further invalid memory access
+ (to a deallocated buffer).
+ **/
+ ~CImg() {
+ if (!_is_shared) delete[] _data;
+ }
+
+ //! Construct empty image.
+ /**
+ \note
+ - An empty image has no pixel data and all of its dimensions width(), height(), depth(), spectrum()
+ are set to \c 0, as well as its pixel buffer pointer data().
+ - An empty image may be re-assigned afterwards, e.g. with the family of
+ assign(unsigned int,unsigned int,unsigned int,unsigned int) methods,
+ or by operator=(const CImg<t>&). In all cases, the type of pixels stays \c T.
+ - An empty image is never shared.
+ \par Example
+ \code
+ CImg<float> img1, img2; // Construct two empty images.
+ img1.assign(256,256,1,3); // Re-assign 'img1' to be a 256x256x1x3 (color) image.
+ img2 = img1.get_rand(0,255); // Re-assign 'img2' to be a random-valued version of 'img1'.
+ img2.assign(); // Re-assign 'img2' to be an empty image again.
+ \endcode
+ **/
+ CImg():_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {}
+
+ //! Construct image with specified size.
+ /**
+ \param size_x Image width().
+ \param size_y Image height().
+ \param size_z Image depth().
+ \param size_c Image spectrum() (number of channels).
+ \note
+ - It is able to create only \e non-shared images, and allocates thus a pixel buffer data()
+ for each constructed image instance.
+ - Setting one dimension \c size_x,\c size_y,\c size_z or \c size_c to \c 0 leads to the construction of
+ an \e empty image.
+ - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated
+ (e.g. when requested size is too big for available memory).
+ \warning
+ - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values.
+ In order to initialize pixel values during construction (e.g. with \c 0), use constructor
+ CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead.
+ \par Example
+ \code
+ CImg<float> img1(256,256,1,3); // Construct a 256x256x1x3 (color) image, filled with garbage values.
+ CImg<float> img2(256,256,1,3,0); // Construct a 256x256x1x3 (color) image, filled with value '0'.
+ \endcode
+ **/
+ explicit CImg(const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1):
+ _is_shared(false) {
+ size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (siz) {
+ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+ try { _data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
+ size_x,size_y,size_z,size_c);
+ }
+ } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+ }
+
+ //! Construct image with specified size and initialize pixel values.
+ /**
+ \param size_x Image width().
+ \param size_y Image height().
+ \param size_z Image depth().
+ \param size_c Image spectrum() (number of channels).
+ \param value Initialization value.
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int),
+ but it also fills the pixel buffer with the specified \c value.
+ \warning
+ - It cannot be used to construct a vector-valued image and initialize it with \e vector-valued pixels
+ (e.g. RGB vector, for color images).
+ For this task, you may use fillC() after construction.
+ **/
+ CImg(const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c, const T& value):
+ _is_shared(false) {
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (siz) {
+ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+ try { _data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
+ size_x,size_y,size_z,size_c);
+ }
+ fill(value);
+ } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+ }
+
+ //! Construct image with specified size and initialize pixel values from a sequence of integers.
+ /**
+ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c,
+ with pixels of type \c T, and initialize pixel
+ values from the specified sequence of integers \c value0,\c value1,\c ...
+ \param size_x Image width().
+ \param size_y Image height().
+ \param size_z Image depth().
+ \param size_c Image spectrum() (number of channels).
+ \param value0 First value of the initialization sequence (must be an \e integer).
+ \param value1 Second value of the initialization sequence (must be an \e integer).
+ \param ...
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills
+ the pixel buffer with a sequence of specified integer values.
+ \warning
+ - You must specify \e exactly \c size_x*\c size_y*\c size_z*\c size_c integers in the initialization sequence.
+ Otherwise, the constructor may crash or fill your image pixels with garbage.
+ \par Example
+ \code
+ const CImg<float> img(2,2,1,3, // Construct a 2x2 color (RGB) image.
+ 0,255,0,255, // Set the 4 values for the red component.
+ 0,0,255,255, // Set the 4 values for the green component.
+ 64,64,64,64); // Set the 4 values for the blue component.
+ img.resize(150,150).display();
+ \endcode
+ \image html ref_constructor1.jpg
+ **/
+ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+ const int value0, const int value1, ...):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+#define _CImg_stdarg(img,a0,a1,N,t) { \
+ size_t _siz = (size_t)N; \
+ if (_siz--) { \
+ va_list ap; \
+ va_start(ap,a1); \
+ T *ptrd = (img)._data; \
+ *(ptrd++) = (T)a0; \
+ if (_siz--) { \
+ *(ptrd++) = (T)a1; \
+ for ( ; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \
+ } \
+ va_end(ap); \
+ } \
+ }
+ assign(size_x,size_y,size_z,size_c);
+ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int);
+ }
+
+#if cimg_use_cpp11==1
+ //! Construct image with specified size and initialize pixel values from an initializer list of integers.
+ /**
+ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c,
+ with pixels of type \c T, and initialize pixel
+ values from the specified initializer list of integers { \c value0,\c value1,\c ... }
+ \param size_x Image width().
+ \param size_y Image height().
+ \param size_z Image depth().
+ \param size_c Image spectrum() (number of channels).
+ \param { value0, value1, ... } Initialization list
+ \param repeat_values Tells if the value filling process is repeated over the image.
+
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills
+ the pixel buffer with a sequence of specified integer values.
+ \par Example
+ \code
+ const CImg<float> img(2,2,1,3, // Construct a 2x2 color (RGB) image.
+ { 0,255,0,255, // Set the 4 values for the red component.
+ 0,0,255,255, // Set the 4 values for the green component.
+ 64,64,64,64 }); // Set the 4 values for the blue component.
+ img.resize(150,150).display();
+ \endcode
+ \image html ref_constructor1.jpg
+ **/
+ template<typename t>
+ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+ const std::initializer_list<t> values,
+ const bool repeat_values=true):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+#define _cimg_constructor_cpp11(repeat_values) \
+ auto it = values.begin(); \
+ size_t siz = size(); \
+ if (repeat_values) for (T *ptrd = _data; siz--; ) { \
+ *(ptrd++) = (T)(*(it++)); if (it==values.end()) it = values.begin(); } \
+ else { siz = std::min(siz,values.size()); for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++)); }
+ assign(size_x,size_y,size_z,size_c);
+ _cimg_constructor_cpp11(repeat_values);
+ }
+
+ template<typename t>
+ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z,
+ std::initializer_list<t> values,
+ const bool repeat_values=true):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(size_x,size_y,size_z);
+ _cimg_constructor_cpp11(repeat_values);
+ }
+
+ template<typename t>
+ CImg(const unsigned int size_x, const unsigned int size_y,
+ std::initializer_list<t> values,
+ const bool repeat_values=true):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(size_x,size_y);
+ _cimg_constructor_cpp11(repeat_values);
+ }
+
+ template<typename t>
+ CImg(const unsigned int size_x,
+ std::initializer_list<t> values,
+ const bool repeat_values=true):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(size_x);
+ _cimg_constructor_cpp11(repeat_values);
+ }
+
+ //! Construct single channel 1D image with pixel values and width obtained from an initializer list of integers.
+ /**
+ Construct a new image instance of size \c width x \c 1 x \c 1 x \c 1,
+ with pixels of type \c T, and initialize pixel
+ values from the specified initializer list of integers { \c value0,\c value1,\c ... }. Image width is
+ given by the size of the initializer list.
+ \param { value0, value1, ... } Initialization list
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int) with height=1, depth=1, and spectrum=1,
+ but it also fills the pixel buffer with a sequence of specified integer values.
+ \par Example
+ \code
+ const CImg<float> img = {10,20,30,20,10 }; // Construct a 5x1 image with one channel, and set its pixel values.
+ img.resize(150,150).display();
+ \endcode
+ \image html ref_constructor1.jpg
+ **/
+ template<typename t>
+ CImg(const std::initializer_list<t> values):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(values.size(),1,1,1);
+ auto it = values.begin();
+ unsigned int siz = _width;
+ for (T *ptrd = _data; siz--; ) *(ptrd++) = (T)(*(it++));
+ }
+
+ template<typename t>
+ CImg<T> & operator=(std::initializer_list<t> values) {
+ _cimg_constructor_cpp11(siz>values.size());
+ return *this;
+ }
+#endif
+
+ //! Construct image with specified size and initialize pixel values from a sequence of doubles.
+ /**
+ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T,
+ and initialize pixel values from the specified sequence of doubles \c value0,\c value1,\c ...
+ \param size_x Image width().
+ \param size_y Image height().
+ \param size_z Image depth().
+ \param size_c Image spectrum() (number of channels).
+ \param value0 First value of the initialization sequence (must be a \e double).
+ \param value1 Second value of the initialization sequence (must be a \e double).
+ \param ...
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...), but
+ takes a sequence of double values instead of integers.
+ \warning
+ - You must specify \e exactly \c dx*\c dy*\c dz*\c dc doubles in the initialization sequence.
+ Otherwise, the constructor may crash or fill your image with garbage.
+ For instance, the code below will probably crash on most platforms:
+ \code
+ const CImg<float> img(2,2,1,1, 0.5,0.5,255,255); // FAIL: The two last arguments are 'int', not 'double'!
+ \endcode
+ **/
+ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+ const double value0, const double value1, ...):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(size_x,size_y,size_z,size_c);
+ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double);
+ }
+
+ //! Construct image with specified size and initialize pixel values from a value string.
+ /**
+ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T,
+ and initializes pixel values from the specified string \c values.
+ \param size_x Image width().
+ \param size_y Image height().
+ \param size_z Image depth().
+ \param size_c Image spectrum() (number of channels).
+ \param values Value string describing the way pixel values are set.
+ \param repeat_values Tells if the value filling process is repeated over the image.
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it also fills
+ the pixel buffer with values described in the value string \c values.
+ - Value string \c values may describe two different filling processes:
+ - Either \c values is a sequences of values assigned to the image pixels, as in <tt>"1,2,3,7,8,2"</tt>.
+ In this case, set \c repeat_values to \c true to periodically fill the image with the value sequence.
+ - Either, \c values is a formula, as in <tt>"cos(x/10)*sin(y/20)"</tt>.
+ In this case, parameter \c repeat_values is pointless.
+ - For both cases, specifying \c repeat_values is mandatory.
+ It disambiguates the possible overloading of constructor
+ CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) with \c T being a <tt>const char*</tt>.
+ - A \c CImgArgumentException is thrown when an invalid value string \c values is specified.
+ \par Example
+ \code
+ const CImg<float> img1(129,129,1,3,"0,64,128,192,255",true), // Construct image from a value sequence.
+ img2(129,129,1,3,"if(c==0,255*abs(cos(x/10)),1.8*y)",false); // Construct image from a formula.
+ (img1,img2).display();
+ \endcode
+ \image html ref_constructor2.jpg
+ **/
+ CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, const unsigned int size_c,
+ const char *const values, const bool repeat_values):_is_shared(false) {
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (siz) {
+ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+ try { _data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
+ size_x,size_y,size_z,size_c);
+ }
+ fill(values,repeat_values);
+ } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+ }
+
+ //! Construct image with specified size and initialize pixel values from a memory buffer.
+ /**
+ Construct a new image instance of size \c size_x x \c size_y x \c size_z x \c size_c, with pixels of type \c T,
+ and initializes pixel values from the specified \c t* memory buffer.
+ \param values Pointer to the input memory buffer.
+ \param size_x Image width().
+ \param size_y Image height().
+ \param size_z Image depth().
+ \param size_c Image spectrum() (number of channels).
+ \param is_shared Tells if input memory buffer must be shared by the current instance.
+ \note
+ - If \c is_shared is \c false, the image instance allocates its own pixel buffer,
+ and values from the specified input buffer are copied to the instance buffer.
+ If buffer types \c T and \c t are different, a regular static cast is performed during buffer copy.
+ - Otherwise, the image instance does \e not allocate a new buffer, and uses the input memory buffer as its
+ own pixel buffer. This case requires that types \c T and \c t are the same. Later, destroying such a shared
+ image will not deallocate the pixel buffer, this task being obviously charged to the initial buffer allocator.
+ - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated
+ (e.g. when requested size is too big for available memory).
+ \warning
+ - You must take care when operating on a shared image, since it may have an invalid pixel buffer pointer data()
+ (e.g. already deallocated).
+ \par Example
+ \code
+ unsigned char tab[256*256] = { 0 };
+ CImg<unsigned char> img1(tab,256,256,1,1,false), // Construct new non-shared image from buffer 'tab'.
+ img2(tab,256,256,1,1,true); // Construct new shared-image from buffer 'tab'.
+ tab[1024] = 255; // Here, 'img2' is indirectly modified, but not 'img1'.
+ \endcode
+ **/
+ template<typename t>
+ CImg(const t *const values, const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false):_is_shared(false) {
+ if (is_shared) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgArgumentException(_cimg_instance
+ "CImg(): Invalid construction request of a (%u,%u,%u,%u) shared instance "
+ "from a (%s*) buffer (pixel types are different).",
+ cimg_instance,
+ size_x,size_y,size_z,size_c,CImg<t>::pixel_type());
+ }
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (values && siz) {
+ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+ try { _data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
+ size_x,size_y,size_z,size_c);
+
+ }
+ const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++);
+ } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+ }
+
+ //! Construct image with specified size and initialize pixel values from a memory buffer \specialization.
+ CImg(const T *const values, const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1, const bool is_shared=false) {
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (values && siz) {
+ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = is_shared;
+ if (_is_shared) _data = const_cast<T*>(values);
+ else {
+ try { _data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
+ size_x,size_y,size_z,size_c);
+ }
+ std::memcpy(_data,values,siz*sizeof(T));
+ }
+ } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+ }
+
+ //! Construct image from reading an image file.
+ /**
+ Construct a new image instance with pixels of type \c T, and initialize pixel values with the data read from
+ an image file.
+ \param filename Filename, as a C-string.
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it reads the image
+ dimensions and pixel values from the specified image file.
+ - The recognition of the image file format by %CImg higly depends on the tools installed on your system
+ and on the external libraries you used to link your code against.
+ - Considered pixel type \c T should better fit the file format specification, or data loss may occur during
+ file load (e.g. constructing a \c CImg<unsigned char> from a float-valued image file).
+ - A \c CImgIOException is thrown when the specified \c filename cannot be read, or if the file format is not
+ recognized.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg");
+ img.display();
+ \endcode
+ \image html ref_image.jpg
+ **/
+ explicit CImg(const char *const filename):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(filename);
+ }
+
+ //! Construct image copy.
+ /**
+ Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg<t> instance.
+ \param img Input image to copy.
+ \note
+ - Constructed copy has the same size width() x height() x depth() x spectrum() and pixel values as the
+ input image \c img.
+ - If input image \c img is \e shared and if types \c T and \c t are the same, the constructed copy is also
+ \e shared, and shares its pixel buffer with \c img.
+ Modifying a pixel value in the constructed copy will thus also modifies it in the input image \c img.
+ This behavior is needful to allow functions to return shared images.
+ - Otherwise, the constructed copy allocates its own pixel buffer, and copies pixel values from the input
+ image \c img into its buffer. The copied pixel values may be eventually statically casted if types \c T and
+ \c t are different.
+ - Constructing a copy from an image \c img when types \c t and \c T are the same is significantly faster than
+ with different types.
+ - A \c CImgInstanceException is thrown when the pixel buffer cannot be allocated
+ (e.g. not enough available memory).
+ **/
+ template<typename t>
+ CImg(const CImg<t>& img):_is_shared(false) {
+ const size_t siz = (size_t)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(sizeof(T)*img._width*img._height*img._depth*img._spectrum),
+ img._width,img._height,img._depth,img._spectrum);
+ }
+ const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++);
+ } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+ }
+
+ //! Construct image copy \specialization.
+ CImg(const CImg<T>& img) {
+ const size_t siz = (size_t)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(sizeof(T)*img._width*img._height*img._depth*img._spectrum),
+ img._width,img._height,img._depth,img._spectrum);
+
+ }
+ std::memcpy(_data,img._data,siz*sizeof(T));
+ }
+ } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+ }
+
+ //! Advanced copy constructor.
+ /**
+ Construct a new image instance with pixels of type \c T, as a copy of an existing \c CImg<t> instance,
+ while forcing the shared state of the constructed copy.
+ \param img Input image to copy.
+ \param is_shared Tells about the shared state of the constructed copy.
+ \note
+ - Similar to CImg(const CImg<t>&), except that it allows to decide the shared state of
+ the constructed image, which does not depend anymore on the shared state of the input image \c img:
+ - If \c is_shared is \c true, the constructed copy will share its pixel buffer with the input image \c img.
+ For that case, the pixel types \c T and \c t \e must be the same.
+ - If \c is_shared is \c false, the constructed copy will allocate its own pixel buffer, whether the input
+ image \c img is shared or not.
+ - A \c CImgArgumentException is thrown when a shared copy is requested with different pixel types \c T and \c t.
+ **/
+ template<typename t>
+ CImg(const CImg<t>& img, const bool is_shared):_is_shared(false) {
+ if (is_shared) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgArgumentException(_cimg_instance
+ "CImg(): Invalid construction request of a shared instance from a "
+ "CImg<%s> image (%u,%u,%u,%u,%p) (pixel types are different).",
+ cimg_instance,
+ CImg<t>::pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data);
+ }
+ const size_t siz = (size_t)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(sizeof(T)*img._width*img._height*img._depth*img._spectrum),
+ img._width,img._height,img._depth,img._spectrum);
+ }
+ const t *ptrs = img._data; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++);
+ } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
+ }
+
+ //! Advanced copy constructor \specialization.
+ CImg(const CImg<T>& img, const bool is_shared) {
+ const size_t siz = (size_t)img.size();
+ if (img._data && siz) {
+ _width = img._width; _height = img._height; _depth = img._depth; _spectrum = img._spectrum;
+ _is_shared = is_shared;
+ if (_is_shared) _data = const_cast<T*>(img._data);
+ else {
+ try { _data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*img._width*img._height*img._depth*img._spectrum),
+ img._width,img._height,img._depth,img._spectrum);
+ }
+ std::memcpy(_data,img._data,siz*sizeof(T));
+ }
+ } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; }
+ }
+
+ //! Construct image with dimensions borrowed from another image.
+ /**
+ Construct a new image instance with pixels of type \c T, and size get from some dimensions of an existing
+ \c CImg<t> instance.
+ \param img Input image from which dimensions are borrowed.
+ \param dimensions C-string describing the image size along the X,Y,Z and C-dimensions.
+ \note
+ - Similar to CImg(unsigned int,unsigned int,unsigned int,unsigned int), but it takes the image dimensions
+ (\e not its pixel values) from an existing \c CImg<t> instance.
+ - The allocated pixel buffer is \e not filled with a default value, and is likely to contain garbage values.
+ In order to initialize pixel values (e.g. with \c 0), use constructor CImg(const CImg<t>&,const char*,T)
+ instead.
+ \par Example
+ \code
+ const CImg<float> img1(256,128,1,3), // 'img1' is a 256x128x1x3 image.
+ img2(img1,"xyzc"), // 'img2' is a 256x128x1x3 image.
+ img3(img1,"y,x,z,c"), // 'img3' is a 128x256x1x3 image.
+ img4(img1,"c,x,y,3",0), // 'img4' is a 3x128x256x3 image (with pixels initialized to '0').
+ \endcode
+ **/
+ template<typename t>
+ CImg(const CImg<t>& img, const char *const dimensions):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(img,dimensions);
+ }
+
+ //! Construct image with dimensions borrowed from another image and initialize pixel values.
+ /**
+ Construct a new image instance with pixels of type \c T, and size get from the dimensions of an existing
+ \c CImg<t> instance, and set all pixel values to specified \c value.
+ \param img Input image from which dimensions are borrowed.
+ \param dimensions String describing the image size along the X,Y,Z and V-dimensions.
+ \param value Value used for initialization.
+ \note
+ - Similar to CImg(const CImg<t>&,const char*), but it also fills the pixel buffer with the specified \c value.
+ **/
+ template<typename t>
+ CImg(const CImg<t>& img, const char *const dimensions, const T& value):
+ _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ assign(img,dimensions).fill(value);
+ }
+
+ //! Construct image from a display window.
+ /**
+ Construct a new image instance with pixels of type \c T, as a snapshot of an existing \c CImgDisplay instance.
+ \param disp Input display window.
+ \note
+ - The width() and height() of the constructed image instance are the same as the specified \c CImgDisplay.
+ - The depth() and spectrum() of the constructed image instance are respectively set to \c 1 and \c 3
+ (i.e. a 2d color image).
+ - The image pixels are read as 8-bits RGB values.
+ **/
+ explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ disp.snapshot(*this);
+ }
+
+ // Constructor and assignment operator for rvalue references (c++11).
+ // This avoids an additional image copy for methods returning new images. Can save RAM for big images !
+#if cimg_use_cpp11==1
+ CImg(CImg<T>&& img):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
+ swap(img);
+ }
+ CImg<T>& operator=(CImg<T>&& img) {
+ if (_is_shared) return assign(img);
+ return img.swap(*this);
+ }
+#endif
+
+ //! Construct empty image \inplace.
+ /**
+ In-place version of the default constructor CImg(). It simply resets the instance to an empty image.
+ **/
+ CImg<T>& assign() {
+ if (!_is_shared) delete[] _data;
+ _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0;
+ return *this;
+ }
+
+ //! Construct image with specified size \inplace.
+ /**
+ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int).
+ **/
+ CImg<T>& assign(const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1) {
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (!siz) return assign();
+ const size_t curr_siz = (size_t)size();
+ if (siz!=curr_siz) {
+ if (_is_shared)
+ throw CImgArgumentException(_cimg_instance
+ "assign(): Invalid assignement request of shared instance from specified "
+ "image (%u,%u,%u,%u).",
+ cimg_instance,
+ size_x,size_y,size_z,size_c);
+ else {
+ delete[] _data;
+ try { _data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
+ size_x,size_y,size_z,size_c);
+ }
+ }
+ }
+ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+ return *this;
+ }
+
+ //! Construct image with specified size and initialize pixel values \inplace.
+ /**
+ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,T).
+ **/
+ CImg<T>& assign(const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c, const T& value) {
+ return assign(size_x,size_y,size_z,size_c).fill(value);
+ }
+
+ //! Construct image with specified size and initialize pixel values from a sequence of integers \inplace.
+ /**
+ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,int,int,...).
+ **/
+ CImg<T>& assign(const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c,
+ const int value0, const int value1, ...) {
+ assign(size_x,size_y,size_z,size_c);
+ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,int);
+ return *this;
+ }
+
+ //! Construct image with specified size and initialize pixel values from a sequence of doubles \inplace.
+ /**
+ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,double,double,...).
+ **/
+ CImg<T>& assign(const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c,
+ const double value0, const double value1, ...) {
+ assign(size_x,size_y,size_z,size_c);
+ _CImg_stdarg(*this,value0,value1,(size_t)size_x*size_y*size_z*size_c,double);
+ return *this;
+ }
+
+ //! Construct image with specified size and initialize pixel values from a value string \inplace.
+ /**
+ In-place version of the constructor CImg(unsigned int,unsigned int,unsigned int,unsigned int,const char*,bool).
+ **/
+ CImg<T>& assign(const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c,
+ const char *const values, const bool repeat_values) {
+ return assign(size_x,size_y,size_z,size_c).fill(values,repeat_values);
+ }
+
+ //! Construct image with specified size and initialize pixel values from a memory buffer \inplace.
+ /**
+ In-place version of the constructor CImg(const t*,unsigned int,unsigned int,unsigned int,unsigned int).
+ **/
+ template<typename t>
+ CImg<T>& assign(const t *const values, const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1) {
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (!values || !siz) return assign();
+ assign(size_x,size_y,size_z,size_c);
+ const t *ptrs = values; cimg_for(*this,ptrd,T) *ptrd = (T)*(ptrs++);
+ return *this;
+ }
+
+ //! Construct image with specified size and initialize pixel values from a memory buffer \specialization.
+ CImg<T>& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1) {
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (!values || !siz) return assign();
+ const size_t curr_siz = (size_t)size();
+ if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c);
+ if (_is_shared || values + siz<_data || values>=_data + size()) {
+ assign(size_x,size_y,size_z,size_c);
+ if (_is_shared) std::memmove(_data,values,siz*sizeof(T));
+ else std::memcpy(_data,values,siz*sizeof(T));
+ } else {
+ T *new_data = 0;
+ try { new_data = new T[siz]; } catch (...) {
+ _width = _height = _depth = _spectrum = 0; _data = 0;
+ throw CImgInstanceException(_cimg_instance
+ "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
+ cimg_instance,
+ cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
+ size_x,size_y,size_z,size_c);
+ }
+ std::memcpy(new_data,values,siz*sizeof(T));
+ delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
+ }
+ return *this;
+ }
+
+ //! Construct image with specified size and initialize pixel values from a memory buffer \overloading.
+ template<typename t>
+ CImg<T>& assign(const t *const values, const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c, const bool is_shared) {
+ if (is_shared)
+ throw CImgArgumentException(_cimg_instance
+ "assign(): Invalid assignment request of shared instance from (%s*) buffer"
+ "(pixel types are different).",
+ cimg_instance,
+ CImg<t>::pixel_type());
+ return assign(values,size_x,size_y,size_z,size_c);
+ }
+
+ //! Construct image with specified size and initialize pixel values from a memory buffer \overloading.
+ CImg<T>& assign(const T *const values, const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c, const bool is_shared) {
+ const size_t siz = (size_t)size_x*size_y*size_z*size_c;
+ if (!values || !siz) return assign();
+ if (!is_shared) { if (_is_shared) assign(); assign(values,size_x,size_y,size_z,size_c); }
+ else {
+ if (!_is_shared) {
+ if (values + siz<_data || values>=_data + size()) assign();
+ else cimg::warn(_cimg_instance
+ "assign(): Shared image instance has overlapping memory.",
+ cimg_instance);
+ }
+ _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true;
+ _data = const_cast<T*>(values);
+ }
+ return *this;
+ }
+
+ //! Construct image from reading an image file \inplace.
+ /**
+ In-place version of the constructor CImg(const char*).
+ **/
+ CImg<T>& assign(const char *const filename) {
+ return load(filename);
+ }
+
+ //! Construct image copy \inplace.
+ /**
+ In-place version of the constructor CImg(const CImg<t>&).
+ **/
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img) {
+ return assign(img._data,img._width,img._height,img._depth,img._spectrum);
+ }
+
+ //! In-place version of the advanced copy constructor.
+ /**
+ In-place version of the constructor CImg(const CImg<t>&,bool).
+ **/
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img, const bool is_shared) {
+ return assign(img._data,img._width,img._height,img._depth,img._spectrum,is_shared);
+ }
+
+ //! Construct image with dimensions borrowed from another image \inplace.
+ /**
+ In-place version of the constructor CImg(const CImg<t>&,const char*).
+ **/
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img, const char *const dimensions) {
+ if (!dimensions || !*dimensions) return assign(img._width,img._height,img._depth,img._spectrum);
+ unsigned int siz[4] = { 0,1,1,1 }, k = 0;
+ CImg<charT> item(256);
+ for (const char *s = dimensions; *s && k<4; ++k) {
+ if (cimg_sscanf(s,"%255[^0-9%xyzvwhdcXYZVWHDC]",item._data)>0) s+=std::strlen(item);
+ if (*s) {
+ unsigned int val = 0; char sep = 0;
+ if (cimg_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::lowercase(*s)) {
+ case 'x' : case 'w' : siz[k] = img._width; ++s; break;
+ case 'y' : case 'h' : siz[k] = img._height; ++s; break;
+ case 'z' : case 'd' : siz[k] = img._depth; ++s; break;
+ case 'c' : case 's' : siz[k] = img._spectrum; ++s; break;
+ default :
+ throw CImgArgumentException(_cimg_instance
+ "assign(): Invalid character '%c' detected in specified dimension string '%s'.",
+ cimg_instance,
+ *s,dimensions);
+ }
+ }
+ }
+ return assign(siz[0],siz[1],siz[2],siz[3]);
+ }
+
+ //! Construct image with dimensions borrowed from another image and initialize pixel values \inplace.
+ /**
+ In-place version of the constructor CImg(const CImg<t>&,const char*,T).
+ **/
+ template<typename t>
+ CImg<T>& assign(const CImg<t>& img, const char *const dimensions, const T& value) {
+ return assign(img,dimensions).fill(value);
+ }
+
+ //! Construct image from a display window \inplace.
+ /**
+ In-place version of the constructor CImg(const CImgDisplay&).
+ **/
+ CImg<T>& assign(const CImgDisplay &disp) {
+ disp.snapshot(*this);
+ return *this;
+ }
+
+ //! Construct empty image \inplace.
+ /**
+ Equivalent to assign().
+ \note
+ - It has been defined for compatibility with STL naming conventions.
+ **/
+ CImg<T>& clear() {
+ return assign();
+ }
+
+ //! Transfer content of an image instance into another one.
+ /**
+ Transfer the dimensions and the pixel buffer content of an image instance into another one,
+ and replace instance by an empty image. It avoids the copy of the pixel buffer
+ when possible.
+ \param img Destination image.
+ \note
+ - Pixel types \c T and \c t of source and destination images can be different, though the process is
+ designed to be instantaneous when \c T and \c t are the same.
+ \par Example
+ \code
+ CImg<float> src(256,256,1,3,0), // Construct a 256x256x1x3 (color) image filled with value '0'.
+ dest(16,16); // Construct a 16x16x1x1 (scalar) image.
+ src.move_to(dest); // Now, 'src' is empty and 'dest' is the 256x256x1x3 image.
+ \endcode
+ **/
+ template<typename t>
+ CImg<t>& move_to(CImg<t>& img) {
+ img.assign(*this);
+ assign();
+ return img;
+ }
+
+ //! Transfer content of an image instance into another one \specialization.
+ CImg<T>& move_to(CImg<T>& img) {
+ if (_is_shared || img._is_shared) img.assign(*this);
+ else swap(img);
+ assign();
+ return img;
+ }
+
+ //! Transfer content of an image instance into a new image in an image list.
+ /**
+ Transfer the dimensions and the pixel buffer content of an image instance
+ into a newly inserted image at position \c pos in specified \c CImgList<t> instance.
+ \param list Destination list.
+ \param pos Position of the newly inserted image in the list.
+ \note
+ - When optional parameter \c pos is ommited, the image instance is transfered as a new
+ image at the end of the specified \c list.
+ - It is convenient to sequentially insert new images into image lists, with no
+ additional copies of memory buffer.
+ \par Example
+ \code
+ CImgList<float> list; // Construct an empty image list.
+ CImg<float> img("reference.jpg"); // Read image from filename.
+ img.move_to(list); // Transfer image content as a new item in the list (no buffer copy).
+ \endcode
+ **/
+ template<typename t>
+ CImgList<t>& move_to(CImgList<t>& list, const unsigned int pos=~0U) {
+ const unsigned int npos = pos>list._width?list._width:pos;
+ move_to(list.insert(1,npos)[npos]);
+ return list;
+ }
+
+ //! Swap fields of two image instances.
+ /**
+ \param img Image to swap fields with.
+ \note
+ - It can be used to interchange the content of two images in a very fast way. Can be convenient when dealing
+ with algorithms requiring two swapping buffers.
+ \par Example
+ \code
+ CImg<float> img1("lena.jpg"),
+ img2("milla.jpg");
+ img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena'.
+ \endcode
+ **/
+ CImg<T>& swap(CImg<T>& img) {
+ cimg::swap(_width,img._width,_height,img._height,_depth,img._depth,_spectrum,img._spectrum);
+ cimg::swap(_data,img._data);
+ cimg::swap(_is_shared,img._is_shared);
+ return img;
+ }
+
+ //! Return a reference to an empty image.
+ /**
+ \note
+ This function is useful mainly to declare optional parameters having type \c CImg<T> in functions prototypes,
+ e.g.
+ \code
+ void f(const int x=0, const int y=0, const CImg<float>& img=CImg<float>::empty());
+ \endcode
+ **/
+ static CImg<T>& empty() {
+ static CImg<T> _empty;
+ return _empty.assign();
+ }
+
+ //! Return a reference to an empty image \const.
+ static const CImg<T>& const_empty() {
+ static const CImg<T> _empty;
+ return _empty;
+ }
+
+ //@}
+ //------------------------------------------
+ //
+ //! \name Overloaded Operators
+ //@{
+ //------------------------------------------
+
+ //! Access to a pixel value.
+ /**
+ Return a reference to a located pixel value of the image instance,
+ being possibly \e const, whether the image instance is \e const or not.
+ This is the standard method to get/set pixel values in \c CImg<T> images.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note
+ - Range of pixel coordinates start from <tt>(0,0,0,0)</tt> to
+ <tt>(width() - 1,height() - 1,depth() - 1,spectrum() - 1)</tt>.
+ - Due to the particular arrangement of the pixel buffers defined in %CImg, you can omit one coordinate if the
+ corresponding dimension is equal to \c 1.
+ For instance, pixels of a 2d image (depth() equal to \c 1) can be accessed by <tt>img(x,y,c)</tt> instead of
+ <tt>img(x,y,0,c)</tt>.
+ \warning
+ - There is \e no boundary checking done in this operator, to make it as fast as possible.
+ You \e must take care of out-of-bounds access by yourself, if necessary.
+ For debuging purposes, you may want to define macro \c 'cimg_verbosity'>=3 to enable additional boundary
+ checking operations in this operator. In that case, warning messages will be printed on the error output
+ when accessing out-of-bounds pixels.
+ \par Example
+ \code
+ CImg<float> img(100,100,1,3,0); // Construct a 100x100x1x3 (color) image with pixels set to '0'.
+ const float
+ valR = img(10,10,0,0), // Read red value at coordinates (10,10).
+ valG = img(10,10,0,1), // Read green value at coordinates (10,10)
+ valB = img(10,10,2), // Read blue value at coordinates (10,10) (Z-coordinate can be omitted).
+ avg = (valR + valG + valB)/3; // Compute average pixel value.
+ img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the color pixel (10,10) by the average grey value.
+ \endcode
+ **/
+#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 ulongT off = (ulongT)offset(x,y,z,c);
+ if (!_data || off>=size()) {
+ cimg::warn(_cimg_instance
+ "operator(): Invalid pixel request, at coordinates (%d,%d,%d,%d) [offset=%u].",
+ cimg_instance,
+ (int)x,(int)y,(int)z,(int)c,off);
+ return *_data;
+ }
+ else return _data[off];
+ }
+
+ //! Access to a pixel value \const.
+ const T& operator()(const unsigned int x, const unsigned int y=0,
+ const unsigned int z=0, const unsigned int c=0) const {
+ return const_cast<CImg<T>*>(this)->operator()(x,y,z,c);
+ }
+
+ //! Access to a pixel value.
+ /**
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param wh Precomputed offset, must be equal to <tt>width()*\ref height()</tt>.
+ \param whd Precomputed offset, must be equal to <tt>width()*\ref height()*\ref depth()</tt>.
+ \note
+ - Similar to (but faster than) operator()().
+ It uses precomputed offsets to optimize memory access. You may use it to optimize
+ the reading/writing of several pixel values in the same image (e.g. in a loop).
+ **/
+ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+ const ulongT wh, const ulongT whd=0) {
+ cimg::unused(wh,whd);
+ return (*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value \const.
+ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+ const ulongT wh, const ulongT whd=0) const {
+ cimg::unused(wh,whd);
+ return (*this)(x,y,z,c);
+ }
+#else
+ T& operator()(const unsigned int x) {
+ return _data[x];
+ }
+
+ const T& operator()(const unsigned int x) const {
+ return _data[x];
+ }
+
+ T& operator()(const unsigned int x, const unsigned int y) {
+ return _data[x + y*_width];
+ }
+
+ const T& operator()(const unsigned int x, const unsigned int y) const {
+ return _data[x + y*_width];
+ }
+
+ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) {
+ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height];
+ }
+
+ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z) const {
+ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height];
+ }
+
+ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) {
+ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth];
+ }
+
+ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c) const {
+ return _data[x + y*(ulongT)_width + z*(ulongT)_width*_height + c*(ulongT)_width*_height*_depth];
+ }
+
+ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int,
+ const ulongT wh) {
+ return _data[x + y*_width + z*wh];
+ }
+
+ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int,
+ const ulongT wh) const {
+ return _data[x + y*_width + z*wh];
+ }
+
+ T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+ const ulongT wh, const ulongT whd) {
+ return _data[x + y*_width + z*wh + c*whd];
+ }
+
+ const T& operator()(const unsigned int x, const unsigned int y, const unsigned int z, const unsigned int c,
+ const ulongT wh, const ulongT whd) const {
+ return _data[x + y*_width + z*wh + c*whd];
+ }
+#endif
+
+ //! Implicitely cast an image into a \c T*.
+ /**
+ Implicitely cast a \c CImg<T> instance into a \c T* or \c const \c T* pointer, whether the image instance
+ is \e const or not. The returned pointer points on the first value of the image pixel buffer.
+ \note
+ - It simply returns the pointer data() to the pixel buffer.
+ - This implicit conversion is convenient to test the empty state of images (data() being \c 0 in this case), e.g.
+ \code
+ CImg<float> img1(100,100), img2; // 'img1' is a 100x100 image, 'img2' is an empty image.
+ if (img1) { // Test succeeds, 'img1' is not an empty image.
+ if (!img2) { // Test succeeds, 'img2' is an empty image.
+ std::printf("'img1' is not empty, 'img2' is empty.");
+ }
+ }
+ \endcode
+ - It also allows to use brackets to access pixel values, without need for a \c CImg<T>::operator[](), e.g.
+ \code
+ CImg<float> img(100,100);
+ const float value = img[99]; // Access to value of the last pixel on the first row.
+ img[510] = 255; // Set pixel value at (10,5).
+ \endcode
+ **/
+ operator T*() {
+ return _data;
+ }
+
+ //! Implicitely cast an image into a \c T* \const.
+ operator const T*() const {
+ return _data;
+ }
+
+ //! Assign a value to all image pixels.
+ /**
+ Assign specified \c value to each pixel value of the image instance.
+ \param value Value that will be assigned to image pixels.
+ \note
+ - The image size is never modified.
+ - The \c value may be casted to pixel type \c T if necessary.
+ \par Example
+ \code
+ CImg<char> img(100,100); // Declare image (with garbage values).
+ img = 0; // Set all pixel values to '0'.
+ img = 1.2; // Set all pixel values to '1' (cast of '1.2' as a 'char').
+ \endcode
+ **/
+ CImg<T>& operator=(const T& value) {
+ return fill(value);
+ }
+
+ //! Assign pixels values from a specified expression.
+ /**
+ Initialize all pixel values from the specified string \c expression.
+ \param expression Value string describing the way pixel values are set.
+ \note
+ - String parameter \c expression may describe different things:
+ - If \c expression is a list of values (as in \c "1,2,3,8,3,2"), or a formula (as in \c "(x*y)%255"),
+ the pixel values are set from specified \c expression and the image size is not modified.
+ - If \c expression is a filename (as in \c "reference.jpg"), the corresponding image file is loaded and
+ replace the image instance. The image size is modified if necessary.
+ \par Example
+ \code
+ CImg<float> img1(100,100), img2(img1), img3(img1); // Declare 3 scalar images 100x100 with unitialized values.
+ img1 = "0,50,100,150,200,250,200,150,100,50"; // Set pixel values of 'img1' from a value sequence.
+ img2 = "10*((x*y)%25)"; // Set pixel values of 'img2' from a formula.
+ img3 = "reference.jpg"; // Set pixel values of 'img3' from a file (image size is modified).
+ (img1,img2,img3).display();
+ \endcode
+ \image html ref_operator_eq.jpg
+ **/
+ CImg<T>& operator=(const char *const expression) {
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ try {
+ _fill(expression,true,true,0,0,"operator=",0);
+ } catch (CImgException&) {
+ cimg::exception_mode(omode);
+ load(expression);
+ }
+ cimg::exception_mode(omode);
+ return *this;
+ }
+
+ //! Copy an image into the current image instance.
+ /**
+ Similar to the in-place copy constructor assign(const CImg<t>&).
+ **/
+ template<typename t>
+ CImg<T>& operator=(const CImg<t>& img) {
+ return assign(img);
+ }
+
+ //! Copy an image into the current image instance \specialization.
+ CImg<T>& operator=(const CImg<T>& img) {
+ return assign(img);
+ }
+
+ //! Copy the content of a display window to the current image instance.
+ /**
+ Similar to assign(const CImgDisplay&).
+ **/
+ CImg<T>& operator=(const CImgDisplay& disp) {
+ disp.snapshot(*this);
+ return *this;
+ }
+
+ //! In-place addition operator.
+ /**
+ Add specified \c value to all pixels of an image instance.
+ \param value Value to add.
+ \note
+ - Resulting pixel values are casted to fit the pixel type \c T.
+ For instance, adding \c 0.2 to a \c CImg<char> is possible but does nothing indeed.
+ - Overflow values are treated as with standard C++ numeric types. For instance,
+ \code
+ CImg<unsigned char> img(100,100,1,1,255); // Construct a 100x100 image with pixel values '255'.
+ img+=1; // Add '1' to each pixels -> Overflow.
+ // here all pixels of image 'img' are equal to '0'.
+ \endcode
+ - To prevent value overflow, you may want to consider pixel type \c T as \c float or \c double,
+ and use cut() after addition.
+ \par Example
+ \code
+ CImg<unsigned char> img1("reference.jpg"); // Load a 8-bits RGB image (values in [0,255]).
+ CImg<float> img2(img1); // Construct a float-valued copy of 'img1'.
+ img2+=100; // Add '100' to pixel values -> goes out of [0,255] but no problems with floats.
+ img2.cut(0,255); // Cut values in [0,255] to fit the 'unsigned char' constraint.
+ img1 = img2; // Rewrite safe result in 'unsigned char' version 'img1'.
+ const CImg<unsigned char> img3 = (img1 + 100).cut(0,255); // Do the same in a more simple and elegant way.
+ (img1,img2,img3).display();
+ \endcode
+ \image html ref_operator_plus.jpg
+ **/
+ template<typename t>
+ CImg<T>& operator+=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd + value);
+ return *this;
+ }
+
+ //! In-place addition operator.
+ /**
+ Add values to image pixels, according to the specified string \c expression.
+ \param expression Value string describing the way pixel values are added.
+ \note
+ - Similar to operator=(const char*), except that it adds values to the pixels of the current image instance,
+ instead of assigning them.
+ **/
+ CImg<T>& operator+=(const char *const expression) {
+ return *this+=(+*this)._fill(expression,true,true,0,0,"operator+=",this);
+ }
+
+ //! In-place addition operator.
+ /**
+ Add values to image pixels, according to the values of the input image \c img.
+ \param img Input image to add.
+ \note
+ - The size of the image instance is never modified.
+ - It is not mandatory that input image \c img has the same size as the image instance.
+ If less values are available in \c img, then the values are added periodically. For instance, adding one
+ WxH scalar image (spectrum() equal to \c 1) to one WxH color image (spectrum() equal to \c 3)
+ means each color channel will be incremented with the same values at the same locations.
+ \par Example
+ \code
+ CImg<float> img1("reference.jpg"); // Load a RGB color image (img1.spectrum()==3)
+ // Construct a scalar shading (img2.spectrum()==1).
+ const CImg<float> img2(img1.width(),img.height(),1,1,"255*(x/w)^2");
+ img1+=img2; // Add shading to each channel of 'img1'.
+ img1.cut(0,255); // Prevent [0,255] overflow.
+ (img2,img1).display();
+ \endcode
+ \image html ref_operator_plus1.jpg
+ **/
+ template<typename t>
+ CImg<T>& operator+=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)(*ptrd + *(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd + *(ptrs++));
+ }
+ return *this;
+ }
+
+ //! In-place increment operator (prefix).
+ /**
+ Add \c 1 to all image pixels, and return a reference to the current incremented image instance.
+ \note
+ - Writing \c ++img is equivalent to \c img+=1.
+ **/
+ CImg<T>& operator++() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288))
+ cimg_rof(*this,ptrd,T) ++*ptrd;
+ return *this;
+ }
+
+ //! In-place increment operator (postfix).
+ /**
+ Add \c 1 to all image pixels, and return a new copy of the initial (pre-incremented) image instance.
+ \note
+ - Use the prefixed version operator++() if you don't need a copy of the initial
+ (pre-incremented) image instance, since a useless image copy may be expensive in terms of memory usage.
+ **/
+ CImg<T> operator++(int) {
+ const CImg<T> copy(*this,false);
+ ++*this;
+ return copy;
+ }
+
+ //! Return a non-shared copy of the image instance.
+ /**
+ \note
+ - Use this operator to ensure you get a non-shared copy of an image instance with same pixel type \c T.
+ Indeed, the usual copy constructor CImg<T>(const CImg<T>&) returns a shared copy of a shared input image,
+ and it may be not desirable to work on a regular copy (e.g. for a resize operation) if you have no
+ information about the shared state of the input image.
+ - Writing \c (+img) is equivalent to \c CImg<T>(img,false).
+ **/
+ CImg<T> operator+() const {
+ return CImg<T>(*this,false);
+ }
+
+ //! Addition operator.
+ /**
+ Similar to operator+=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator+(const t value) const {
+ return CImg<_cimg_Tt>(*this,false)+=value;
+ }
+
+ //! Addition operator.
+ /**
+ Similar to operator+=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ CImg<Tfloat> operator+(const char *const expression) const {
+ return CImg<Tfloat>(*this,false)+=expression;
+ }
+
+ //! Addition operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator+(const CImg<t>& img) const {
+ return CImg<_cimg_Tt>(*this,false)+=img;
+ }
+
+ //! In-place substraction operator.
+ /**
+ Similar to operator+=(const t), except that it performs a substraction instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator-=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd - value);
+ return *this;
+ }
+
+ //! In-place substraction operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a substraction instead of an addition.
+ **/
+ CImg<T>& operator-=(const char *const expression) {
+ return *this-=(+*this)._fill(expression,true,true,0,0,"operator-=",this);
+ }
+
+ //! In-place substraction operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it performs a substraction instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator-=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)(*ptrd - *(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd - *(ptrs++));
+ }
+ return *this;
+ }
+
+ //! In-place decrement operator (prefix).
+ /**
+ Similar to operator++(), except that it performs a decrement instead of an increment.
+ **/
+ CImg<T>& operator--() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288))
+ cimg_rof(*this,ptrd,T) *ptrd = *ptrd - (T)1;
+ return *this;
+ }
+
+ //! In-place decrement operator (postfix).
+ /**
+ Similar to operator++(int), except that it performs a decrement instead of an increment.
+ **/
+ CImg<T> operator--(int) {
+ const CImg<T> copy(*this,false);
+ --*this;
+ return copy;
+ }
+
+ //! Replace each pixel by its opposite value.
+ /**
+ \note
+ - If the computed opposite values are out-of-range, they are treated as with standard C++ numeric types.
+ For instance, the \c unsigned \c char opposite of \c 1 is \c 255.
+ \par Example
+ \code
+ const CImg<unsigned char>
+ img1("reference.jpg"), // Load a RGB color image.
+ img2 = -img1; // Compute its opposite (in 'unsigned char').
+ (img1,img2).display();
+ \endcode
+ \image html ref_operator_minus.jpg
+ **/
+ CImg<T> operator-() const {
+ return CImg<T>(_width,_height,_depth,_spectrum,(T)0)-=*this;
+ }
+
+ //! Substraction operator.
+ /**
+ Similar to operator-=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator-(const t value) const {
+ return CImg<_cimg_Tt>(*this,false)-=value;
+ }
+
+ //! Substraction operator.
+ /**
+ Similar to operator-=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ CImg<Tfloat> operator-(const char *const expression) const {
+ return CImg<Tfloat>(*this,false)-=expression;
+ }
+
+ //! Substraction operator.
+ /**
+ Similar to operator-=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator-(const CImg<t>& img) const {
+ return CImg<_cimg_Tt>(*this,false)-=img;
+ }
+
+ //! In-place multiplication operator.
+ /**
+ Similar to operator+=(const t), except that it performs a multiplication instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator*=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=262144))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd * value);
+ return *this;
+ }
+
+ //! In-place multiplication operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a multiplication instead of an addition.
+ **/
+ CImg<T>& operator*=(const char *const expression) {
+ return mul((+*this)._fill(expression,true,true,0,0,"operator*=",this));
+ }
+
+ //! In-place multiplication operator.
+ /**
+ Replace the image instance by the matrix multiplication between the image instance and the specified matrix
+ \c img.
+ \param img Second operand of the matrix multiplication.
+ \note
+ - It does \e not compute a pointwise multiplication between two images. For this purpose, use
+ mul(const CImg<t>&) instead.
+ - The size of the image instance can be modified by this operator.
+ \par Example
+ \code
+ CImg<float> A(2,2,1,1, 1,2,3,4); // Construct 2x2 matrix A = [1,2;3,4].
+ const CImg<float> X(1,2,1,1, 1,2); // Construct 1x2 vector X = [1;2].
+ A*=X; // Assign matrix multiplication A*X to 'A'.
+ // 'A' is now a 1x2 vector whose values are [5;11].
+ \endcode
+ **/
+ template<typename t>
+ CImg<T>& operator*=(const CImg<t>& img) {
+ return ((*this)*img).move_to(*this);
+ }
+
+ //! Multiplication operator.
+ /**
+ Similar to operator*=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator*(const t value) const {
+ return CImg<_cimg_Tt>(*this,false)*=value;
+ }
+
+ //! Multiplication operator.
+ /**
+ Similar to operator*=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ CImg<Tfloat> operator*(const char *const expression) const {
+ return CImg<Tfloat>(*this,false)*=expression;
+ }
+
+ //! Multiplication operator.
+ /**
+ Similar to operator*=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator*(const CImg<t>& img) const {
+ if (_width!=img._height || _depth!=1 || _spectrum!=1)
+ throw CImgArgumentException(_cimg_instance
+ "operator*(): Invalid multiplication of instance by specified "
+ "matrix (%u,%u,%u,%u,%p)",
+ cimg_instance,
+ img._width,img._height,img._depth,img._spectrum,img._data);
+ CImg<_cimg_Tt> res(img._width,_height);
+#ifdef cimg_use_openmp
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(size()>1024 && img.size()>1024))
+ cimg_forXY(res,i,j) {
+ _cimg_Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); res(i,j) = (_cimg_Tt)value;
+ }
+#else
+ _cimg_Tt *ptrd = res._data;
+ cimg_forXY(res,i,j) {
+ _cimg_Ttdouble value = 0; cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); *(ptrd++) = (_cimg_Tt)value;
+ }
+#endif
+ return res;
+ }
+
+ //! In-place division operator.
+ /**
+ Similar to operator+=(const t), except that it performs a division instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator/=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)(*ptrd / value);
+ return *this;
+ }
+
+ //! In-place division operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a division instead of an addition.
+ **/
+ CImg<T>& operator/=(const char *const expression) {
+ return div((+*this)._fill(expression,true,true,0,0,"operator/=",this));
+ }
+
+ //! In-place division operator.
+ /**
+ Replace the image instance by the (right) matrix division between the image instance and the specified
+ matrix \c img.
+ \param img Second operand of the matrix division.
+ \note
+ - It does \e not compute a pointwise division between two images. For this purpose, use
+ div(const CImg<t>&) instead.
+ - It returns the matrix operation \c A*inverse(img).
+ - The size of the image instance can be modified by this operator.
+ **/
+ template<typename t>
+ CImg<T>& operator/=(const CImg<t>& img) {
+ return (*this*img.get_invert()).move_to(*this);
+ }
+
+ //! Division operator.
+ /**
+ Similar to operator/=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator/(const t value) const {
+ return CImg<_cimg_Tt>(*this,false)/=value;
+ }
+
+ //! Division operator.
+ /**
+ Similar to operator/=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ CImg<Tfloat> operator/(const char *const expression) const {
+ return CImg<Tfloat>(*this,false)/=expression;
+ }
+
+ //! Division operator.
+ /**
+ Similar to operator/=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator/(const CImg<t>& img) const {
+ return (*this)*img.get_invert();
+ }
+
+ //! In-place modulo operator.
+ /**
+ Similar to operator+=(const t), except that it performs a modulo operation instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator%=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=16384))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::mod(*ptrd,(T)value);
+ return *this;
+ }
+
+ //! In-place modulo operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a modulo operation instead of an addition.
+ **/
+ CImg<T>& operator%=(const char *const expression) {
+ return *this%=(+*this)._fill(expression,true,true,0,0,"operator%=",this);
+ }
+
+ //! In-place modulo operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it performs a modulo operation instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator%=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = cimg::mod(*ptrd,(T)*(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = cimg::mod(*ptrd,(T)*(ptrs++));
+ }
+ return *this;
+ }
+
+ //! Modulo operator.
+ /**
+ Similar to operator%=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator%(const t value) const {
+ return CImg<_cimg_Tt>(*this,false)%=value;
+ }
+
+ //! Modulo operator.
+ /**
+ Similar to operator%=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ CImg<Tfloat> operator%(const char *const expression) const {
+ return CImg<Tfloat>(*this,false)%=expression;
+ }
+
+ //! Modulo operator.
+ /**
+ Similar to operator%=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image may be a superset of the initial pixel type \c T, if necessary.
+ **/
+ template<typename t>
+ CImg<_cimg_Tt> operator%(const CImg<t>& img) const {
+ return CImg<_cimg_Tt>(*this,false)%=img;
+ }
+
+ //! In-place bitwise AND operator.
+ /**
+ Similar to operator+=(const t), except that it performs a bitwise AND operation instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator&=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)((ulongT)*ptrd & (ulongT)value);
+ return *this;
+ }
+
+ //! In-place bitwise AND operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition.
+ **/
+ CImg<T>& operator&=(const char *const expression) {
+ return *this&=(+*this)._fill(expression,true,true,0,0,"operator&=",this);
+ }
+
+ //! In-place bitwise AND operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it performs a bitwise AND operation instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator&=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)((ulongT)*ptrd & (ulongT)*(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((ulongT)*ptrd & (ulongT)*(ptrs++));
+ }
+ return *this;
+ }
+
+ //! Bitwise AND operator.
+ /**
+ Similar to operator&=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator&(const t value) const {
+ return (+*this)&=value;
+ }
+
+ //! Bitwise AND operator.
+ /**
+ Similar to operator&=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ CImg<T> operator&(const char *const expression) const {
+ return (+*this)&=expression;
+ }
+
+ //! Bitwise AND operator.
+ /**
+ Similar to operator&=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator&(const CImg<t>& img) const {
+ return (+*this)&=img;
+ }
+
+ //! In-place bitwise OR operator.
+ /**
+ Similar to operator+=(const t), except that it performs a bitwise OR operation instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator|=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)((ulongT)*ptrd | (ulongT)value);
+ return *this;
+ }
+
+ //! In-place bitwise OR operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition.
+ **/
+ CImg<T>& operator|=(const char *const expression) {
+ return *this|=(+*this)._fill(expression,true,true,0,0,"operator|=",this);
+ }
+
+ //! In-place bitwise OR operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it performs a bitwise OR operation instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator|=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)((ulongT)*ptrd | (ulongT)*(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((ulongT)*ptrd | (ulongT)*(ptrs++));
+ }
+ return *this;
+ }
+
+ //! Bitwise OR operator.
+ /**
+ Similar to operator|=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator|(const t value) const {
+ return (+*this)|=value;
+ }
+
+ //! Bitwise OR operator.
+ /**
+ Similar to operator|=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ CImg<T> operator|(const char *const expression) const {
+ return (+*this)|=expression;
+ }
+
+ //! Bitwise OR operator.
+ /**
+ Similar to operator|=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator|(const CImg<t>& img) const {
+ return (+*this)|=img;
+ }
+
+ //! In-place bitwise XOR operator.
+ /**
+ Similar to operator+=(const t), except that it performs a bitwise XOR operation instead of an addition.
+ \warning
+ - It does \e not compute the \e power of pixel values. For this purpose, use pow(const t) instead.
+ **/
+ template<typename t>
+ CImg<T>& operator^=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)((ulongT)*ptrd ^ (ulongT)value);
+ return *this;
+ }
+
+ //! In-place bitwise XOR operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a bitwise XOR operation instead of an addition.
+ \warning
+ - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead.
+ **/
+ CImg<T>& operator^=(const char *const expression) {
+ return *this^=(+*this)._fill(expression,true,true,0,0,"operator^=",this);
+ }
+
+ //! In-place bitwise XOR operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it performs a bitwise XOR operation instead of an addition.
+ \warning
+ - It does \e not compute the \e power of pixel values. For this purpose, use pow(const CImg<t>&) instead.
+ **/
+ template<typename t>
+ CImg<T>& operator^=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)((ulongT)*ptrd ^ (ulongT)*(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((ulongT)*ptrd ^ (ulongT)*(ptrs++));
+ }
+ return *this;
+ }
+
+ //! Bitwise XOR operator.
+ /**
+ Similar to operator^=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator^(const t value) const {
+ return (+*this)^=value;
+ }
+
+ //! Bitwise XOR operator.
+ /**
+ Similar to operator^=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ CImg<T> operator^(const char *const expression) const {
+ return (+*this)^=expression;
+ }
+
+ //! Bitwise XOR operator.
+ /**
+ Similar to operator^=(const CImg<t>&), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator^(const CImg<t>& img) const {
+ return (+*this)^=img;
+ }
+
+ //! In-place bitwise left shift operator.
+ /**
+ Similar to operator+=(const t), except that it performs a bitwise left shift instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator<<=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)(((longT)*ptrd) << (int)value);
+ return *this;
+ }
+
+ //! In-place bitwise left shift operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition.
+ **/
+ CImg<T>& operator<<=(const char *const expression) {
+ return *this<<=(+*this)._fill(expression,true,true,0,0,"operator<<=",this);
+ }
+
+ //! In-place bitwise left shift operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it performs a bitwise left shift instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator<<=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)((longT)*ptrd << (int)*(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((longT)*ptrd << (int)*(ptrs++));
+ }
+ return *this;
+ }
+
+ //! Bitwise left shift operator.
+ /**
+ Similar to operator<<=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator<<(const t value) const {
+ return (+*this)<<=value;
+ }
+
+ //! Bitwise left shift operator.
+ /**
+ Similar to operator<<=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ CImg<T> operator<<(const char *const expression) const {
+ return (+*this)<<=expression;
+ }
+
+ //! Bitwise left shift operator.
+ /**
+ Similar to operator<<=(const CImg<t>&), except that it returns a new image instance instead of
+ operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator<<(const CImg<t>& img) const {
+ return (+*this)<<=img;
+ }
+
+ //! In-place bitwise right shift operator.
+ /**
+ Similar to operator+=(const t), except that it performs a bitwise right shift instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator>>=(const t value) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)(((longT)*ptrd) >> (int)value);
+ return *this;
+ }
+
+ //! In-place bitwise right shift operator.
+ /**
+ Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition.
+ **/
+ CImg<T>& operator>>=(const char *const expression) {
+ return *this>>=(+*this)._fill(expression,true,true,0,0,"operator>>=",this);
+ }
+
+ //! In-place bitwise right shift operator.
+ /**
+ Similar to operator+=(const CImg<t>&), except that it performs a bitwise right shift instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& operator>>=(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)((longT)*ptrd >> (int)*(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)((longT)*ptrd >> (int)*(ptrs++));
+ }
+ return *this;
+ }
+
+ //! Bitwise right shift operator.
+ /**
+ Similar to operator>>=(const t), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator>>(const t value) const {
+ return (+*this)>>=value;
+ }
+
+ //! Bitwise right shift operator.
+ /**
+ Similar to operator>>=(const char*), except that it returns a new image instance instead of operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ CImg<T> operator>>(const char *const expression) const {
+ return (+*this)>>=expression;
+ }
+
+ //! Bitwise right shift operator.
+ /**
+ Similar to operator>>=(const CImg<t>&), except that it returns a new image instance instead of
+ operating in-place.
+ The pixel type of the returned image is \c T.
+ **/
+ template<typename t>
+ CImg<T> operator>>(const CImg<t>& img) const {
+ return (+*this)>>=img;
+ }
+
+ //! Bitwise inversion operator.
+ /**
+ Similar to operator-(), except that it compute the bitwise inverse instead of the opposite value.
+ **/
+ CImg<T> operator~() const {
+ CImg<T> res(_width,_height,_depth,_spectrum);
+ const T *ptrs = _data;
+ cimg_for(res,ptrd,T) { const ulongT value = (ulongT)*(ptrs++); *ptrd = (T)~value; }
+ return res;
+ }
+
+ //! Test if all pixels of an image have the same value.
+ /**
+ Return \c true is all pixels of the image instance are equal to the specified \c value.
+ \param value Reference value to compare with.
+ **/
+ template<typename t>
+ bool operator==(const t value) const {
+ if (is_empty()) return false;
+ typedef _cimg_Tt Tt;
+ bool is_equal = true;
+ for (T *ptrd = _data + size(); is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)value)) {}
+ return is_equal;
+ }
+
+ //! Test if all pixel values of an image follow a specified expression.
+ /**
+ Return \c true is all pixels of the image instance are equal to the specified \c expression.
+ \param expression Value string describing the way pixel values are compared.
+ **/
+ bool operator==(const char *const expression) const {
+ return *this==(+*this)._fill(expression,true,true,0,0,"operator==",this);
+ }
+
+ //! Test if two images have the same size and values.
+ /**
+ Return \c true if the image instance and the input image \c img have the same dimensions and pixel values,
+ and \c false otherwise.
+ \param img Input image to compare with.
+ \note
+ - The pixel buffer pointers data() of the two compared images do not have to be the same for operator==()
+ to return \c true.
+ Only the dimensions and the pixel values matter. Thus, the comparison can be \c true even for different
+ pixel types \c T and \c t.
+ \par Example
+ \code
+ const CImg<float> img1(1,3,1,1, 0,1,2); // Construct a 1x3 vector [0;1;2] (with 'float' pixel values).
+ const CImg<char> img2(1,3,1,1, 0,1,2); // Construct a 1x3 vector [0;1;2] (with 'char' pixel values).
+ if (img1==img2) { // Test succeeds, image dimensions and values are the same.
+ std::printf("'img1' and 'img2' have same dimensions and values.");
+ }
+ \endcode
+ **/
+ template<typename t>
+ bool operator==(const CImg<t>& img) const {
+ typedef _cimg_Tt Tt;
+ const ulongT siz = size();
+ bool is_equal = true;
+ if (siz!=img.size()) return false;
+ t *ptrs = img._data + siz;
+ for (T *ptrd = _data + siz; is_equal && ptrd>_data; is_equal = ((Tt)*(--ptrd)==(Tt)*(--ptrs))) {}
+ return is_equal;
+ }
+
+ //! Test if pixels of an image are all different from a value.
+ /**
+ Return \c true is all pixels of the image instance are different than the specified \c value.
+ \param value Reference value to compare with.
+ **/
+ template<typename t>
+ bool operator!=(const t value) const {
+ return !((*this)==value);
+ }
+
+ //! Test if all pixel values of an image are different from a specified expression.
+ /**
+ Return \c true is all pixels of the image instance are different to the specified \c expression.
+ \param expression Value string describing the way pixel values are compared.
+ **/
+ bool operator!=(const char *const expression) const {
+ return !((*this)==expression);
+ }
+
+ //! Test if two images have different sizes or values.
+ /**
+ Return \c true if the image instance and the input image \c img have different dimensions or pixel values,
+ and \c false otherwise.
+ \param img Input image to compare with.
+ \note
+ - Writing \c img1!=img2 is equivalent to \c !(img1==img2).
+ **/
+ template<typename t>
+ bool operator!=(const CImg<t>& img) const {
+ return !((*this)==img);
+ }
+
+ //! Construct an image list from two images.
+ /**
+ Return a new list of image (\c CImgList instance) containing exactly two elements:
+ - A copy of the image instance, at position [\c 0].
+ - A copy of the specified image \c img, at position [\c 1].
+
+ \param img Input image that will be the second image of the resulting list.
+ \note
+ - The family of operator,() is convenient to easily create list of images, but it is also \e quite \e slow
+ in practice (see warning below).
+ - Constructed lists contain no shared images. If image instance or input image \c img are shared, they are
+ inserted as new non-shared copies in the resulting list.
+ - The pixel type of the returned list may be a superset of the initial pixel type \c T, if necessary.
+ \warning
+ - Pipelining operator,() \c N times will perform \c N copies of the entire content of a (growing) image list.
+ This may become very expensive in terms of speed and used memory. You should avoid using this technique to
+ build a new CImgList instance from several images, if you are seeking for performance.
+ Fast insertions of images in an image list are possible with
+ CImgList<T>::insert(const CImg<t>&,unsigned int,bool) or move_to(CImgList<t>&,unsigned int).
+ \par Example
+ \code
+ const CImg<float>
+ img1("reference.jpg"),
+ img2 = img1.get_mirror('x'),
+ img3 = img2.get_blur(5);
+ const CImgList<float> list = (img1,img2); // Create list of two elements from 'img1' and 'img2'.
+ (list,img3).display(); // Display image list containing copies of 'img1','img2' and 'img3'.
+ \endcode
+ \image html ref_operator_comma.jpg
+ **/
+ template<typename t>
+ CImgList<_cimg_Tt> operator,(const CImg<t>& img) const {
+ return CImgList<_cimg_Tt>(*this,img);
+ }
+
+ //! Construct an image list from image instance and an input image list.
+ /**
+ Return a new list of images (\c CImgList instance) containing exactly \c list.size() \c + \c 1 elements:
+ - A copy of the image instance, at position [\c 0].
+ - A copy of the specified image list \c list, from positions [\c 1] to [\c list.size()].
+
+ \param list Input image list that will be appended to the image instance.
+ \note
+ - Similar to operator,(const CImg<t>&) const, except that it takes an image list as an argument.
+ **/
+ template<typename t>
+ CImgList<_cimg_Tt> operator,(const CImgList<t>& list) const {
+ return CImgList<_cimg_Tt>(list,false).insert(*this,0);
+ }
+
+ //! Split image along specified axis.
+ /**
+ Return a new list of images (\c CImgList instance) containing the splitted components
+ of the instance image along the specified axis.
+ \param axis Splitting axis (can be '\c x','\c y','\c z' or '\c c')
+ \note
+ - Similar to get_split(char,int) const, with default second argument.
+ \par Example
+ \code
+ const CImg<unsigned char> img("reference.jpg"); // Load a RGB color image.
+ const CImgList<unsigned char> list = (img<'c'); // Get a list of its three R,G,B channels.
+ (img,list).display();
+ \endcode
+ \image html ref_operator_less.jpg
+ **/
+ CImgList<T> operator<(const char axis) const {
+ return get_split(axis);
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Instance Characteristics
+ //@{
+ //-------------------------------------
+
+ //! Return the type of image pixel values as a C string.
+ /**
+ Return a \c char* string containing the usual type name of the image pixel values
+ (i.e. a stringified version of the template parameter \c T).
+ \note
+ - The returned string may contain spaces (as in \c "unsigned char").
+ - If the pixel type \c T does not correspond to a registered type, the string <tt>"unknown"</tt> is returned.
+ **/
+ static const char* pixel_type() {
+ return cimg::type<T>::string();
+ }
+
+ //! Return the number of image columns.
+ /**
+ Return the image width, i.e. the image dimension along the X-axis.
+ \note
+ - The width() of an empty image is equal to \c 0.
+ - width() is typically equal to \c 1 when considering images as \e vectors for matrix calculations.
+ - width() returns an \c int, although the image width is internally stored as an \c unsigned \c int.
+ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+ \c unsigned \c int variables.
+ Access to the initial \c unsigned \c int variable is possible (though not recommended) by
+ <tt>(*this)._width</tt>.
+ **/
+ int width() const {
+ return (int)_width;
+ }
+
+ //! Return the number of image rows.
+ /**
+ Return the image height, i.e. the image dimension along the Y-axis.
+ \note
+ - The height() of an empty image is equal to \c 0.
+ - height() returns an \c int, although the image height is internally stored as an \c unsigned \c int.
+ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+ \c unsigned \c int variables.
+ Access to the initial \c unsigned \c int variable is possible (though not recommended) by
+ <tt>(*this)._height</tt>.
+ **/
+ int height() const {
+ return (int)_height;
+ }
+
+ //! Return the number of image slices.
+ /**
+ Return the image depth, i.e. the image dimension along the Z-axis.
+ \note
+ - The depth() of an empty image is equal to \c 0.
+ - depth() is typically equal to \c 1 when considering usual 2d images. When depth()\c > \c 1, the image
+ is said to be \e volumetric.
+ - depth() returns an \c int, although the image depth is internally stored as an \c unsigned \c int.
+ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+ \c unsigned \c int variables.
+ Access to the initial \c unsigned \c int variable is possible (though not recommended) by
+ <tt>(*this)._depth</tt>.
+ **/
+ int depth() const {
+ return (int)_depth;
+ }
+
+ //! Return the number of image channels.
+ /**
+ Return the number of image channels, i.e. the image dimension along the C-axis.
+ \note
+ - The spectrum() of an empty image is equal to \c 0.
+ - spectrum() is typically equal to \c 1 when considering scalar-valued images, to \c 3
+ for RGB-coded color images, and to \c 4 for RGBA-coded color images (with alpha-channel).
+ The number of channels of an image instance is not limited. The meaning of the pixel values is not linked
+ up to the number of channels (e.g. a 4-channel image may indifferently stands for a RGBA or CMYK color image).
+ - spectrum() returns an \c int, although the image spectrum is internally stored as an \c unsigned \c int.
+ Using an \c int is safer and prevents arithmetic traps possibly encountered when doing calculations involving
+ \c unsigned \c int variables.
+ Access to the initial \c unsigned \c int variable is possible (though not recommended) by
+ <tt>(*this)._spectrum</tt>.
+ **/
+ int spectrum() const {
+ return (int)_spectrum;
+ }
+
+ //! Return the total number of pixel values.
+ /**
+ Return <tt>width()*\ref height()*\ref depth()*\ref spectrum()</tt>,
+ i.e. the total number of values of type \c T in the pixel buffer of the image instance.
+ \note
+ - The size() of an empty image is equal to \c 0.
+ - The allocated memory size for a pixel buffer of a non-shared \c CImg<T> instance is equal to
+ <tt>size()*sizeof(T)</tt>.
+ \par Example
+ \code
+ const CImg<float> img(100,100,1,3); // Construct new 100x100 color image.
+ if (img.size()==30000) // Test succeeds.
+ std::printf("Pixel buffer uses %lu bytes",
+ img.size()*sizeof(float));
+ \endcode
+ **/
+ ulongT size() const {
+ return (ulongT)_width*_height*_depth*_spectrum;
+ }
+
+ //! Return a pointer to the first pixel value.
+ /**
+ Return a \c T*, or a \c const \c T* pointer to the first value in the pixel buffer of the image instance,
+ whether the instance is \c const or not.
+ \note
+ - The data() of an empty image is equal to \c 0 (null pointer).
+ - The allocated pixel buffer for the image instance starts from \c data()
+ and goes to <tt>data()+\ref size() - 1</tt> (included).
+ - To get the pointer to one particular location of the pixel buffer, use
+ data(unsigned int,unsigned int,unsigned int,unsigned int) instead.
+ **/
+ T* data() {
+ return _data;
+ }
+
+ //! Return a pointer to the first pixel value \const.
+ const T* data() const {
+ return _data;
+ }
+
+ //! Return a pointer to a located pixel value.
+ /**
+ Return a \c T*, or a \c const \c T* pointer to the value located at (\c x,\c y,\c z,\c c) in the pixel buffer
+ of the image instance,
+ whether the instance is \c const or not.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note
+ - Writing \c img.data(x,y,z,c) is equivalent to <tt>&(img(x,y,z,c))</tt>. Thus, this method has the same
+ properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int).
+ **/
+#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 ulongT off = (ulongT)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 + off;
+ }
+
+ //! Return a pointer to a located pixel value \const.
+ const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
+ return const_cast<CImg<T>*>(this)->data(x,y,z,c);
+ }
+#else
+ T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) {
+ return _data + x + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)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 + (ulongT)y*_width + (ulongT)z*_width*_height + (ulongT)c*_width*_height*_depth;
+ }
+#endif
+
+ //! Return the offset to a located pixel value, with respect to the beginning of the pixel buffer.
+ /**
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note
+ - Writing \c img.data(x,y,z,c) is equivalent to <tt>&(img(x,y,z,c)) - img.data()</tt>.
+ Thus, this method has the same properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int).
+ \par Example
+ \code
+ const CImg<float> img(100,100,1,3); // Define a 100x100 RGB-color image.
+ const long off = img.offset(10,10,0,2); // Get the offset of the blue value of the pixel located at (10,10).
+ const float val = img[off]; // Get the blue value of this pixel.
+ \endcode
+ **/
+ longT offset(const int x, const int y=0, const int z=0, const int c=0) const {
+ return x + (longT)y*_width + (longT)z*_width*_height + (longT)c*_width*_height*_depth;
+ }
+
+ //! Return a CImg<T>::iterator pointing to the first pixel value.
+ /**
+ \note
+ - Equivalent to data().
+ - It has been mainly defined for compatibility with STL naming conventions.
+ **/
+ iterator begin() {
+ return _data;
+ }
+
+ //! Return a CImg<T>::iterator pointing to the first value of the pixel buffer \const.
+ const_iterator begin() const {
+ return _data;
+ }
+
+ //! Return a CImg<T>::iterator pointing next to the last pixel value.
+ /**
+ \note
+ - Writing \c img.end() is equivalent to <tt>img.data() + img.size()</tt>.
+ - It has been mainly defined for compatibility with STL naming conventions.
+ \warning
+ - The returned iterator actually points to a value located \e outside the acceptable bounds of the pixel buffer.
+ Trying to read or write the content of the returned iterator will probably result in a crash.
+ Use it mainly as a strict upper bound for a CImg<T>::iterator.
+ \par Example
+ \code
+ CImg<float> img(100,100,1,3); // Define a 100x100 RGB color image.
+ // 'img.end()' used below as an upper bound for the iterator.
+ for (CImg<float>::iterator it = img.begin(); it<img.end(); ++it)
+ *it = 0;
+ \endcode
+ **/
+ iterator end() {
+ return _data + size();
+ }
+
+ //! Return a CImg<T>::iterator pointing next to the last pixel value \const.
+ const_iterator end() const {
+ return _data + size();
+ }
+
+ //! Return a reference to the first pixel value.
+ /**
+ \note
+ - Writing \c img.front() is equivalent to <tt>img[0]</tt>, or <tt>img(0,0,0,0)</tt>.
+ - It has been mainly defined for compatibility with STL naming conventions.
+ **/
+ T& front() {
+ return *_data;
+ }
+
+ //! Return a reference to the first pixel value \const.
+ const T& front() const {
+ return *_data;
+ }
+
+ //! Return a reference to the last pixel value.
+ /**
+ \note
+ - Writing \c img.end() is equivalent to <tt>img[img.size() - 1]</tt>, or
+ <tt>img(img.width() - 1,img.height() - 1,img.depth() - 1,img.spectrum() - 1)</tt>.
+ - It has been mainly defined for compatibility with STL naming conventions.
+ **/
+ T& back() {
+ return *(_data + size() - 1);
+ }
+
+ //! Return a reference to the last pixel value \const.
+ const T& back() const {
+ return *(_data + size() - 1);
+ }
+
+ //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions.
+ /**
+ Return a reference to the pixel value of the image instance located at a specified \c offset,
+ or to a specified default value in case of out-of-bounds access.
+ \param offset Offset to the desired pixel value.
+ \param out_value Default value returned if \c offset is outside image bounds.
+ \note
+ - Writing \c img.at(offset,out_value) is similar to <tt>img[offset]</tt>, except that if \c offset
+ is outside bounds (e.g. \c offset<0 or \c offset>=img.size()), a reference to a value \c out_value
+ is safely returned instead.
+ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+ you are \e not sure about the validity of the specified pixel offset.
+ **/
+ T& at(const int offset, const T& out_value) {
+ return (offset<0 || offset>=(int)size())?(cimg::temporary(out_value)=out_value):(*this)[offset];
+ }
+
+ //! Access to a pixel value at a specified offset, using Dirichlet boundary conditions \const.
+ T at(const int offset, const T& out_value) const {
+ return (offset<0 || offset>=(int)size())?out_value:(*this)[offset];
+ }
+
+ //! Access to a pixel value at a specified offset, using Neumann boundary conditions.
+ /**
+ Return a reference to the pixel value of the image instance located at a specified \c offset,
+ or to the nearest pixel location in the image instance in case of out-of-bounds access.
+ \param offset Offset to the desired pixel value.
+ \note
+ - Similar to at(int,const T), except that an out-of-bounds access returns the value of the
+ nearest pixel in the image instance, regarding the specified offset, i.e.
+ - If \c offset<0, then \c img[0] is returned.
+ - If \c offset>=img.size(), then \c img[img.size() - 1] is returned.
+ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+ you are \e not sure about the validity of the specified pixel offset.
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method \c _at(int).
+ **/
+ T& at(const int offset) {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "at(): Empty instance.",
+ cimg_instance);
+ return _at(offset);
+ }
+
+ T& _at(const int offset) {
+ const unsigned int siz = (unsigned int)size();
+ return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset];
+ }
+
+ //! Access to a pixel value at a specified offset, using Neumann boundary conditions \const.
+ const T& at(const int offset) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "at(): Empty instance.",
+ cimg_instance);
+ return _at(offset);
+ }
+
+ const T& _at(const int offset) const {
+ const unsigned int siz = (unsigned int)size();
+ return (*this)[offset<0?0:(unsigned int)offset>=siz?siz - 1:offset];
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate.
+ /**
+ Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c),
+ or to a specified default value in case of out-of-bounds access along the X-axis.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c (\c x,\c y,\c z,\c c) is outside image bounds.
+ \note
+ - Similar to operator()(), except that an out-of-bounds access along the X-axis returns the specified value
+ \c out_value.
+ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+ you are \e not sure about the validity of the specified pixel coordinates.
+ \warning
+ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+ **/
+ T& atX(const int x, const int y, const int z, const int c, const T& out_value) {
+ return (x<0 || x>=width())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions for the X-coordinate \const.
+ T atX(const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (x<0 || x>=width())?out_value:(*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate.
+ /**
+ Return a reference to the pixel value of the image instance located at (\c x,\c y,\c z,\c c),
+ or to the nearest pixel location in the image instance in case of out-of-bounds access along the X-axis.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note
+ - Similar to at(int,int,int,int,const T), except that an out-of-bounds access returns the value of the
+ nearest pixel in the image instance, regarding the specified X-coordinate.
+ - Due to the additional boundary checking operation, this method is slower than operator()(). Use it when
+ you are \e not sure about the validity of the specified pixel coordinates.
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _at(int,int,int,int).
+ \warning
+ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+ **/
+ T& atX(const int x, const int y=0, const int z=0, const int c=0) {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atX(): Empty instance.",
+ cimg_instance);
+ return _atX(x,y,z,c);
+ }
+
+ T& _atX(const int x, const int y=0, const int z=0, const int c=0) {
+ return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c);
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions for the X-coordinate \const.
+ const T& atX(const int x, const int y=0, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atX(): Empty instance.",
+ cimg_instance);
+ return _atX(x,y,z,c);
+ }
+
+ const T& _atX(const int x, const int y=0, const int z=0, const int c=0) const {
+ return (*this)(x<0?0:(x>=width()?width() - 1:x),y,z,c);
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y-coordinates.
+ /**
+ Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on X and Y-coordinates.
+ **/
+ T& atXY(const int x, const int y, const int z, const int c, const T& out_value) {
+ return (x<0 || y<0 || x>=width() || y>=height())?(cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions for the X and Y coordinates \const.
+ T atXY(const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (x<0 || y<0 || x>=width() || y>=height())?out_value:(*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates.
+ /**
+ Similar to atX(int,int,int,int), except that boundary checking is performed both on X and Y-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _atXY(int,int,int,int).
+ **/
+ T& atXY(const int x, const int y, const int z=0, const int c=0) {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atXY(): Empty instance.",
+ cimg_instance);
+ return _atXY(x,y,z,c);
+ }
+
+ T& _atXY(const int x, const int y, const int z=0, const int c=0) {
+ return (*this)(cimg::cut(x,0,width() - 1),
+ cimg::cut(y,0,height() - 1),z,c);
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions for the X and Y-coordinates \const.
+ const T& atXY(const int x, const int y, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atXY(): Empty instance.",
+ cimg_instance);
+ return _atXY(x,y,z,c);
+ }
+
+ const T& _atXY(const int x, const int y, const int z=0, const int c=0) const {
+ return (*this)(cimg::cut(x,0,width() - 1),
+ cimg::cut(y,0,height() - 1),z,c);
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates.
+ /**
+ Similar to atX(int,int,int,int,const T), except that boundary checking is performed both on
+ X,Y and Z-coordinates.
+ **/
+ T& atXYZ(const int x, const int y, const int z, const int c, const T& out_value) {
+ return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?
+ (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions for the X,Y and Z-coordinates \const.
+ T atXYZ(const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (x<0 || y<0 || z<0 || x>=width() || y>=height() || z>=depth())?out_value:(*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates.
+ /**
+ Similar to atX(int,int,int,int), except that boundary checking is performed both on X,Y and Z-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _atXYZ(int,int,int,int).
+ **/
+ T& atXYZ(const int x, const int y, const int z, const int c=0) {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atXYZ(): Empty instance.",
+ cimg_instance);
+ return _atXYZ(x,y,z,c);
+ }
+
+ T& _atXYZ(const int x, const int y, const int z, const int c=0) {
+ return (*this)(cimg::cut(x,0,width() - 1),
+ cimg::cut(y,0,height() - 1),
+ cimg::cut(z,0,depth() - 1),c);
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions for the X,Y and Z-coordinates \const.
+ const T& atXYZ(const int x, const int y, const int z, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atXYZ(): Empty instance.",
+ cimg_instance);
+ return _atXYZ(x,y,z,c);
+ }
+
+ const T& _atXYZ(const int x, const int y, const int z, const int c=0) const {
+ return (*this)(cimg::cut(x,0,width() - 1),
+ cimg::cut(y,0,height() - 1),
+ cimg::cut(z,0,depth() - 1),c);
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions.
+ /**
+ Similar to atX(int,int,int,int,const T), except that boundary checking is performed on all
+ X,Y,Z and C-coordinates.
+ **/
+ T& atXYZC(const int x, const int y, const int z, const int c, const T& out_value) {
+ return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?
+ (cimg::temporary(out_value)=out_value):(*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Dirichlet boundary conditions \const.
+ T atXYZC(const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (x<0 || y<0 || z<0 || c<0 || x>=width() || y>=height() || z>=depth() || c>=spectrum())?out_value:
+ (*this)(x,y,z,c);
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions.
+ /**
+ Similar to atX(int,int,int,int), except that boundary checking is performed on all X,Y,Z and C-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _atXYZC(int,int,int,int).
+ **/
+ T& atXYZC(const int x, const int y, const int z, const int c) {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atXYZC(): Empty instance.",
+ cimg_instance);
+ return _atXYZC(x,y,z,c);
+ }
+
+ T& _atXYZC(const int x, const int y, const int z, const int c) {
+ return (*this)(cimg::cut(x,0,width() - 1),
+ cimg::cut(y,0,height() - 1),
+ cimg::cut(z,0,depth() - 1),
+ cimg::cut(c,0,spectrum() - 1));
+ }
+
+ //! Access to a pixel value, using Neumann boundary conditions \const.
+ const T& atXYZC(const int x, const int y, const int z, const int c) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "atXYZC(): Empty instance.",
+ cimg_instance);
+ return _atXYZC(x,y,z,c);
+ }
+
+ const T& _atXYZC(const int x, const int y, const int z, const int c) const {
+ return (*this)(cimg::cut(x,0,width() - 1),
+ cimg::cut(y,0,height() - 1),
+ cimg::cut(z,0,depth() - 1),
+ cimg::cut(c,0,spectrum() - 1));
+ }
+
+ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X-coordinate.
+ /**
+ Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+ or a specified default value in case of out-of-bounds access along the X-axis.
+ \param fx X-coordinate of the pixel value (float-valued).
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds.
+ \note
+ - Similar to atX(int,int,int,int,const T), except that the returned pixel value is approximated by
+ a linear interpolation along the X-axis, if corresponding coordinates are not integers.
+ - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued.
+ \warning
+ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+ **/
+ Tfloat linear_atX(const float fx, const int y, const int z, const int c, const T& out_value) const {
+ const int
+ x = (int)fx - (fx>=0?0:1), nx = x + 1;
+ const float
+ dx = fx - x;
+ const Tfloat
+ Ic = (Tfloat)atX(x,y,z,c,out_value), In = (Tfloat)atXY(nx,y,z,c,out_value);
+ return Ic + dx*(In - Ic);
+ }
+
+ //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X-coordinate.
+ /**
+ Return a linearly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+ or the value of the nearest pixel location in the image instance in case of out-of-bounds access along
+ the X-axis.
+ \param fx X-coordinate of the pixel value (float-valued).
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note
+ - Similar to linear_atX(float,int,int,int,const T) const, except that an out-of-bounds access returns
+ the value of the nearest pixel in the image instance, regarding the specified X-coordinate.
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _linear_atX(float,int,int,int).
+ \warning
+ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+ **/
+ 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 = cimg::cut(fx,0,width() - 1);
+ const unsigned int
+ x = (unsigned int)nfx;
+ const float
+ dx = nfx - x;
+ const unsigned int
+ nx = dx>0?x + 1:x;
+ const Tfloat
+ Ic = (Tfloat)(*this)(x,y,z,c), In = (Tfloat)(*this)(nx,y,z,c);
+ return Ic + dx*(In - Ic);
+ }
+
+ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X and Y-coordinates.
+ /**
+ Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the
+ boundary checking are achieved both for X and Y-coordinates.
+ **/
+ Tfloat linear_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const {
+ const int
+ x = (int)fx - (fx>=0?0:1), nx = x + 1,
+ y = (int)fy - (fy>=0?0:1), ny = y + 1;
+ const float
+ dx = fx - x,
+ dy = fy - y;
+ const Tfloat
+ Icc = (Tfloat)atXY(x,y,z,c,out_value), Inc = (Tfloat)atXY(nx,y,z,c,out_value),
+ Icn = (Tfloat)atXY(x,ny,z,c,out_value), Inn = (Tfloat)atXY(nx,ny,z,c,out_value);
+ return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc);
+ }
+
+ //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates.
+ /**
+ Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking
+ are achieved both for X and Y-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _linear_atXY(float,float,int,int).
+ **/
+ Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "linear_atXY(): Empty instance.",
+ cimg_instance);
+
+ return _linear_atXY(fx,fy,z,c);
+ }
+
+ Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+ const float
+ nfx = cimg::cut(fx,0,width() - 1),
+ nfy = cimg::cut(fy,0,height() - 1);
+ const unsigned int
+ x = (unsigned int)nfx,
+ y = (unsigned int)nfy;
+ const float
+ dx = nfx - x,
+ dy = nfy - y;
+ const unsigned int
+ nx = dx>0?x + 1:x,
+ ny = dy>0?y + 1:y;
+ const Tfloat
+ Icc = (Tfloat)(*this)(x,y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c),
+ Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c);
+ return Icc + dx*(Inc - Icc + dy*(Icc + Inn - Icn - Inc)) + dy*(Icn - Icc);
+ }
+
+ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates.
+ /**
+ Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the
+ boundary checking are achieved both for X,Y and Z-coordinates.
+ **/
+ Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const {
+ const int
+ x = (int)fx - (fx>=0?0:1), nx = x + 1,
+ y = (int)fy - (fy>=0?0:1), ny = y + 1,
+ z = (int)fz - (fz>=0?0:1), nz = z + 1;
+ const float
+ dx = fx - x,
+ dy = fy - y,
+ dz = fz - z;
+ const Tfloat
+ Iccc = (Tfloat)atXYZ(x,y,z,c,out_value), Incc = (Tfloat)atXYZ(nx,y,z,c,out_value),
+ Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value), Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value),
+ Iccn = (Tfloat)atXYZ(x,y,nz,c,out_value), Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value),
+ Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value), Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value);
+ return Iccc +
+ dx*(Incc - Iccc +
+ dy*(Iccc + Innc - Icnc - Incc +
+ dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) +
+ dz*(Iccc + Incn - Iccn - Incc)) +
+ dy*(Icnc - Iccc +
+ dz*(Iccc + Icnn - Iccn - Icnc)) +
+ dz*(Iccn - Iccc);
+ }
+
+ //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates.
+ /**
+ Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking
+ are achieved both for X,Y and Z-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _linear_atXYZ(float,float,float,int).
+ **/
+ Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "linear_atXYZ(): Empty instance.",
+ cimg_instance);
+
+ return _linear_atXYZ(fx,fy,fz,c);
+ }
+
+ Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int c=0) const {
+ const float
+ nfx = cimg::cut(fx,0,width() - 1),
+ nfy = cimg::cut(fy,0,height() - 1),
+ nfz = cimg::cut(fz,0,depth() - 1);
+ const unsigned int
+ x = (unsigned int)nfx,
+ y = (unsigned int)nfy,
+ z = (unsigned int)nfz;
+ const float
+ dx = nfx - x,
+ dy = nfy - y,
+ dz = nfz - z;
+ const unsigned int
+ nx = dx>0?x + 1:x,
+ ny = dy>0?y + 1:y,
+ nz = dz>0?z + 1:z;
+ const Tfloat
+ Iccc = (Tfloat)(*this)(x,y,z,c), Incc = (Tfloat)(*this)(nx,y,z,c),
+ Icnc = (Tfloat)(*this)(x,ny,z,c), Innc = (Tfloat)(*this)(nx,ny,z,c),
+ Iccn = (Tfloat)(*this)(x,y,nz,c), Incn = (Tfloat)(*this)(nx,y,nz,c),
+ Icnn = (Tfloat)(*this)(x,ny,nz,c), Innn = (Tfloat)(*this)(nx,ny,nz,c);
+ return Iccc +
+ dx*(Incc - Iccc +
+ dy*(Iccc + Innc - Icnc - Incc +
+ dz*(Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)) +
+ dz*(Iccc + Incn - Iccn - Incc)) +
+ dy*(Icnc - Iccc +
+ dz*(Iccc + Icnn - Iccn - Icnc)) +
+ dz*(Iccn - Iccc);
+ }
+
+ //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z,C-coordinates.
+ /**
+ Similar to linear_atX(float,int,int,int,const T) const, except that the linear interpolation and the
+ boundary checking are achieved for all X,Y,Z and C-coordinates.
+ **/
+ Tfloat linear_atXYZC(const float fx, const float fy, const float fz, const float fc, const T& out_value) const {
+ const int
+ x = (int)fx - (fx>=0?0:1), nx = x + 1,
+ y = (int)fy - (fy>=0?0:1), ny = y + 1,
+ z = (int)fz - (fz>=0?0:1), nz = z + 1,
+ c = (int)fc - (fc>=0?0:1), nc = c + 1;
+ const float
+ dx = fx - x,
+ dy = fy - y,
+ dz = fz - z,
+ dc = fc - c;
+ const Tfloat
+ Icccc = (Tfloat)atXYZC(x,y,z,c,out_value), Inccc = (Tfloat)atXYZC(nx,y,z,c,out_value),
+ Icncc = (Tfloat)atXYZC(x,ny,z,c,out_value), Inncc = (Tfloat)atXYZC(nx,ny,z,c,out_value),
+ Iccnc = (Tfloat)atXYZC(x,y,nz,c,out_value), Incnc = (Tfloat)atXYZC(nx,y,nz,c,out_value),
+ Icnnc = (Tfloat)atXYZC(x,ny,nz,c,out_value), Innnc = (Tfloat)atXYZC(nx,ny,nz,c,out_value),
+ Icccn = (Tfloat)atXYZC(x,y,z,nc,out_value), Inccn = (Tfloat)atXYZC(nx,y,z,nc,out_value),
+ Icncn = (Tfloat)atXYZC(x,ny,z,nc,out_value), Inncn = (Tfloat)atXYZC(nx,ny,z,nc,out_value),
+ Iccnn = (Tfloat)atXYZC(x,y,nz,nc,out_value), Incnn = (Tfloat)atXYZC(nx,y,nz,nc,out_value),
+ Icnnn = (Tfloat)atXYZC(x,ny,nz,nc,out_value), Innnn = (Tfloat)atXYZC(nx,ny,nz,nc,out_value);
+ return Icccc +
+ dx*(Inccc - Icccc +
+ dy*(Icccc + Inncc - Icncc - Inccc +
+ dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc +
+ dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc -
+ Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) +
+ dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) +
+ dz*(Icccc + Incnc - Iccnc - Inccc +
+ dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) +
+ dc*(Icccc + Inccn - Inccc - Icccn)) +
+ dy*(Icncc - Icccc +
+ dz*(Icccc + Icnnc - Iccnc - Icncc +
+ dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) +
+ dc*(Icccc + Icncn - Icncc - Icccn)) +
+ dz*(Iccnc - Icccc +
+ dc*(Icccc + Iccnn - Iccnc - Icccn)) +
+ dc*(Icccn -Icccc);
+ }
+
+ //! Return pixel value, using linear interpolation and Neumann boundary conditions for all X,Y,Z and C-coordinates.
+ /**
+ Similar to linear_atX(float,int,int,int) const, except that the linear interpolation and the boundary checking
+ are achieved for all X,Y,Z and C-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _linear_atXYZC(float,float,float,float).
+ **/
+ Tfloat linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "linear_atXYZC(): Empty instance.",
+ cimg_instance);
+
+ return _linear_atXYZC(fx,fy,fz,fc);
+ }
+
+ Tfloat _linear_atXYZC(const float fx, const float fy=0, const float fz=0, const float fc=0) const {
+ const float
+ nfx = cimg::cut(fx,0,width() - 1),
+ nfy = cimg::cut(fy,0,height() - 1),
+ nfz = cimg::cut(fz,0,depth() - 1),
+ nfc = cimg::cut(fc,0,spectrum() - 1);
+ const unsigned int
+ x = (unsigned int)nfx,
+ y = (unsigned int)nfy,
+ z = (unsigned int)nfz,
+ c = (unsigned int)nfc;
+ const float
+ dx = nfx - x,
+ dy = nfy - y,
+ dz = nfz - z,
+ dc = nfc - c;
+ const unsigned int
+ nx = dx>0?x + 1:x,
+ ny = dy>0?y + 1:y,
+ nz = dz>0?z + 1:z,
+ nc = dc>0?c + 1:c;
+ const Tfloat
+ Icccc = (Tfloat)(*this)(x,y,z,c), Inccc = (Tfloat)(*this)(nx,y,z,c),
+ Icncc = (Tfloat)(*this)(x,ny,z,c), Inncc = (Tfloat)(*this)(nx,ny,z,c),
+ Iccnc = (Tfloat)(*this)(x,y,nz,c), Incnc = (Tfloat)(*this)(nx,y,nz,c),
+ Icnnc = (Tfloat)(*this)(x,ny,nz,c), Innnc = (Tfloat)(*this)(nx,ny,nz,c),
+ Icccn = (Tfloat)(*this)(x,y,z,nc), Inccn = (Tfloat)(*this)(nx,y,z,nc),
+ Icncn = (Tfloat)(*this)(x,ny,z,nc), Inncn = (Tfloat)(*this)(nx,ny,z,nc),
+ Iccnn = (Tfloat)(*this)(x,y,nz,nc), Incnn = (Tfloat)(*this)(nx,y,nz,nc),
+ Icnnn = (Tfloat)(*this)(x,ny,nz,nc), Innnn = (Tfloat)(*this)(nx,ny,nz,nc);
+ return Icccc +
+ dx*(Inccc - Icccc +
+ dy*(Icccc + Inncc - Icncc - Inccc +
+ dz*(Iccnc + Innnc + Icncc + Inccc - Icnnc - Incnc - Icccc - Inncc +
+ dc*(Iccnn + Innnn + Icncn + Inccn + Icnnc + Incnc + Icccc + Inncc -
+ Icnnn - Incnn - Icccn - Inncn - Iccnc - Innnc - Icncc - Inccc)) +
+ dc*(Icccn + Inncn + Icncc + Inccc - Icncn - Inccn - Icccc - Inncc)) +
+ dz*(Icccc + Incnc - Iccnc - Inccc +
+ dc*(Icccn + Incnn + Iccnc + Inccc - Iccnn - Inccn - Icccc - Incnc)) +
+ dc*(Icccc + Inccn - Inccc - Icccn)) +
+ dy*(Icncc - Icccc +
+ dz*(Icccc + Icnnc - Iccnc - Icncc +
+ dc*(Icccn + Icnnn + Iccnc + Icncc - Iccnn - Icncn - Icccc - Icnnc)) +
+ dc*(Icccc + Icncn - Icncc - Icccn)) +
+ dz*(Iccnc - Icccc +
+ dc*(Icccc + Iccnn - Iccnc - Icccn)) +
+ dc*(Icccn - Icccc);
+ }
+
+ //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate.
+ /**
+ Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+ or a specified default value in case of out-of-bounds access along the X-axis.
+ The cubic interpolation uses Hermite splines.
+ \param fx d X-coordinate of the pixel value (float-valued).
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c (\c fx,\c y,\c z,\c c) is outside image bounds.
+ \note
+ - Similar to linear_atX(float,int,int,int,const T) const, except that the returned pixel value is
+ approximated by a \e cubic interpolation along the X-axis.
+ - The type of the returned pixel value is extended to \c float, if the pixel type \c T is not float-valued.
+ \warning
+ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+ **/
+ Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T& out_value) const {
+ const int
+ x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2;
+ const float
+ dx = fx - x;
+ const Tfloat
+ Ip = (Tfloat)atX(px,y,z,c,out_value), Ic = (Tfloat)atX(x,y,z,c,out_value),
+ In = (Tfloat)atX(nx,y,z,c,out_value), Ia = (Tfloat)atX(ax,y,z,c,out_value);
+ return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia));
+ }
+
+ //! Return damped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X-coordinate.
+ /**
+ Similar to cubic_atX(float,int,int,int,const T) const, except that you can specify the authorized minimum
+ and maximum of the returned value.
+ **/
+ Tfloat cubic_atX(const float fx, const int y, const int z, const int c, const T& out_value,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = cubic_atX(fx,y,z,c,out_value);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate.
+ /**
+ Return a cubicly-interpolated pixel value of the image instance located at (\c fx,\c y,\c z,\c c),
+ or the value of the nearest pixel location in the image instance in case of out-of-bounds access
+ along the X-axis. The cubic interpolation uses Hermite splines.
+ \param fx X-coordinate of the pixel value (float-valued).
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note
+ - Similar to cubic_atX(float,int,int,int,const T) const, except that the returned pixel value is
+ approximated by a cubic interpolation along the X-axis.
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _cubic_atX(float,int,int,int).
+ \warning
+ - There is \e no boundary checking performed for the Y,Z and C-coordinates, so they must be inside image bounds.
+ **/
+ Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "cubic_atX(): Empty instance.",
+ cimg_instance);
+ return _cubic_atX(fx,y,z,c);
+ }
+
+ Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int c=0) const {
+ const float
+ nfx = cimg::cut(fx,0,width() - 1);
+ const int
+ x = (int)nfx;
+ const float
+ dx = nfx - x;
+ const int
+ px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2;
+ const Tfloat
+ Ip = (Tfloat)(*this)(px,y,z,c), Ic = (Tfloat)(*this)(x,y,z,c),
+ In = (Tfloat)(*this)(nx,y,z,c), Ia = (Tfloat)(*this)(ax,y,z,c);
+ return Ic + 0.5f*(dx*(-Ip + In) + dx*dx*(2*Ip - 5*Ic + 4*In - Ia) + dx*dx*dx*(-Ip + 3*Ic - 3*In + Ia));
+ }
+
+ //! Return damped pixel value, using cubic interpolation and Neumann boundary conditions for the X-coordinate.
+ /**
+ Similar to cubic_atX(float,int,int,int) const, except that you can specify the authorized minimum and maximum
+ of the returned value.
+ **/
+ Tfloat cubic_atX(const float fx, const int y, const int z, const int c,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = cubic_atX(fx,y,z,c);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ Tfloat _cubic_atX(const float fx, const int y, const int z, const int c,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = _cubic_atX(fx,y,z,c);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X and Y-coordinates.
+ /**
+ Similar to cubic_atX(float,int,int,int,const T) const, except that the cubic interpolation and boundary checking
+ are achieved both for X and Y-coordinates.
+ **/
+ Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const {
+ const int
+ x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2,
+ y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2;
+ const float dx = fx - x, dy = fy - y;
+ const Tfloat
+ Ipp = (Tfloat)atXY(px,py,z,c,out_value), Icp = (Tfloat)atXY(x,py,z,c,out_value),
+ Inp = (Tfloat)atXY(nx,py,z,c,out_value), Iap = (Tfloat)atXY(ax,py,z,c,out_value),
+ Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)),
+ Ipc = (Tfloat)atXY(px,y,z,c,out_value), Icc = (Tfloat)atXY(x, y,z,c,out_value),
+ Inc = (Tfloat)atXY(nx,y,z,c,out_value), Iac = (Tfloat)atXY(ax,y,z,c,out_value),
+ Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)),
+ Ipn = (Tfloat)atXY(px,ny,z,c,out_value), Icn = (Tfloat)atXY(x,ny,z,c,out_value),
+ Inn = (Tfloat)atXY(nx,ny,z,c,out_value), Ian = (Tfloat)atXY(ax,ny,z,c,out_value),
+ In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)),
+ Ipa = (Tfloat)atXY(px,ay,z,c,out_value), Ica = (Tfloat)atXY(x,ay,z,c,out_value),
+ Ina = (Tfloat)atXY(nx,ay,z,c,out_value), Iaa = (Tfloat)atXY(ax,ay,z,c,out_value),
+ Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa));
+ return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia));
+ }
+
+ //! Return damped pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y-coordinates.
+ /**
+ Similar to cubic_atXY(float,float,int,int,const T) const, except that you can specify the authorized
+ minimum and maximum of the returned value.
+ **/
+ Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c, const T& out_value,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = cubic_atXY(fx,fy,z,c,out_value);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X and Y-coordinates.
+ /**
+ Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking
+ are achieved for both X and Y-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _cubic_atXY(float,float,int,int).
+ **/
+ Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "cubic_atXY(): Empty instance.",
+ cimg_instance);
+ return _cubic_atXY(fx,fy,z,c);
+ }
+
+ Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
+ const float
+ nfx = cimg::cut(fx,0,width() - 1),
+ nfy = cimg::cut(fy,0,height() - 1);
+ const int x = (int)nfx, y = (int)nfy;
+ const float dx = nfx - x, dy = nfy - y;
+ const int
+ px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2,
+ py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2;
+ const Tfloat
+ Ipp = (Tfloat)(*this)(px,py,z,c), Icp = (Tfloat)(*this)(x,py,z,c), Inp = (Tfloat)(*this)(nx,py,z,c),
+ Iap = (Tfloat)(*this)(ax,py,z,c),
+ Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)),
+ Ipc = (Tfloat)(*this)(px,y,z,c), Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c),
+ Iac = (Tfloat)(*this)(ax,y,z,c),
+ Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)),
+ Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c),
+ Ian = (Tfloat)(*this)(ax,ny,z,c),
+ In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)),
+ Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c),
+ Iaa = (Tfloat)(*this)(ax,ay,z,c),
+ Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa));
+ return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia));
+ }
+
+ //! Return damped pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y-coordinates.
+ /**
+ Similar to cubic_atXY(float,float,int,int) const, except that you can specify the authorized minimum and
+ maximum of the returned value.
+ **/
+ Tfloat cubic_atXY(const float fx, const float fy, const int z, const int c,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = cubic_atXY(fx,fy,z,c);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ Tfloat _cubic_atXY(const float fx, const float fy, const int z, const int c,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = _cubic_atXY(fx,fy,z,c);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ //! Return pixel value, using cubic interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates.
+ /**
+ Similar to cubic_atX(float,int,int,int,const T) const, except that the cubic interpolation and boundary checking
+ are achieved both for X,Y and Z-coordinates.
+ **/
+ Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const {
+ const int
+ x = (int)fx - (fx>=0?0:1), px = x - 1, nx = x + 1, ax = x + 2,
+ y = (int)fy - (fy>=0?0:1), py = y - 1, ny = y + 1, ay = y + 2,
+ z = (int)fz - (fz>=0?0:1), pz = z - 1, nz = z + 1, az = z + 2;
+ const float dx = fx - x, dy = fy - y, dz = fz - z;
+ const Tfloat
+ Ippp = (Tfloat)atXYZ(px,py,pz,c,out_value), Icpp = (Tfloat)atXYZ(x,py,pz,c,out_value),
+ Inpp = (Tfloat)atXYZ(nx,py,pz,c,out_value), Iapp = (Tfloat)atXYZ(ax,py,pz,c,out_value),
+ Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) +
+ dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)),
+ Ipcp = (Tfloat)atXYZ(px,y,pz,c,out_value), Iccp = (Tfloat)atXYZ(x, y,pz,c,out_value),
+ Incp = (Tfloat)atXYZ(nx,y,pz,c,out_value), Iacp = (Tfloat)atXYZ(ax,y,pz,c,out_value),
+ Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) +
+ dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)),
+ Ipnp = (Tfloat)atXYZ(px,ny,pz,c,out_value), Icnp = (Tfloat)atXYZ(x,ny,pz,c,out_value),
+ Innp = (Tfloat)atXYZ(nx,ny,pz,c,out_value), Ianp = (Tfloat)atXYZ(ax,ny,pz,c,out_value),
+ Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) +
+ dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)),
+ Ipap = (Tfloat)atXYZ(px,ay,pz,c,out_value), Icap = (Tfloat)atXYZ(x,ay,pz,c,out_value),
+ Inap = (Tfloat)atXYZ(nx,ay,pz,c,out_value), Iaap = (Tfloat)atXYZ(ax,ay,pz,c,out_value),
+ Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) +
+ dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)),
+ Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) +
+ dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)),
+ Ippc = (Tfloat)atXYZ(px,py,z,c,out_value), Icpc = (Tfloat)atXYZ(x,py,z,c,out_value),
+ Inpc = (Tfloat)atXYZ(nx,py,z,c,out_value), Iapc = (Tfloat)atXYZ(ax,py,z,c,out_value),
+ Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) +
+ dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)),
+ Ipcc = (Tfloat)atXYZ(px,y,z,c,out_value), Iccc = (Tfloat)atXYZ(x, y,z,c,out_value),
+ Incc = (Tfloat)atXYZ(nx,y,z,c,out_value), Iacc = (Tfloat)atXYZ(ax,y,z,c,out_value),
+ Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) +
+ dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)),
+ Ipnc = (Tfloat)atXYZ(px,ny,z,c,out_value), Icnc = (Tfloat)atXYZ(x,ny,z,c,out_value),
+ Innc = (Tfloat)atXYZ(nx,ny,z,c,out_value), Ianc = (Tfloat)atXYZ(ax,ny,z,c,out_value),
+ Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) +
+ dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)),
+ Ipac = (Tfloat)atXYZ(px,ay,z,c,out_value), Icac = (Tfloat)atXYZ(x,ay,z,c,out_value),
+ Inac = (Tfloat)atXYZ(nx,ay,z,c,out_value), Iaac = (Tfloat)atXYZ(ax,ay,z,c,out_value),
+ Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) +
+ dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)),
+ Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) +
+ dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)),
+ Ippn = (Tfloat)atXYZ(px,py,nz,c,out_value), Icpn = (Tfloat)atXYZ(x,py,nz,c,out_value),
+ Inpn = (Tfloat)atXYZ(nx,py,nz,c,out_value), Iapn = (Tfloat)atXYZ(ax,py,nz,c,out_value),
+ Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) +
+ dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)),
+ Ipcn = (Tfloat)atXYZ(px,y,nz,c,out_value), Iccn = (Tfloat)atXYZ(x, y,nz,c,out_value),
+ Incn = (Tfloat)atXYZ(nx,y,nz,c,out_value), Iacn = (Tfloat)atXYZ(ax,y,nz,c,out_value),
+ Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) +
+ dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)),
+ Ipnn = (Tfloat)atXYZ(px,ny,nz,c,out_value), Icnn = (Tfloat)atXYZ(x,ny,nz,c,out_value),
+ Innn = (Tfloat)atXYZ(nx,ny,nz,c,out_value), Iann = (Tfloat)atXYZ(ax,ny,nz,c,out_value),
+ Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) +
+ dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)),
+ Ipan = (Tfloat)atXYZ(px,ay,nz,c,out_value), Ican = (Tfloat)atXYZ(x,ay,nz,c,out_value),
+ Inan = (Tfloat)atXYZ(nx,ay,nz,c,out_value), Iaan = (Tfloat)atXYZ(ax,ay,nz,c,out_value),
+ Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) +
+ dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)),
+ In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) +
+ dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)),
+ Ippa = (Tfloat)atXYZ(px,py,az,c,out_value), Icpa = (Tfloat)atXYZ(x,py,az,c,out_value),
+ Inpa = (Tfloat)atXYZ(nx,py,az,c,out_value), Iapa = (Tfloat)atXYZ(ax,py,az,c,out_value),
+ Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) +
+ dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)),
+ Ipca = (Tfloat)atXYZ(px,y,az,c,out_value), Icca = (Tfloat)atXYZ(x, y,az,c,out_value),
+ Inca = (Tfloat)atXYZ(nx,y,az,c,out_value), Iaca = (Tfloat)atXYZ(ax,y,az,c,out_value),
+ Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) +
+ dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)),
+ Ipna = (Tfloat)atXYZ(px,ny,az,c,out_value), Icna = (Tfloat)atXYZ(x,ny,az,c,out_value),
+ Inna = (Tfloat)atXYZ(nx,ny,az,c,out_value), Iana = (Tfloat)atXYZ(ax,ny,az,c,out_value),
+ Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) +
+ dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)),
+ Ipaa = (Tfloat)atXYZ(px,ay,az,c,out_value), Icaa = (Tfloat)atXYZ(x,ay,az,c,out_value),
+ Inaa = (Tfloat)atXYZ(nx,ay,az,c,out_value), Iaaa = (Tfloat)atXYZ(ax,ay,az,c,out_value),
+ Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) +
+ dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)),
+ Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) +
+ dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa));
+ return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia));
+ }
+
+ //! Return damped pixel value, using cubic interpolation and Dirichlet boundary conditions for the XYZ-coordinates.
+ /**
+ Similar to cubic_atXYZ(float,float,float,int,const T) const, except that you can specify the authorized
+ minimum and maximum of the returned value.
+ **/
+ Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = cubic_atXYZ(fx,fy,fz,c,out_value);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ //! Return pixel value, using cubic interpolation and Neumann boundary conditions for the X,Y and Z-coordinates.
+ /**
+ Similar to cubic_atX(float,int,int,int) const, except that the cubic interpolation and boundary checking
+ are achieved both for X,Y and Z-coordinates.
+ \note
+ - If you know your image instance is \e not empty, you may rather use the slightly faster method
+ \c _cubic_atXYZ(float,float,float,int).
+ **/
+ Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "cubic_atXYZ(): Empty instance.",
+ cimg_instance);
+ return _cubic_atXYZ(fx,fy,fz,c);
+ }
+
+ Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c=0) const {
+ const float
+ nfx = cimg::cut(fx,0,width() - 1),
+ nfy = cimg::cut(fy,0,height() - 1),
+ nfz = cimg::cut(fz,0,depth() - 1);
+ const int x = (int)nfx, y = (int)nfy, z = (int)nfz;
+ const float dx = nfx - x, dy = nfy - y, dz = nfz - z;
+ const int
+ px = x - 1<0?0:x - 1, nx = dx>0?x + 1:x, ax = x + 2>=width()?width() - 1:x + 2,
+ py = y - 1<0?0:y - 1, ny = dy>0?y + 1:y, ay = y + 2>=height()?height() - 1:y + 2,
+ pz = z - 1<0?0:z - 1, nz = dz>0?z + 1:z, az = z + 2>=depth()?depth() - 1:z + 2;
+ const Tfloat
+ Ippp = (Tfloat)(*this)(px,py,pz,c), Icpp = (Tfloat)(*this)(x,py,pz,c),
+ Inpp = (Tfloat)(*this)(nx,py,pz,c), Iapp = (Tfloat)(*this)(ax,py,pz,c),
+ Ipp = Icpp + 0.5f*(dx*(-Ippp + Inpp) + dx*dx*(2*Ippp - 5*Icpp + 4*Inpp - Iapp) +
+ dx*dx*dx*(-Ippp + 3*Icpp - 3*Inpp + Iapp)),
+ Ipcp = (Tfloat)(*this)(px,y,pz,c), Iccp = (Tfloat)(*this)(x, y,pz,c),
+ Incp = (Tfloat)(*this)(nx,y,pz,c), Iacp = (Tfloat)(*this)(ax,y,pz,c),
+ Icp = Iccp + 0.5f*(dx*(-Ipcp + Incp) + dx*dx*(2*Ipcp - 5*Iccp + 4*Incp - Iacp) +
+ dx*dx*dx*(-Ipcp + 3*Iccp - 3*Incp + Iacp)),
+ Ipnp = (Tfloat)(*this)(px,ny,pz,c), Icnp = (Tfloat)(*this)(x,ny,pz,c),
+ Innp = (Tfloat)(*this)(nx,ny,pz,c), Ianp = (Tfloat)(*this)(ax,ny,pz,c),
+ Inp = Icnp + 0.5f*(dx*(-Ipnp + Innp) + dx*dx*(2*Ipnp - 5*Icnp + 4*Innp - Ianp) +
+ dx*dx*dx*(-Ipnp + 3*Icnp - 3*Innp + Ianp)),
+ Ipap = (Tfloat)(*this)(px,ay,pz,c), Icap = (Tfloat)(*this)(x,ay,pz,c),
+ Inap = (Tfloat)(*this)(nx,ay,pz,c), Iaap = (Tfloat)(*this)(ax,ay,pz,c),
+ Iap = Icap + 0.5f*(dx*(-Ipap + Inap) + dx*dx*(2*Ipap - 5*Icap + 4*Inap - Iaap) +
+ dx*dx*dx*(-Ipap + 3*Icap - 3*Inap + Iaap)),
+ Ip = Icp + 0.5f*(dy*(-Ipp + Inp) + dy*dy*(2*Ipp - 5*Icp + 4*Inp - Iap) +
+ dy*dy*dy*(-Ipp + 3*Icp - 3*Inp + Iap)),
+ Ippc = (Tfloat)(*this)(px,py,z,c), Icpc = (Tfloat)(*this)(x,py,z,c),
+ Inpc = (Tfloat)(*this)(nx,py,z,c), Iapc = (Tfloat)(*this)(ax,py,z,c),
+ Ipc = Icpc + 0.5f*(dx*(-Ippc + Inpc) + dx*dx*(2*Ippc - 5*Icpc + 4*Inpc - Iapc) +
+ dx*dx*dx*(-Ippc + 3*Icpc - 3*Inpc + Iapc)),
+ Ipcc = (Tfloat)(*this)(px,y,z,c), Iccc = (Tfloat)(*this)(x, y,z,c),
+ Incc = (Tfloat)(*this)(nx,y,z,c), Iacc = (Tfloat)(*this)(ax,y,z,c),
+ Icc = Iccc + 0.5f*(dx*(-Ipcc + Incc) + dx*dx*(2*Ipcc - 5*Iccc + 4*Incc - Iacc) +
+ dx*dx*dx*(-Ipcc + 3*Iccc - 3*Incc + Iacc)),
+ Ipnc = (Tfloat)(*this)(px,ny,z,c), Icnc = (Tfloat)(*this)(x,ny,z,c),
+ Innc = (Tfloat)(*this)(nx,ny,z,c), Ianc = (Tfloat)(*this)(ax,ny,z,c),
+ Inc = Icnc + 0.5f*(dx*(-Ipnc + Innc) + dx*dx*(2*Ipnc - 5*Icnc + 4*Innc - Ianc) +
+ dx*dx*dx*(-Ipnc + 3*Icnc - 3*Innc + Ianc)),
+ Ipac = (Tfloat)(*this)(px,ay,z,c), Icac = (Tfloat)(*this)(x,ay,z,c),
+ Inac = (Tfloat)(*this)(nx,ay,z,c), Iaac = (Tfloat)(*this)(ax,ay,z,c),
+ Iac = Icac + 0.5f*(dx*(-Ipac + Inac) + dx*dx*(2*Ipac - 5*Icac + 4*Inac - Iaac) +
+ dx*dx*dx*(-Ipac + 3*Icac - 3*Inac + Iaac)),
+ Ic = Icc + 0.5f*(dy*(-Ipc + Inc) + dy*dy*(2*Ipc - 5*Icc + 4*Inc - Iac) +
+ dy*dy*dy*(-Ipc + 3*Icc - 3*Inc + Iac)),
+ Ippn = (Tfloat)(*this)(px,py,nz,c), Icpn = (Tfloat)(*this)(x,py,nz,c),
+ Inpn = (Tfloat)(*this)(nx,py,nz,c), Iapn = (Tfloat)(*this)(ax,py,nz,c),
+ Ipn = Icpn + 0.5f*(dx*(-Ippn + Inpn) + dx*dx*(2*Ippn - 5*Icpn + 4*Inpn - Iapn) +
+ dx*dx*dx*(-Ippn + 3*Icpn - 3*Inpn + Iapn)),
+ Ipcn = (Tfloat)(*this)(px,y,nz,c), Iccn = (Tfloat)(*this)(x, y,nz,c),
+ Incn = (Tfloat)(*this)(nx,y,nz,c), Iacn = (Tfloat)(*this)(ax,y,nz,c),
+ Icn = Iccn + 0.5f*(dx*(-Ipcn + Incn) + dx*dx*(2*Ipcn - 5*Iccn + 4*Incn - Iacn) +
+ dx*dx*dx*(-Ipcn + 3*Iccn - 3*Incn + Iacn)),
+ Ipnn = (Tfloat)(*this)(px,ny,nz,c), Icnn = (Tfloat)(*this)(x,ny,nz,c),
+ Innn = (Tfloat)(*this)(nx,ny,nz,c), Iann = (Tfloat)(*this)(ax,ny,nz,c),
+ Inn = Icnn + 0.5f*(dx*(-Ipnn + Innn) + dx*dx*(2*Ipnn - 5*Icnn + 4*Innn - Iann) +
+ dx*dx*dx*(-Ipnn + 3*Icnn - 3*Innn + Iann)),
+ Ipan = (Tfloat)(*this)(px,ay,nz,c), Ican = (Tfloat)(*this)(x,ay,nz,c),
+ Inan = (Tfloat)(*this)(nx,ay,nz,c), Iaan = (Tfloat)(*this)(ax,ay,nz,c),
+ Ian = Ican + 0.5f*(dx*(-Ipan + Inan) + dx*dx*(2*Ipan - 5*Ican + 4*Inan - Iaan) +
+ dx*dx*dx*(-Ipan + 3*Ican - 3*Inan + Iaan)),
+ In = Icn + 0.5f*(dy*(-Ipn + Inn) + dy*dy*(2*Ipn - 5*Icn + 4*Inn - Ian) +
+ dy*dy*dy*(-Ipn + 3*Icn - 3*Inn + Ian)),
+ Ippa = (Tfloat)(*this)(px,py,az,c), Icpa = (Tfloat)(*this)(x,py,az,c),
+ Inpa = (Tfloat)(*this)(nx,py,az,c), Iapa = (Tfloat)(*this)(ax,py,az,c),
+ Ipa = Icpa + 0.5f*(dx*(-Ippa + Inpa) + dx*dx*(2*Ippa - 5*Icpa + 4*Inpa - Iapa) +
+ dx*dx*dx*(-Ippa + 3*Icpa - 3*Inpa + Iapa)),
+ Ipca = (Tfloat)(*this)(px,y,az,c), Icca = (Tfloat)(*this)(x, y,az,c),
+ Inca = (Tfloat)(*this)(nx,y,az,c), Iaca = (Tfloat)(*this)(ax,y,az,c),
+ Ica = Icca + 0.5f*(dx*(-Ipca + Inca) + dx*dx*(2*Ipca - 5*Icca + 4*Inca - Iaca) +
+ dx*dx*dx*(-Ipca + 3*Icca - 3*Inca + Iaca)),
+ Ipna = (Tfloat)(*this)(px,ny,az,c), Icna = (Tfloat)(*this)(x,ny,az,c),
+ Inna = (Tfloat)(*this)(nx,ny,az,c), Iana = (Tfloat)(*this)(ax,ny,az,c),
+ Ina = Icna + 0.5f*(dx*(-Ipna + Inna) + dx*dx*(2*Ipna - 5*Icna + 4*Inna - Iana) +
+ dx*dx*dx*(-Ipna + 3*Icna - 3*Inna + Iana)),
+ Ipaa = (Tfloat)(*this)(px,ay,az,c), Icaa = (Tfloat)(*this)(x,ay,az,c),
+ Inaa = (Tfloat)(*this)(nx,ay,az,c), Iaaa = (Tfloat)(*this)(ax,ay,az,c),
+ Iaa = Icaa + 0.5f*(dx*(-Ipaa + Inaa) + dx*dx*(2*Ipaa - 5*Icaa + 4*Inaa - Iaaa) +
+ dx*dx*dx*(-Ipaa + 3*Icaa - 3*Inaa + Iaaa)),
+ Ia = Ica + 0.5f*(dy*(-Ipa + Ina) + dy*dy*(2*Ipa - 5*Ica + 4*Ina - Iaa) +
+ dy*dy*dy*(-Ipa + 3*Ica - 3*Ina + Iaa));
+ return Ic + 0.5f*(dz*(-Ip + In) + dz*dz*(2*Ip - 5*Ic + 4*In - Ia) + dz*dz*dz*(-Ip + 3*Ic - 3*In + Ia));
+ }
+
+ //! Return damped pixel value, using cubic interpolation and Neumann boundary conditions for the XYZ-coordinates.
+ /**
+ Similar to cubic_atXYZ(float,float,float,int) const, except that you can specify the authorized minimum and
+ maximum of the returned value.
+ **/
+ Tfloat cubic_atXYZ(const float fx, const float fy, const float fz, const int c,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = cubic_atXYZ(fx,fy,fz,c);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ Tfloat _cubic_atXYZ(const float fx, const float fy, const float fz, const int c,
+ const Tfloat min_value, const Tfloat max_value) const {
+ const Tfloat val = _cubic_atXYZ(fx,fy,fz,c);
+ return val<min_value?min_value:val>max_value?max_value:val;
+ }
+
+ //! Set pixel value, using linear interpolation for the X-coordinates.
+ /**
+ Set pixel value at specified coordinates (\c fx,\c y,\c z,\c c) in the image instance, in a way that
+ the value is spread amongst several neighbors if the pixel coordinates are float-valued.
+ \param value Pixel value to set.
+ \param fx X-coordinate of the pixel value (float-valued).
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param is_added Tells if the pixel value is added to (\c true), or simply replace (\c false) the current image
+ pixel(s).
+ \return A reference to the current image instance.
+ \note
+ - Calling this method with out-of-bounds coordinates does nothing.
+ **/
+ CImg<T>& set_linear_atX(const T& value, const float fx, const int y=0, const int z=0, const int c=0,
+ const bool is_added=false) {
+ const int
+ x = (int)fx - (fx>=0?0:1), nx = x + 1;
+ const float
+ dx = fx - x;
+ if (y>=0 && y<height() && z>=0 && z<depth() && c>=0 && c<spectrum()) {
+ if (x>=0 && x<width()) {
+ const float w1 = 1 - dx, w2 = is_added?1:(1 - w1);
+ (*this)(x,y,z,c) = (T)(w1*value + w2*(*this)(x,y,z,c));
+ }
+ if (nx>=0 && nx<width()) {
+ const float w1 = dx, w2 = is_added?1:(1 - w1);
+ (*this)(nx,y,z,c) = (T)(w1*value + w2*(*this)(nx,y,z,c));
+ }
+ }
+ return *this;
+ }
+
+ //! Set pixel value, using linear interpolation for the X and Y-coordinates.
+ /**
+ Similar to set_linear_atX(const T&,float,int,int,int,bool), except that the linear interpolation
+ is achieved both for X and Y-coordinates.
+ **/
+ CImg<T>& set_linear_atXY(const T& value, const float fx, const float fy=0, const int z=0, const int c=0,
+ const bool is_added=false) {
+ const int
+ x = (int)fx - (fx>=0?0:1), nx = x + 1,
+ y = (int)fy - (fy>=0?0:1), ny = y + 1;
+ const float
+ dx = fx - x,
+ dy = fy - y;
+ if (z>=0 && z<depth() && c>=0 && c<spectrum()) {
+ if (y>=0 && y<height()) {
+ if (x>=0 && x<width()) {
+ const float w1 = (1 - dx)*(1 - dy), w2 = is_added?1:(1 - w1);
+ (*this)(x,y,z,c) = (T)(w1*value + w2*(*this)(x,y,z,c));
+ }
+ if (nx>=0 && nx<width()) {
+ const float w1 = dx*(1 - dy), w2 = is_added?1:(1 - w1);
+ (*this)(nx,y,z,c) = (T)(w1*value + w2*(*this)(nx,y,z,c));
+ }
+ }
+ if (ny>=0 && ny<height()) {
+ if (x>=0 && x<width()) {
+ const float w1 = (1 - dx)*dy, w2 = is_added?1:(1 - w1);
+ (*this)(x,ny,z,c) = (T)(w1*value + w2*(*this)(x,ny,z,c));
+ }
+ if (nx>=0 && nx<width()) {
+ const float w1 = dx*dy, w2 = is_added?1:(1 - w1);
+ (*this)(nx,ny,z,c) = (T)(w1*value + w2*(*this)(nx,ny,z,c));
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Set pixel value, using linear interpolation for the X,Y and Z-coordinates.
+ /**
+ Similar to set_linear_atXY(const T&,float,float,int,int,bool), except that the linear interpolation
+ is achieved both for X,Y and Z-coordinates.
+ **/
+ CImg<T>& set_linear_atXYZ(const T& value, const float fx, const float fy=0, const float fz=0, const int c=0,
+ const bool is_added=false) {
+ const int
+ x = (int)fx - (fx>=0?0:1), nx = x + 1,
+ y = (int)fy - (fy>=0?0:1), ny = y + 1,
+ z = (int)fz - (fz>=0?0:1), nz = z + 1;
+ const float
+ dx = fx - x,
+ dy = fy - y,
+ dz = fz - z;
+ if (c>=0 && c<spectrum()) {
+ if (z>=0 && z<depth()) {
+ if (y>=0 && y<height()) {
+ if (x>=0 && x<width()) {
+ const float w1 = (1 - dx)*(1 - dy)*(1 - dz), w2 = is_added?1:(1 - w1);
+ (*this)(x,y,z,c) = (T)(w1*value + w2*(*this)(x,y,z,c));
+ }
+ if (nx>=0 && nx<width()) {
+ const float w1 = dx*(1 - dy)*(1 - dz), w2 = is_added?1:(1 - w1);
+ (*this)(nx,y,z,c) = (T)(w1*value + w2*(*this)(nx,y,z,c));
+ }
+ }
+ if (ny>=0 && ny<height()) {
+ if (x>=0 && x<width()) {
+ const float w1 = (1 - dx)*dy*(1 - dz), w2 = is_added?1:(1 - w1);
+ (*this)(x,ny,z,c) = (T)(w1*value + w2*(*this)(x,ny,z,c));
+ }
+ if (nx>=0 && nx<width()) {
+ const float w1 = dx*dy*(1 - dz), w2 = is_added?1:(1 - w1);
+ (*this)(nx,ny,z,c) = (T)(w1*value + w2*(*this)(nx,ny,z,c));
+ }
+ }
+ }
+ if (nz>=0 && nz<depth()) {
+ if (y>=0 && y<height()) {
+ if (x>=0 && x<width()) {
+ const float w1 = (1 - dx)*(1 - dy)*dz, w2 = is_added?1:(1 - w1);
+ (*this)(x,y,nz,c) = (T)(w1*value + w2*(*this)(x,y,nz,c));
+ }
+ if (nx>=0 && nx<width()) {
+ const float w1 = dx*(1 - dy)*dz, w2 = is_added?1:(1 - w1);
+ (*this)(nx,y,nz,c) = (T)(w1*value + w2*(*this)(nx,y,nz,c));
+ }
+ }
+ if (ny>=0 && ny<height()) {
+ if (x>=0 && x<width()) {
+ const float w1 = (1 - dx)*dy*dz, w2 = is_added?1:(1 - w1);
+ (*this)(x,ny,nz,c) = (T)(w1*value + w2*(*this)(x,ny,nz,c));
+ }
+ if (nx>=0 && nx<width()) {
+ const float w1 = dx*dy*dz, w2 = is_added?1:(1 - w1);
+ (*this)(nx,ny,nz,c) = (T)(w1*value + w2*(*this)(nx,ny,nz,c));
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Return a C-string containing a list of all values of the image instance.
+ /**
+ Return a new \c CImg<char> image whose buffer data() is a \c char* string describing the list of all pixel values
+ of the image instance (written in base 10), separated by specified \c separator character.
+ \param separator A \c char character which specifies the separator between values in the returned C-string.
+ \param max_size Maximum size of the returned image.
+ \param format For float-values, tell the printf format used to generate the ascii representation of the numbers.
+ (or \c 0 for default representation).
+ \note
+ - The returned image is never empty.
+ - For an empty image instance, the returned string is <tt>""</tt>.
+ - If \c max_size is equal to \c 0, there are no limits on the size of the returned string.
+ - Otherwise, if the maximum number of string characters is exceeded, the value string is cut off
+ and terminated by character \c '\0'. In that case, the returned image size is <tt>max_size + 1</tt>.
+ **/
+ CImg<charT> value_string(const char separator=',', const unsigned int max_size=0,
+ const char *const format=0) const {
+ if (is_empty()) return CImg<charT>::string("");
+ CImgList<charT> items;
+ CImg<charT> s_item(256); *s_item = 0;
+ const T *ptrs = _data;
+ unsigned int string_size = 0;
+ const char *const _format = format?format:cimg::type<T>::format();
+ for (ulongT off = 0, siz = size(); off<siz && string_size<=max_size; ++off) {
+ const unsigned int printed_size = 1U + cimg_snprintf(s_item,s_item._width,_format,
+ cimg::type<T>::format(*(ptrs++)));
+ CImg<charT> item(s_item._data,printed_size);
+ item[printed_size - 1] = separator;
+ item.move_to(items);
+ if (max_size) string_size+=printed_size;
+ }
+ CImg<charT> res;
+ (items>'x').move_to(res);
+ if (max_size && res._width>max_size) res.crop(0,max_size);
+ res.back() = 0;
+ return res;
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Instance Checking
+ //@{
+ //-------------------------------------
+
+ //! Test shared state of the pixel buffer.
+ /**
+ Return \c true if image instance has a shared memory buffer, and \c false otherwise.
+ \note
+ - A shared image do not own his pixel buffer data() and will not deallocate it on destruction.
+ - Most of the time, a \c CImg<T> image instance will \e not be shared.
+ - A shared image can only be obtained by a limited set of constructors and methods (see list below).
+ **/
+ bool is_shared() const {
+ return _is_shared;
+ }
+
+ //! Test if image instance is empty.
+ /**
+ Return \c true, if image instance is empty, i.e. does \e not contain any pixel values, has dimensions
+ \c 0 x \c 0 x \c 0 x \c 0 and a pixel buffer pointer set to \c 0 (null pointer), and \c false otherwise.
+ **/
+ bool is_empty() const {
+ return !(_data && _width && _height && _depth && _spectrum);
+ }
+
+ //! Test if image instance contains a 'inf' value.
+ /**
+ Return \c true, if image instance contains a 'inf' value, and \c false otherwise.
+ **/
+ bool is_inf() const {
+ if (cimg::type<T>::is_float()) cimg_for(*this,p,T) if (cimg::type<T>::is_inf((float)*p)) return true;
+ return false;
+ }
+
+ //! Test if image instance contains a NaN value.
+ /**
+ Return \c true, if image instance contains a NaN value, and \c false otherwise.
+ **/
+ bool is_nan() const {
+ if (cimg::type<T>::is_float()) cimg_for(*this,p,T) if (cimg::type<T>::is_nan((float)*p)) return true;
+ return false;
+ }
+
+ //! Test if image width is equal to specified value.
+ bool is_sameX(const unsigned int size_x) const {
+ return _width==size_x;
+ }
+
+ //! Test if image width is equal to specified value.
+ template<typename t>
+ bool is_sameX(const CImg<t>& img) const {
+ return is_sameX(img._width);
+ }
+
+ //! Test if image width is equal to specified value.
+ bool is_sameX(const CImgDisplay& disp) const {
+ return is_sameX(disp._width);
+ }
+
+ //! Test if image height is equal to specified value.
+ bool is_sameY(const unsigned int size_y) const {
+ return _height==size_y;
+ }
+
+ //! Test if image height is equal to specified value.
+ template<typename t>
+ bool is_sameY(const CImg<t>& img) const {
+ return is_sameY(img._height);
+ }
+
+ //! Test if image height is equal to specified value.
+ bool is_sameY(const CImgDisplay& disp) const {
+ return is_sameY(disp._height);
+ }
+
+ //! Test if image depth is equal to specified value.
+ bool is_sameZ(const unsigned int size_z) const {
+ return _depth==size_z;
+ }
+
+ //! Test if image depth is equal to specified value.
+ template<typename t>
+ bool is_sameZ(const CImg<t>& img) const {
+ return is_sameZ(img._depth);
+ }
+
+ //! Test if image spectrum is equal to specified value.
+ bool is_sameC(const unsigned int size_c) const {
+ return _spectrum==size_c;
+ }
+
+ //! Test if image spectrum is equal to specified value.
+ template<typename t>
+ bool is_sameC(const CImg<t>& img) const {
+ return is_sameC(img._spectrum);
+ }
+
+ //! Test if image width and height are equal to specified values.
+ /**
+ Test if is_sameX(unsigned int) const and is_sameY(unsigned int) const are both verified.
+ **/
+ bool is_sameXY(const unsigned int size_x, const unsigned int size_y) const {
+ return _width==size_x && _height==size_y;
+ }
+
+ //! Test if image width and height are the same as that of another image.
+ /**
+ Test if is_sameX(const CImg<t>&) const and is_sameY(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameXY(const CImg<t>& img) const {
+ return is_sameXY(img._width,img._height);
+ }
+
+ //! Test if image width and height are the same as that of an existing display window.
+ /**
+ Test if is_sameX(const CImgDisplay&) const and is_sameY(const CImgDisplay&) const are both verified.
+ **/
+ bool is_sameXY(const CImgDisplay& disp) const {
+ return is_sameXY(disp._width,disp._height);
+ }
+
+ //! Test if image width and depth are equal to specified values.
+ /**
+ Test if is_sameX(unsigned int) const and is_sameZ(unsigned int) const are both verified.
+ **/
+ bool is_sameXZ(const unsigned int size_x, const unsigned int size_z) const {
+ return _width==size_x && _depth==size_z;
+ }
+
+ //! Test if image width and depth are the same as that of another image.
+ /**
+ Test if is_sameX(const CImg<t>&) const and is_sameZ(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameXZ(const CImg<t>& img) const {
+ return is_sameXZ(img._width,img._depth);
+ }
+
+ //! Test if image width and spectrum are equal to specified values.
+ /**
+ Test if is_sameX(unsigned int) const and is_sameC(unsigned int) const are both verified.
+ **/
+ bool is_sameXC(const unsigned int size_x, const unsigned int size_c) const {
+ return _width==size_x && _spectrum==size_c;
+ }
+
+ //! Test if image width and spectrum are the same as that of another image.
+ /**
+ Test if is_sameX(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameXC(const CImg<t>& img) const {
+ return is_sameXC(img._width,img._spectrum);
+ }
+
+ //! Test if image height and depth are equal to specified values.
+ /**
+ Test if is_sameY(unsigned int) const and is_sameZ(unsigned int) const are both verified.
+ **/
+ bool is_sameYZ(const unsigned int size_y, const unsigned int size_z) const {
+ return _height==size_y && _depth==size_z;
+ }
+
+ //! Test if image height and depth are the same as that of another image.
+ /**
+ Test if is_sameY(const CImg<t>&) const and is_sameZ(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameYZ(const CImg<t>& img) const {
+ return is_sameYZ(img._height,img._depth);
+ }
+
+ //! Test if image height and spectrum are equal to specified values.
+ /**
+ Test if is_sameY(unsigned int) const and is_sameC(unsigned int) const are both verified.
+ **/
+ bool is_sameYC(const unsigned int size_y, const unsigned int size_c) const {
+ return _height==size_y && _spectrum==size_c;
+ }
+
+ //! Test if image height and spectrum are the same as that of another image.
+ /**
+ Test if is_sameY(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameYC(const CImg<t>& img) const {
+ return is_sameYC(img._height,img._spectrum);
+ }
+
+ //! Test if image depth and spectrum are equal to specified values.
+ /**
+ Test if is_sameZ(unsigned int) const and is_sameC(unsigned int) const are both verified.
+ **/
+ bool is_sameZC(const unsigned int size_z, const unsigned int size_c) const {
+ return _depth==size_z && _spectrum==size_c;
+ }
+
+ //! Test if image depth and spectrum are the same as that of another image.
+ /**
+ Test if is_sameZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameZC(const CImg<t>& img) const {
+ return is_sameZC(img._depth,img._spectrum);
+ }
+
+ //! Test if image width, height and depth are equal to specified values.
+ /**
+ Test if is_sameXY(unsigned int,unsigned int) const and is_sameZ(unsigned int) const are both verified.
+ **/
+ bool is_sameXYZ(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z) const {
+ return is_sameXY(size_x,size_y) && _depth==size_z;
+ }
+
+ //! Test if image width, height and depth are the same as that of another image.
+ /**
+ Test if is_sameXY(const CImg<t>&) const and is_sameZ(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameXYZ(const CImg<t>& img) const {
+ return is_sameXYZ(img._width,img._height,img._depth);
+ }
+
+ //! Test if image width, height and spectrum are equal to specified values.
+ /**
+ Test if is_sameXY(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified.
+ **/
+ bool is_sameXYC(const unsigned int size_x, const unsigned int size_y, const unsigned int size_c) const {
+ return is_sameXY(size_x,size_y) && _spectrum==size_c;
+ }
+
+ //! Test if image width, height and spectrum are the same as that of another image.
+ /**
+ Test if is_sameXY(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameXYC(const CImg<t>& img) const {
+ return is_sameXYC(img._width,img._height,img._spectrum);
+ }
+
+ //! Test if image width, depth and spectrum are equal to specified values.
+ /**
+ Test if is_sameXZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified.
+ **/
+ bool is_sameXZC(const unsigned int size_x, const unsigned int size_z, const unsigned int size_c) const {
+ return is_sameXZ(size_x,size_z) && _spectrum==size_c;
+ }
+
+ //! Test if image width, depth and spectrum are the same as that of another image.
+ /**
+ Test if is_sameXZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameXZC(const CImg<t>& img) const {
+ return is_sameXZC(img._width,img._depth,img._spectrum);
+ }
+
+ //! Test if image height, depth and spectrum are equal to specified values.
+ /**
+ Test if is_sameYZ(unsigned int,unsigned int) const and is_sameC(unsigned int) const are both verified.
+ **/
+ bool is_sameYZC(const unsigned int size_y, const unsigned int size_z, const unsigned int size_c) const {
+ return is_sameYZ(size_y,size_z) && _spectrum==size_c;
+ }
+
+ //! Test if image height, depth and spectrum are the same as that of another image.
+ /**
+ Test if is_sameYZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameYZC(const CImg<t>& img) const {
+ return is_sameYZC(img._height,img._depth,img._spectrum);
+ }
+
+ //! Test if image width, height, depth and spectrum are equal to specified values.
+ /**
+ Test if is_sameXYZ(unsigned int,unsigned int,unsigned int) const and is_sameC(unsigned int) const are both
+ verified.
+ **/
+ bool is_sameXYZC(const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c) const {
+ return is_sameXYZ(size_x,size_y,size_z) && _spectrum==size_c;
+ }
+
+ //! Test if image width, height, depth and spectrum are the same as that of another image.
+ /**
+ Test if is_sameXYZ(const CImg<t>&) const and is_sameC(const CImg<t>&) const are both verified.
+ **/
+ template<typename t>
+ bool is_sameXYZC(const CImg<t>& img) const {
+ return is_sameXYZC(img._width,img._height,img._depth,img._spectrum);
+ }
+
+ //! Test if specified coordinates are inside image bounds.
+ /**
+ Return \c true if pixel located at (\c x,\c y,\c z,\c c) is inside bounds of the image instance,
+ and \c false otherwise.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note
+ - Return \c true only if all these conditions are verified:
+ - The image instance is \e not empty.
+ - <tt>0<=x<=\ref width() - 1</tt>.
+ - <tt>0<=y<=\ref height() - 1</tt>.
+ - <tt>0<=z<=\ref depth() - 1</tt>.
+ - <tt>0<=c<=\ref spectrum() - 1</tt>.
+ **/
+ bool containsXYZC(const int x, const int y=0, const int z=0, const int c=0) const {
+ return !is_empty() && x>=0 && x<width() && y>=0 && y<height() && z>=0 && z<depth() && c>=0 && c<spectrum();
+ }
+
+ //! Test if pixel value is inside image bounds and get its X,Y,Z and C-coordinates.
+ /**
+ Return \c true, if specified reference refers to a pixel value inside bounds of the image instance,
+ and \c false otherwise.
+ \param pixel Reference to pixel value to test.
+ \param[out] x X-coordinate of the pixel value, if test succeeds.
+ \param[out] y Y-coordinate of the pixel value, if test succeeds.
+ \param[out] z Z-coordinate of the pixel value, if test succeeds.
+ \param[out] c C-coordinate of the pixel value, if test succeeds.
+ \note
+ - Useful to convert an offset to a buffer value into pixel value coordinates:
+ \code
+ const CImg<float> img(100,100,1,3); // Construct a 100x100 RGB color image.
+ const unsigned long offset = 1249; // Offset to the pixel (49,12,0,0).
+ unsigned int x,y,z,c;
+ if (img.contains(img[offset],x,y,z,c)) { // Convert offset to (x,y,z,c) coordinates.
+ std::printf("Offset %u refers to pixel located at (%u,%u,%u,%u).\n",
+ offset,x,y,z,c);
+ }
+ \endcode
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& x, t& y, t& z, t& c) const {
+ const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum;
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false;
+ ulongT off = (ulongT)(ppixel - _data);
+ const ulongT nc = off/whd;
+ off%=whd;
+ const ulongT nz = off/wh;
+ off%=wh;
+ const ulongT ny = off/_width, nx = off%_width;
+ x = (t)nx; y = (t)ny; z = (t)nz; c = (t)nc;
+ return true;
+ }
+
+ //! Test if pixel value is inside image bounds and get its X,Y and Z-coordinates.
+ /**
+ Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X,Y and Z-coordinates are set.
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& x, t& y, t& z) const {
+ const ulongT wh = (ulongT)_width*_height, whd = wh*_depth, siz = whd*_spectrum;
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false;
+ ulongT off = ((ulongT)(ppixel - _data))%whd;
+ const ulongT nz = off/wh;
+ off%=wh;
+ const ulongT ny = off/_width, nx = off%_width;
+ x = (t)nx; y = (t)ny; z = (t)nz;
+ return true;
+ }
+
+ //! Test if pixel value is inside image bounds and get its X and Y-coordinates.
+ /**
+ Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X and Y-coordinates are set.
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& x, t& y) const {
+ const ulongT wh = (ulongT)_width*_height, siz = wh*_depth*_spectrum;
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<_data || ppixel>=_data + siz) return false;
+ ulongT off = ((unsigned int)(ppixel - _data))%wh;
+ const ulongT ny = off/_width, nx = off%_width;
+ x = (t)nx; y = (t)ny;
+ return true;
+ }
+
+ //! Test if pixel value is inside image bounds and get its X-coordinate.
+ /**
+ Similar to contains(const T&,t&,t&,t&,t&) const, except that only the X-coordinate is set.
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& x) const {
+ const T *const ppixel = &pixel;
+ if (is_empty() || ppixel<_data || ppixel>=_data + size()) return false;
+ x = (t)(((ulongT)(ppixel - _data))%_width);
+ return true;
+ }
+
+ //! Test if pixel value is inside image bounds.
+ /**
+ Similar to contains(const T&,t&,t&,t&,t&) const, except that no pixel coordinates are set.
+ **/
+ bool contains(const T& pixel) const {
+ const T *const ppixel = &pixel;
+ return !is_empty() && ppixel>=_data && ppixel<_data + size();
+ }
+
+ //! Test if pixel buffers of instance and input images overlap.
+ /**
+ Return \c true, if pixel buffers attached to image instance and input image \c img overlap,
+ and \c false otherwise.
+ \param img Input image to compare with.
+ \note
+ - Buffer overlapping may happen when manipulating \e shared images.
+ - If two image buffers overlap, operating on one of the image will probably modify the other one.
+ - Most of the time, \c CImg<T> instances are \e non-shared and do not overlap between each others.
+ \par Example
+ \code
+ const CImg<float>
+ img1("reference.jpg"), // Load RGB-color image.
+ img2 = img1.get_shared_channel(1); // Get shared version of the green channel.
+ if (img1.is_overlapped(img2)) { // Test succeeds, 'img1' and 'img2' overlaps.
+ std::printf("Buffers overlap!\n");
+ }
+ \endcode
+ **/
+ template<typename t>
+ bool is_overlapped(const CImg<t>& img) const {
+ const ulongT csiz = size(), isiz = img.size();
+ return !((void*)(_data + csiz)<=(void*)img._data || (void*)_data>=(void*)(img._data + isiz));
+ }
+
+ //! Test if the set {\c *this,\c primitives,\c colors,\c opacities} defines a valid 3d object.
+ /**
+ Return \c true is the 3d object represented by the set {\c *this,\c primitives,\c colors,\c opacities} defines a
+ valid 3d object, and \c false otherwise. The vertex coordinates are defined by the instance image.
+ \param primitives List of primitives of the 3d object.
+ \param colors List of colors of the 3d object.
+ \param opacities List (or image) of opacities of the 3d object.
+ \param full_check Tells if full checking of the 3d object must be performed.
+ \param[out] error_message C-string to contain the error message, if the test does not succeed.
+ \note
+ - Set \c full_checking to \c false to speed-up the 3d object checking. In this case, only the size of
+ each 3d object component is checked.
+ - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message.
+ **/
+ 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) cimg_sprintf(error_message,
+ "3d object (%u,%u) defines no vertices but %u primitives, "
+ "%u colors and %lu opacities",
+ _width,primitives._width,primitives._width,
+ colors._width,(unsigned long)opacities.size());
+ return false;
+ }
+ return true;
+ }
+
+ // Check consistency of vertices.
+ if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions.
+ if (error_message) cimg_sprintf(error_message,
+ "3d object (%u,%u) has invalid vertex dimensions (%u,%u,%u,%u)",
+ _width,primitives._width,_width,_height,_depth,_spectrum);
+ return false;
+ }
+ if (colors._width>primitives._width + 1) {
+ if (error_message) cimg_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) cimg_sprintf(error_message,
+ "3d object (%u,%u) defines %lu opacities",
+ _width,primitives._width,(unsigned long)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 = (unsigned int)primitive.size();
+ switch (psiz) {
+ case 1 : { // Point.
+ const unsigned int i0 = (unsigned int)primitive(0);
+ if (i0>=_width) {
+ if (error_message) cimg_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) cimg_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) cimg_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) cimg_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) cimg_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) cimg_sprintf(error_message,
+ "3d object (%u,%u) defines an invalid primitive [%u] of size %u",
+ _width,primitives._width,l,(unsigned int)psiz);
+ return false;
+ }
+ }
+
+ // Check consistency of colors.
+ cimglist_for(colors,c) {
+ const CImg<tc>& color = colors[c];
+ if (!color) {
+ if (error_message) cimg_sprintf(error_message,
+ "3d object (%u,%u) defines no color for primitive [%u]",
+ _width,primitives._width,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) cimg_sprintf(error_message,
+ "3d object (%u,%u) defines an invalid light texture (%u,%u,%u,%u)",
+ _width,primitives._width,light._width,
+ light._height,light._depth,light._spectrum);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ //! Test if image instance represents a valid serialization of a 3d object.
+ /**
+ Return \c true if the image instance represents a valid serialization of a 3d object, and \c false otherwise.
+ \param full_check Tells if full checking of the instance must be performed.
+ \param[out] error_message C-string to contain the error message, if the test does not succeed.
+ \note
+ - Set \c full_check to \c false to speed-up the 3d object checking. In this case, only the size of
+ each 3d object component is checked.
+ - Size of the string \c error_message should be at least 128-bytes long, to be able to contain the error message.
+ **/
+ 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) cimg_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) cimg_sprintf(error_message,
+ "CImg3d header not found");
+ return false;
+ }
+ const unsigned int
+ nb_points = cimg::float2uint((float)*(ptrs++)),
+ nb_primitives = cimg::float2uint((float)*(ptrs++));
+
+ // Check consistency of number of vertices / primitives.
+ if (!full_check) {
+ const ulongT minimal_size = 8UL + 3*nb_points + 6*nb_primitives;
+ if (_data + minimal_size>ptre) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) has only %lu values, while at least %lu values were expected",
+ nb_points,nb_primitives,(unsigned long)size(),(unsigned long)minimal_size);
+ return false;
+ }
+ }
+
+ // Check consistency of vertex data.
+ if (!nb_points) {
+ if (nb_primitives) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) defines no vertices but %u primitives",
+ nb_points,nb_primitives,nb_primitives);
+ return false;
+ }
+ if (ptrs!=ptre) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) is an empty object but contains %u value%s "
+ "more than expected",
+ nb_points,nb_primitives,(unsigned int)(ptre - ptrs),(ptre - ptrs)>1?"s":"");
+ return false;
+ }
+ return true;
+ }
+ if (ptrs + 3*nb_points>ptre) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) defines only %u vertices data",
+ nb_points,nb_primitives,(unsigned int)(ptre - ptrs)/3);
+ return false;
+ }
+ ptrs+=3*nb_points;
+
+ // Check consistency of primitive data.
+ if (ptrs==ptre) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) defines %u vertices but no primitive",
+ nb_points,nb_primitives,nb_points);
+ return false;
+ }
+
+ if (!full_check) return true;
+
+ for (unsigned int p = 0; p<nb_primitives; ++p) {
+ const unsigned int nb_inds = (unsigned int)*(ptrs++);
+ switch (nb_inds) {
+ case 1 : { // Point.
+ const unsigned int i0 = cimg::float2uint((float)*(ptrs++));
+ if (i0>=nb_points) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) refers to invalid vertex indice %u in point primitive [%u]",
+ nb_points,nb_primitives,i0,p);
+ return false;
+ }
+ } break;
+ case 5 : { // Sphere.
+ const unsigned int
+ i0 = cimg::float2uint((float)*(ptrs++)),
+ i1 = cimg::float2uint((float)*(ptrs++));
+ ptrs+=3;
+ if (i0>=nb_points || i1>=nb_points) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in "
+ "sphere primitive [%u]",
+ nb_points,nb_primitives,i0,i1,p);
+ return false;
+ }
+ } break;
+ case 2 : case 6 : { // Segment.
+ const unsigned int
+ i0 = cimg::float2uint((float)*(ptrs++)),
+ i1 = cimg::float2uint((float)*(ptrs++));
+ if (nb_inds==6) ptrs+=4;
+ if (i0>=nb_points || i1>=nb_points) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u) in "
+ "segment primitive [%u]",
+ nb_points,nb_primitives,i0,i1,p);
+ return false;
+ }
+ } break;
+ case 3 : case 9 : { // Triangle.
+ const unsigned int
+ i0 = cimg::float2uint((float)*(ptrs++)),
+ i1 = cimg::float2uint((float)*(ptrs++)),
+ i2 = cimg::float2uint((float)*(ptrs++));
+ if (nb_inds==9) ptrs+=6;
+ if (i0>=nb_points || i1>=nb_points || i2>=nb_points) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) refers to invalid vertex indices (%u,%u,%u) in "
+ "triangle primitive [%u]",
+ nb_points,nb_primitives,i0,i1,i2,p);
+ return false;
+ }
+ } break;
+ case 4 : case 12 : { // Quadrangle.
+ const unsigned int
+ i0 = cimg::float2uint((float)*(ptrs++)),
+ i1 = cimg::float2uint((float)*(ptrs++)),
+ i2 = cimg::float2uint((float)*(ptrs++)),
+ i3 = cimg::float2uint((float)*(ptrs++));
+ if (nb_inds==12) ptrs+=8;
+ if (i0>=nb_points || i1>=nb_points || i2>=nb_points || i3>=nb_points) {
+ if (error_message) cimg_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) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) defines an invalid primitive [%u] of size %u",
+ nb_points,nb_primitives,p,nb_inds);
+ return false;
+ }
+ if (ptrs>ptre) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) has incomplete primitive data for primitive [%u], "
+ "%u values missing",
+ nb_points,nb_primitives,p,(unsigned int)(ptrs - ptre));
+ return false;
+ }
+ }
+
+ // Check consistency of color data.
+ if (ptrs==ptre) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) defines no color/texture data",
+ nb_points,nb_primitives);
+ return false;
+ }
+ for (unsigned int c = 0; c<nb_primitives; ++c) {
+ if (*(ptrs++)!=(T)-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) cimg_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) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) has incomplete color/texture data for primitive [%u], "
+ "%u values missing",
+ nb_points,nb_primitives,c,(unsigned int)(ptrs - ptre));
+ return false;
+ }
+ }
+
+ // Check consistency of opacity data.
+ if (ptrs==ptre) {
+ if (error_message) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) defines no opacity data",
+ nb_points,nb_primitives);
+ return false;
+ }
+ for (unsigned int o = 0; o<nb_primitives; ++o) {
+ if (*(ptrs++)==(T)-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) cimg_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) cimg_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) cimg_sprintf(error_message,
+ "CImg3d (%u,%u) contains %u value%s more than expected",
+ nb_points,nb_primitives,(unsigned int)(ptre - ptrs),(ptre - ptrs)>1?"s":"");
+ return false;
+ }
+ return true;
+ }
+
+ static bool _is_CImg3d(const T val, const char c) {
+ return val>=(T)c && val<(T)(c + 1);
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Mathematical Functions
+ //@{
+ //-------------------------------------
+
+ // Define the math formula parser/compiler and expression evaluator.
+ struct _cimg_math_parser {
+ CImg<doubleT> mem;
+ CImg<intT> memtype;
+ CImgList<ulongT> _code, &code, code_init, code_end;
+ CImg<ulongT> opcode;
+ const CImg<ulongT> *p_code_end, *p_code;
+ const CImg<ulongT> *const p_break;
+
+ CImg<charT> expr, pexpr;
+ const CImg<T>& imgin;
+ const CImgList<T>& listin;
+ CImg<T> &imgout;
+ CImgList<T>& listout;
+
+ CImg<doubleT> _img_stats, &img_stats, constcache_vals;
+ CImgList<doubleT> _list_stats, &list_stats, _list_median, &list_median;
+ CImg<uintT> mem_img_stats, constcache_inds;
+
+ CImg<uintT> level, variable_pos, reserved_label;
+ CImgList<charT> variable_def, macro_def, macro_body;
+ CImgList<boolT> macro_body_is_string;
+ char *user_macro;
+
+ unsigned int mempos, mem_img_median, debug_indent, result_dim, break_type, constcache_size;
+ bool is_parallelizable, is_fill, need_input_copy;
+ double *result;
+ const char *const calling_function, *s_op, *ss_op;
+ typedef double (*mp_func)(_cimg_math_parser&);
+
+#define _cimg_mp_is_constant(arg) (memtype[arg]==1) // Is constant value?
+#define _cimg_mp_is_scalar(arg) (memtype[arg]<2) // Is scalar value?
+#define _cimg_mp_is_comp(arg) (!memtype[arg]) // Is computation value?
+#define _cimg_mp_is_variable(arg) (memtype[arg]==-1) // Is scalar variable?
+#define _cimg_mp_is_vector(arg) (memtype[arg]>1) // Is vector?
+#define _cimg_mp_vector_size(arg) (_cimg_mp_is_scalar(arg)?0U:(unsigned int)memtype[arg] - 1) // Vector size
+#define _cimg_mp_calling_function calling_function_s()._data
+#define _cimg_mp_op(s) s_op = s; ss_op = ss
+#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char)
+#define _cimg_mp_check_constant(arg,n_arg,mode) check_constant(arg,n_arg,mode,ss,se,saved_char)
+#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char)
+#define _cimg_mp_check_vector0(dim) check_vector0(dim,ss,se,saved_char)
+#define _cimg_mp_check_list(is_out) check_list(is_out,ss,se,saved_char)
+#define _cimg_mp_defunc(mp) (*(mp_func)(*(mp).opcode))(mp)
+#define _cimg_mp_return(x) { *se = saved_char; s_op = previous_s_op; ss_op = previous_ss_op; return x; }
+#define _cimg_mp_constant(val) _cimg_mp_return(constant((double)(val)))
+#define _cimg_mp_scalar0(op) _cimg_mp_return(scalar0(op))
+#define _cimg_mp_scalar1(op,i1) _cimg_mp_return(scalar1(op,i1))
+#define _cimg_mp_scalar2(op,i1,i2) _cimg_mp_return(scalar2(op,i1,i2))
+#define _cimg_mp_scalar3(op,i1,i2,i3) _cimg_mp_return(scalar3(op,i1,i2,i3))
+#define _cimg_mp_scalar4(op,i1,i2,i3,i4) _cimg_mp_return(scalar4(op,i1,i2,i3,i4))
+#define _cimg_mp_scalar5(op,i1,i2,i3,i4,i5) _cimg_mp_return(scalar5(op,i1,i2,i3,i4,i5))
+#define _cimg_mp_scalar6(op,i1,i2,i3,i4,i5,i6) _cimg_mp_return(scalar6(op,i1,i2,i3,i4,i5,i6))
+#define _cimg_mp_scalar7(op,i1,i2,i3,i4,i5,i6,i7) _cimg_mp_return(scalar7(op,i1,i2,i3,i4,i5,i6,i7))
+#define _cimg_mp_vector1_v(op,i1) _cimg_mp_return(vector1_v(op,i1))
+#define _cimg_mp_vector2_sv(op,i1,i2) _cimg_mp_return(vector2_sv(op,i1,i2))
+#define _cimg_mp_vector2_vs(op,i1,i2) _cimg_mp_return(vector2_vs(op,i1,i2))
+#define _cimg_mp_vector2_vv(op,i1,i2) _cimg_mp_return(vector2_vv(op,i1,i2))
+#define _cimg_mp_vector3_vss(op,i1,i2,i3) _cimg_mp_return(vector3_vss(op,i1,i2,i3))
+
+ // Constructors.
+ _cimg_math_parser(const char *const expression, const char *const funcname=0,
+ const CImg<T>& img_input=CImg<T>::const_empty(), CImg<T> *const img_output=0,
+ const CImgList<T> *const list_input=0, CImgList<T> *const list_output=0,
+ const bool _is_fill=false):
+ code(_code),p_break((CImg<ulongT>*)(-2*sizeof(CImg<ulongT>))),
+ imgin(img_input),listin(list_input?*list_input:CImgList<T>::const_empty()),
+ imgout(img_output?*img_output:CImg<T>::empty()),listout(list_output?*list_output:CImgList<T>::empty()),
+ img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),user_macro(0),
+ mem_img_median(~0U),debug_indent(0),result_dim(0),break_type(0),constcache_size(0),
+ is_parallelizable(true),is_fill(_is_fill),need_input_copy(false),
+ calling_function(funcname?funcname:"cimg_math_parser") {
+ if (!expression || !*expression)
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Empty expression.",
+ pixel_type(),_cimg_mp_calling_function);
+ const char *_expression = expression;
+ while (*_expression && ((signed char)*_expression<=' ' || *_expression==';')) ++_expression;
+ CImg<charT>::string(_expression).move_to(expr);
+ char *ps = &expr.back() - 1;
+ while (ps>expr._data && ((signed char)*ps<=' ' || *ps==';')) --ps;
+ *(++ps) = 0; expr._width = (unsigned int)(ps - expr._data + 1);
+
+ // Ease the retrieval of previous non-space characters afterwards.
+ pexpr.assign(expr._width);
+ char c, *pe = pexpr._data;
+ for (ps = expr._data, c = ' '; *ps; ++ps) {
+ if ((signed char)*ps>' ') c = *ps; else *ps = ' ';
+ *(pe++) = c;
+ }
+ *pe = 0;
+ level = get_level(expr);
+
+ // Init constant values.
+#define _cimg_mp_interpolation (reserved_label[29]!=~0U?reserved_label[29]:0)
+#define _cimg_mp_boundary (reserved_label[30]!=~0U?reserved_label[30]:0)
+#define _cimg_mp_slot_nan 29
+#define _cimg_mp_slot_x 30
+#define _cimg_mp_slot_y 31
+#define _cimg_mp_slot_z 32
+#define _cimg_mp_slot_c 33
+
+ mem.assign(96);
+ for (unsigned int i = 0; i<=10; ++i) mem[i] = (double)i; // mem[0-10] = 0...10
+ for (unsigned int i = 1; i<=5; ++i) mem[i + 10] = -(double)i; // mem[11-15] = -1...-5
+ mem[16] = 0.5;
+ mem[17] = 0; // thread_id
+ mem[18] = (double)imgin._width; // w
+ mem[19] = (double)imgin._height; // h
+ mem[20] = (double)imgin._depth; // d
+ mem[21] = (double)imgin._spectrum; // s
+ mem[22] = (double)imgin._is_shared; // r
+ mem[23] = (double)imgin._width*imgin._height; // wh
+ mem[24] = (double)imgin._width*imgin._height*imgin._depth; // whd
+ mem[25] = (double)imgin._width*imgin._height*imgin._depth*imgin._spectrum; // whds
+ mem[26] = (double)listin._width; // l
+ mem[27] = std::exp(1.0); // e
+ mem[28] = cimg::PI; // pi
+ mem[_cimg_mp_slot_nan] = cimg::type<double>::nan(); // nan
+
+ // Set value property :
+ // { -2 = other | -1 = variable | 0 = computation value |
+ // 1 = compile-time constant | N>1 = constant ptr to vector[N-1] }.
+ memtype.assign(mem._width,1,1,1,0);
+ for (unsigned int i = 0; i<_cimg_mp_slot_x; ++i) memtype[i] = 1;
+ memtype[17] = 0;
+ memtype[_cimg_mp_slot_x] = memtype[_cimg_mp_slot_y] = memtype[_cimg_mp_slot_z] = memtype[_cimg_mp_slot_c] = -2;
+ mempos = _cimg_mp_slot_c + 1;
+ variable_pos.assign(8);
+
+ reserved_label.assign(128,1,1,1,~0U);
+ // reserved_label[4-28] are used to store these two-char variables:
+ // [0] = wh, [1] = whd, [2] = whds, [3] = pi, [4] = im, [5] = iM, [6] = ia, [7] = iv,
+ // [8] = is, [9] = ip, [10] = ic, [11] = xm, [12] = ym, [13] = zm, [14] = cm, [15] = xM,
+ // [16] = yM, [17] = zM, [18]=cM, [19]=i0...[28]=i9, [29] = interpolation, [30] = boundary
+
+ // Compile expression into a serie of opcodes.
+ s_op = ""; ss_op = expr._data;
+ const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0);
+ if (!_cimg_mp_is_constant(ind_result)) {
+ if (_cimg_mp_is_vector(ind_result))
+ CImg<doubleT>(&mem[ind_result] + 1,_cimg_mp_vector_size(ind_result),1,1,1,true).
+ fill(cimg::type<double>::nan());
+ else mem[ind_result] = cimg::type<double>::nan();
+ }
+
+ // Free resources used for compiling expression and prepare evaluation.
+ result_dim = _cimg_mp_vector_size(ind_result);
+ mem.resize(mempos,1,1,1,-1);
+ result = mem._data + ind_result;
+ memtype.assign();
+ constcache_vals.assign();
+ constcache_inds.assign();
+ level.assign();
+ variable_pos.assign();
+ reserved_label.assign();
+ expr.assign();
+ pexpr.assign();
+ opcode.assign();
+ opcode._is_shared = true;
+
+ // Execute init() bloc if any specified.
+ if (code_init) {
+ mem[_cimg_mp_slot_x] = mem[_cimg_mp_slot_y] = mem[_cimg_mp_slot_z] = mem[_cimg_mp_slot_c] = 0;
+ p_code_end = code_init.end();
+ for (p_code = code_init; p_code<p_code_end; ++p_code) {
+ opcode._data = p_code->_data;
+ const ulongT target = opcode[1];
+ mem[target] = _cimg_mp_defunc(*this);
+ }
+ }
+ p_code_end = code.end();
+ }
+
+ _cimg_math_parser():
+ code(_code),p_code_end(0),p_break((CImg<ulongT>*)(-2*sizeof(CImg<ulongT>))),
+ imgin(CImg<T>::const_empty()),listin(CImgList<T>::const_empty()),
+ imgout(CImg<T>::empty()),listout(CImgList<T>::empty()),
+ img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),debug_indent(0),
+ result_dim(0),break_type(0),constcache_size(0),is_parallelizable(true),is_fill(false),need_input_copy(false),
+ calling_function(0) {
+ mem.assign(1 + _cimg_mp_slot_c,1,1,1,0); // Allow to skip 'is_empty?' test in operator()()
+ result = mem._data;
+ }
+
+ _cimg_math_parser(const _cimg_math_parser& mp):
+ mem(mp.mem),code(mp.code),p_code_end(mp.p_code_end),p_break(mp.p_break),
+ imgin(mp.imgin),listin(mp.listin),imgout(mp.imgout),listout(mp.listout),img_stats(mp.img_stats),
+ list_stats(mp.list_stats),list_median(mp.list_median),debug_indent(0),result_dim(mp.result_dim),
+ break_type(0),constcache_size(0),is_parallelizable(mp.is_parallelizable),is_fill(mp.is_fill),
+ need_input_copy(mp.need_input_copy), result(mem._data + (mp.result - mp.mem._data)),calling_function(0) {
+#ifdef cimg_use_openmp
+ mem[17] = omp_get_thread_num();
+#endif
+ opcode.assign();
+ opcode._is_shared = true;
+ }
+
+ // Count parentheses/brackets level of each character of the expression.
+ CImg<uintT> get_level(CImg<charT>& expr) const {
+ bool is_escaped = false, next_is_escaped = false;
+ unsigned int mode = 0, next_mode = 0; // { 0=normal | 1=char-string | 2=vector-string
+ CImg<uintT> res(expr._width - 1);
+ unsigned int *pd = res._data;
+ int level = 0;
+ for (const char *ps = expr._data; *ps && level>=0; ++ps) {
+ if (!is_escaped && !next_is_escaped && *ps=='\\') next_is_escaped = true;
+ if (!is_escaped && *ps=='\'') { // Non-escaped character
+ if (!mode && ps>expr._data && *(ps - 1)=='[') next_mode = mode = 2; // Start vector-string
+ else if (mode==2 && *(ps + 1)==']') next_mode = !mode; // End vector-string
+ else if (mode<2) next_mode = mode?(mode = 0):1; // Start/end char-string
+ }
+ *(pd++) = (unsigned int)(mode>=1 || is_escaped?level + (mode==1):
+ *ps=='(' || *ps=='['?level++:
+ *ps==')' || *ps==']'?--level:
+ level);
+ mode = next_mode;
+ is_escaped = next_is_escaped;
+ next_is_escaped = false;
+ }
+ if (mode) {
+ cimg::strellipsize(expr,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Unterminated string literal, in expression '%s'.",
+ pixel_type(),_cimg_mp_calling_function,
+ expr._data);
+ }
+ if (level) {
+ cimg::strellipsize(expr,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Unbalanced parentheses/brackets, in expression '%s'.",
+ pixel_type(),_cimg_mp_calling_function,
+ expr._data);
+ }
+ return res;
+ }
+
+ // Tell for each character of an expression if it is inside a string or not.
+ CImg<boolT> is_inside_string(CImg<charT>& expr) const {
+ bool is_escaped = false, next_is_escaped = false;
+ unsigned int mode = 0, next_mode = 0; // { 0=normal | 1=char-string | 2=vector-string
+ CImg<boolT> res = CImg<charT>::string(expr);
+ bool *pd = res._data;
+ for (const char *ps = expr._data; *ps; ++ps) {
+ if (!next_is_escaped && *ps=='\\') next_is_escaped = true;
+ if (!is_escaped && *ps=='\'') { // Non-escaped character
+ if (!mode && ps>expr._data && *(ps - 1)=='[') next_mode = mode = 2; // Start vector-string
+ else if (mode==2 && *(ps + 1)==']') next_mode = !mode; // End vector-string
+ else if (mode<2) next_mode = mode?(mode = 0):1; // Start/end char-string
+ }
+ *(pd++) = mode>=1 || is_escaped;
+ mode = next_mode;
+ is_escaped = next_is_escaped;
+ next_is_escaped = false;
+ }
+ return res;
+ }
+
+ // Compilation procedure.
+ unsigned int compile(char *ss, char *se, const unsigned int depth, unsigned int *const p_ref) {
+ if (depth>256) {
+ cimg::strellipsize(expr,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Call stack overflow (infinite recursion?), "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,
+ (ss - 4)>expr._data?"...":"",
+ (ss - 4)>expr._data?ss - 4:expr._data,
+ se<&expr.back()?"...":"");
+ }
+ char c1, c2, c3, c4;
+
+ // Simplify expression when possible.
+ do {
+ c2 = 0;
+ if (ss<se) {
+ while (*ss && ((signed char)*ss<=' ' || *ss==';')) ++ss;
+ while (se>ss && ((signed char)(c1 = *(se - 1))<=' ' || c1==';')) --se;
+ }
+ while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) {
+ ++ss; --se; c2 = 1;
+ }
+ } while (c2 && ss<se);
+
+ if (se<=ss || !*ss) {
+ cimg::strellipsize(expr,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s%s Missing %s, in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",
+ *s_op=='F'?"argument":"item",
+ (ss_op - 4)>expr._data?"...":"",
+ (ss_op - 4)>expr._data?ss_op - 4:expr._data,
+ ss_op + std::strlen(ss_op)<&expr.back()?"...":"");
+ }
+
+ const char *const previous_s_op = s_op, *const previous_ss_op = ss_op;
+ const unsigned int depth1 = depth + 1;
+ unsigned int pos, p1, p2, p3, arg1, arg2, arg3, arg4, arg5, arg6;
+ char
+ *const se1 = se - 1, *const se2 = se - 2, *const se3 = se - 3,
+ *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 ss8 = ss + 8,
+ *s, *ps, *ns, *s0, *s1, *s2, *s3, sep = 0, end = 0;
+ double val, val1, val2;
+ mp_func op;
+
+ // 'p_ref' is a 'unsigned int[7]' used to return a reference to an image or vector value
+ // linked to the returned memory slot (reference that cannot be determined at compile time).
+ // p_ref[0] can be { 0 = scalar (unlinked) | 1 = vector value | 2 = image value (offset) |
+ // 3 = image value (coordinates) | 4 = image value as a vector (offsets) |
+ // 5 = image value as a vector (coordinates) }.
+ // Depending on p_ref[0], the remaining p_ref[k] have the following meaning:
+ // When p_ref[0]==0, p_ref is actually unlinked.
+ // When p_ref[0]==1, p_ref = [ 1, vector_ind, offset ].
+ // When p_ref[0]==2, p_ref = [ 2, image_ind (or ~0U), is_relative, offset ].
+ // When p_ref[0]==3, p_ref = [ 3, image_ind (or ~0U), is_relative, x, y, z, c ].
+ // When p_ref[0]==4, p_ref = [ 4, image_ind (or ~0U), is_relative, offset ].
+ // When p_ref[0]==5, p_ref = [ 5, image_ind (or ~0U), is_relative, x, y, z ].
+ if (p_ref) { *p_ref = 0; p_ref[1] = p_ref[2] = p_ref[3] = p_ref[4] = p_ref[5] = p_ref[6] = ~0U; }
+
+ const char saved_char = *se; *se = 0;
+ const unsigned int clevel = level[ss - expr._data], clevel1 = clevel + 1;
+ bool is_sth, is_relative;
+ CImg<uintT> ref;
+ CImgList<ulongT> _opcode;
+ CImg<charT> variable_name;
+
+ // Look for a single value or a pre-defined variable.
+ int nb = cimg_sscanf(ss,"%lf%c%c",&val,&(sep=0),&(end=0));
+
+#if cimg_OS==2
+ // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able
+ // to read those particular values.
+ if (!nb && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) {
+ is_sth = true;
+ s = ss;
+ if (*s=='+') ++s; else if (*s=='-') { ++s; is_sth = false; }
+ if (!cimg::strcasecmp(s,"inf")) { val = cimg::type<double>::inf(); nb = 1; }
+ else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type<double>::nan(); nb = 1; }
+ if (nb==1 && !is_sth) val = -val;
+ }
+#endif
+ if (nb==1) _cimg_mp_constant(val);
+ if (nb==2 && sep=='%') _cimg_mp_constant(val/100);
+
+ if (ss1==se) switch (*ss) { // One-char reserved variable
+ case 'c' : _cimg_mp_return(reserved_label['c']!=~0U?reserved_label['c']:_cimg_mp_slot_c);
+ case 'd' : _cimg_mp_return(reserved_label['d']!=~0U?reserved_label['d']:20);
+ case 'e' : _cimg_mp_return(reserved_label['e']!=~0U?reserved_label['e']:27);
+ case 'h' : _cimg_mp_return(reserved_label['h']!=~0U?reserved_label['h']:19);
+ case 'l' : _cimg_mp_return(reserved_label['l']!=~0U?reserved_label['l']:26);
+ case 'r' : _cimg_mp_return(reserved_label['r']!=~0U?reserved_label['r']:22);
+ case 's' : _cimg_mp_return(reserved_label['s']!=~0U?reserved_label['s']:21);
+ case 't' : _cimg_mp_return(reserved_label['t']!=~0U?reserved_label['t']:17);
+ case 'w' : _cimg_mp_return(reserved_label['w']!=~0U?reserved_label['w']:18);
+ case 'x' : _cimg_mp_return(reserved_label['x']!=~0U?reserved_label['x']:_cimg_mp_slot_x);
+ case 'y' : _cimg_mp_return(reserved_label['y']!=~0U?reserved_label['y']:_cimg_mp_slot_y);
+ case 'z' : _cimg_mp_return(reserved_label['z']!=~0U?reserved_label['z']:_cimg_mp_slot_z);
+ case 'u' :
+ if (reserved_label['u']!=~0U) _cimg_mp_return(reserved_label['u']);
+ _cimg_mp_scalar2(mp_u,0,1);
+ case 'g' :
+ if (reserved_label['g']!=~0U) _cimg_mp_return(reserved_label['g']);
+ _cimg_mp_scalar0(mp_g);
+ case 'i' :
+ if (reserved_label['i']!=~0U) _cimg_mp_return(reserved_label['i']);
+ _cimg_mp_scalar0(mp_i);
+ case 'I' :
+ _cimg_mp_op("Variable 'I'");
+ if (reserved_label['I']!=~0U) _cimg_mp_return(reserved_label['I']);
+ _cimg_mp_check_vector0(imgin._spectrum);
+ need_input_copy = true;
+ pos = vector(imgin._spectrum);
+ CImg<ulongT>::vector((ulongT)mp_Joff,pos,0,0,imgin._spectrum).move_to(code);
+ _cimg_mp_return(pos);
+ case 'R' :
+ if (reserved_label['R']!=~0U) _cimg_mp_return(reserved_label['R']);
+ need_input_copy = true;
+ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,0,0);
+ case 'G' :
+ if (reserved_label['G']!=~0U) _cimg_mp_return(reserved_label['G']);
+ need_input_copy = true;
+ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,0,0);
+ case 'B' :
+ if (reserved_label['B']!=~0U) _cimg_mp_return(reserved_label['B']);
+ need_input_copy = true;
+ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,0,0);
+ case 'A' :
+ if (reserved_label['A']!=~0U) _cimg_mp_return(reserved_label['A']);
+ need_input_copy = true;
+ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,0,0);
+ }
+ else if (ss2==se) { // Two-chars reserved variable
+ arg1 = arg2 = ~0U;
+ if (*ss=='w' && *ss1=='h') // wh
+ _cimg_mp_return(reserved_label[0]!=~0U?reserved_label[0]:23);
+ if (*ss=='p' && *ss1=='i') // pi
+ _cimg_mp_return(reserved_label[3]!=~0U?reserved_label[3]:28);
+ if (*ss=='i') {
+ if (*ss1>='0' && *ss1<='9') { // i0...i9
+ pos = 19 + *ss1 - '0';
+ if (reserved_label[pos]!=~0U) _cimg_mp_return(reserved_label[pos]);
+ need_input_copy = true;
+ _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 19,0,0);
+ }
+ switch (*ss1) {
+ case 'm' : arg1 = 4; arg2 = 0; break; // im
+ case 'M' : arg1 = 5; arg2 = 1; break; // iM
+ case 'a' : arg1 = 6; arg2 = 2; break; // ia
+ case 'v' : arg1 = 7; arg2 = 3; break; // iv
+ case 's' : arg1 = 8; arg2 = 12; break; // is
+ case 'p' : arg1 = 9; arg2 = 13; break; // is
+ case 'c' : // ic
+ if (reserved_label[10]!=~0U) _cimg_mp_return(reserved_label[10]);
+ if (mem_img_median==~0U) mem_img_median = imgin?constant(imgin.median()):0;
+ _cimg_mp_return(mem_img_median);
+ break;
+ }
+ }
+ else if (*ss1=='m') switch (*ss) {
+ case 'x' : arg1 = 11; arg2 = 4; break; // xm
+ case 'y' : arg1 = 12; arg2 = 5; break; // ym
+ case 'z' : arg1 = 13; arg2 = 6; break; // zm
+ case 'c' : arg1 = 14; arg2 = 7; break; // cm
+ }
+ else if (*ss1=='M') switch (*ss) {
+ case 'x' : arg1 = 15; arg2 = 8; break; // xM
+ case 'y' : arg1 = 16; arg2 = 9; break; // yM
+ case 'z' : arg1 = 17; arg2 = 10; break; // zM
+ case 'c' : arg1 = 18; arg2 = 11; break; // cM
+ }
+ if (arg1!=~0U) {
+ if (reserved_label[arg1]!=~0U) _cimg_mp_return(reserved_label[arg1]);
+ if (!img_stats) {
+ img_stats.assign(1,14,1,1,0).fill(imgin.get_stats(),false);
+ mem_img_stats.assign(1,14,1,1,~0U);
+ }
+ if (mem_img_stats[arg2]==~0U) mem_img_stats[arg2] = constant(img_stats[arg2]);
+ _cimg_mp_return(mem_img_stats[arg2]);
+ }
+ } else if (ss3==se) { // Three-chars reserved variable
+ if (*ss=='w' && *ss1=='h' && *ss2=='d') // whd
+ _cimg_mp_return(reserved_label[1]!=~0U?reserved_label[1]:24);
+ } else if (ss4==se) { // Four-chars reserved variable
+ if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s') // whds
+ _cimg_mp_return(reserved_label[2]!=~0U?reserved_label[2]:25);
+ }
+
+ pos = ~0U;
+ for (s0 = ss, s = ss1; s<se1; ++s)
+ if (*s==';' && level[s - expr._data]==clevel) { // Separator ';'
+ pos = compile(s0,s++,depth,0);
+ while (*s && ((signed char)*s<=' ' || *s==';')) ++s;
+ s0 = s;
+ }
+ if (pos!=~0U) _cimg_mp_return(compile(s0,se,depth,p_ref));
+
+ // Declare / assign variable, vector value or image value.
+ for (s = ss1, ps = ss, ns = ss2; s<se1; ++s, ++ps, ++ns)
+ if (*s=='=' && *ns!='=' && *ps!='=' && *ps!='>' && *ps!='<' && *ps!='!' &&
+ *ps!='+' && *ps!='-' && *ps!='*' && *ps!='/' && *ps!='%' &&
+ *ps!='>' && *ps!='<' && *ps!='&' && *ps!='|' && *ps!='^' &&
+ level[s - expr._data]==clevel) {
+ variable_name.assign(ss,(unsigned int)(s + 1 - ss)).back() = 0;
+ cimg::strpare(variable_name,false,true);
+ const unsigned int l_variable_name = (unsigned int)std::strlen(variable_name);
+ char *const ve1 = ss + l_variable_name - 1;
+ _cimg_mp_op("Operator '='");
+
+ // Assign image value (direct).
+ if (l_variable_name>2 && (*ss=='i' || *ss=='j' || *ss=='I' || *ss=='J') && (*ss1=='(' || *ss1=='[') &&
+ (reserved_label[*ss]==~0U || *ss1=='(' || !_cimg_mp_is_vector(reserved_label[*ss]))) {
+ is_relative = *ss=='j' || *ss=='J';
+
+ if (*ss1=='[' && *ve1==']') { // i/j/I/J[_#ind,offset] = value
+ is_parallelizable = false;
+ if (*ss2=='#') { // Index specified
+ s0 = ss3; while (s0<ve1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss3,s0++,depth1,0);
+ _cimg_mp_check_list(true);
+ } else { p1 = ~0U; s0 = ss2; }
+ arg1 = compile(s0,ve1,depth1,0); // Offset
+ _cimg_mp_check_type(arg1,0,1,0);
+ arg2 = compile(s + 1,se,depth1,0); // Value to assign
+ if (_cimg_mp_is_vector(arg2)) {
+ p2 = ~0U; // 'p2' must be the dimension of the vector-valued operand if any
+ if (p1==~0U) p2 = imgin._spectrum;
+ else if (_cimg_mp_is_constant(p1)) {
+ p3 = (unsigned int)cimg::mod((int)mem[p1],listin.width());
+ p2 = listin[p3]._spectrum;
+ }
+ _cimg_mp_check_vector0(p2);
+ } else p2 = 0;
+ _cimg_mp_check_type(arg2,2,*ss>='i'?1:3,p2);
+
+ if (p_ref) {
+ *p_ref = _cimg_mp_is_vector(arg2)?4:2;
+ p_ref[1] = p1;
+ p_ref[2] = (unsigned int)is_relative;
+ p_ref[3] = arg1;
+ if (_cimg_mp_is_vector(arg2))
+ set_variable_vector(arg2); // Prevent from being used in further optimization
+ else if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2;
+ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2;
+ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2;
+ }
+
+
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg2);
+ if (*ss>='i')
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff),
+ arg2,p1,arg1).move_to(code);
+ else if (_cimg_mp_is_scalar(arg2))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s),
+ arg2,p1,arg1).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v),
+ arg2,p1,arg1,_cimg_mp_vector_size(arg2)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg2);
+ if (*ss>='i')
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff),
+ arg2,arg1).move_to(code);
+ else if (_cimg_mp_is_scalar(arg2))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s),
+ arg2,arg1).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v),
+ arg2,arg1,_cimg_mp_vector_size(arg2)).move_to(code);
+ }
+ _cimg_mp_return(arg2);
+ }
+
+ if (*ss1=='(' && *ve1==')') { // i/j/I/J(_#ind,_x,_y,_z,_c) = value
+ is_parallelizable = false;
+ if (*ss2=='#') { // Index specified
+ s0 = ss3; while (s0<ve1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss3,s0++,depth1,0);
+ _cimg_mp_check_list(true);
+ } else { p1 = ~0U; s0 = ss2; }
+ arg1 = is_relative?0U:(unsigned int)_cimg_mp_slot_x;
+ arg2 = is_relative?0U:(unsigned int)_cimg_mp_slot_y;
+ arg3 = is_relative?0U:(unsigned int)_cimg_mp_slot_z;
+ arg4 = is_relative?0U:(unsigned int)_cimg_mp_slot_c;
+ arg5 = compile(s + 1,se,depth1,0); // Value to assign
+ if (s0<ve1) { // X or [ X,_Y,_Z,_C ]
+ s1 = s0; while (s1<ve1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(s0,s1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) { // Coordinates specified as a vector
+ p2 = _cimg_mp_vector_size(arg1); // Vector size
+ ++arg1;
+ if (p2>1) {
+ arg2 = arg1 + 1;
+ if (p2>2) {
+ arg3 = arg2 + 1;
+ if (p2>3) arg4 = arg3 + 1;
+ }
+ }
+ } else if (s1<ve1) { // Y
+ s2 = ++s1; while (s2<ve1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(s1,s2,depth1,0);
+ if (s2<ve1) { // Z
+ s3 = ++s2; while (s3<ve1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg3 = compile(s2,s3,depth1,0);
+ if (s3<ve1) arg4 = compile(++s3,ve1,depth1,0); // C
+ }
+ }
+ }
+
+ if (_cimg_mp_is_vector(arg5)) {
+ p2 = ~0U; // 'p2' must be the dimension of the vector-valued operand if any
+ if (p1==~0U) p2 = imgin._spectrum;
+ else if (_cimg_mp_is_constant(p1)) {
+ p3 = (unsigned int)cimg::mod((int)mem[p1],listin.width());
+ p2 = listin[p3]._spectrum;
+ }
+ _cimg_mp_check_vector0(p2);
+ } else p2 = 0;
+ _cimg_mp_check_type(arg5,2,*ss>='i'?1:3,p2);
+
+ if (p_ref) {
+ *p_ref = _cimg_mp_is_vector(arg5)?5:3;
+ p_ref[1] = p1;
+ p_ref[2] = (unsigned int)is_relative;
+ p_ref[3] = arg1;
+ p_ref[4] = arg2;
+ p_ref[5] = arg3;
+ p_ref[6] = arg4;
+ if (_cimg_mp_is_vector(arg5))
+ set_variable_vector(arg5); // Prevent from being used in further optimization
+ else if (_cimg_mp_is_comp(arg5)) memtype[arg5] = -2;
+ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2;
+ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2;
+ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2;
+ if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2;
+ if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -2;
+ }
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg5);
+ if (*ss>='i')
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc),
+ arg5,p1,arg1,arg2,arg3,arg4).move_to(code);
+ else if (_cimg_mp_is_scalar(arg5))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s),
+ arg5,p1,arg1,arg2,arg3).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v),
+ arg5,p1,arg1,arg2,arg3,_cimg_mp_vector_size(arg5)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg5);
+ if (*ss>='i')
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc),
+ arg5,arg1,arg2,arg3,arg4).move_to(code);
+ else if (_cimg_mp_is_scalar(arg5))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s),
+ arg5,arg1,arg2,arg3).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v),
+ arg5,arg1,arg2,arg3,_cimg_mp_vector_size(arg5)).move_to(code);
+ }
+ _cimg_mp_return(arg5);
+ }
+ }
+
+ // Assign vector value (direct).
+ if (l_variable_name>3 && *ve1==']' && *ss!='[') {
+ s0 = ve1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0;
+ is_sth = true; // is_valid_variable_name?
+ if (*ss>='0' && *ss<='9') is_sth = false;
+ else for (ns = ss; ns<s0; ++ns)
+ if (!is_varchar(*ns)) { is_sth = false; break; }
+ if (is_sth && s0>ss) {
+ variable_name[s0 - ss] = 0; // Remove brackets in variable name
+ arg1 = ~0U; // Vector slot
+ arg2 = compile(++s0,ve1,depth1,0); // Index
+ arg3 = compile(s + 1,se,depth1,0); // Value to assign
+ _cimg_mp_check_type(arg3,2,1,0);
+
+ if (variable_name[1]) { // Multi-char variable
+ cimglist_for(variable_def,i) if (!std::strcmp(variable_name,variable_def[i])) {
+ arg1 = variable_pos[i]; break;
+ }
+ } else arg1 = reserved_label[*variable_name]; // Single-char variable
+ if (arg1==~0U) compile(ss,s0 - 1,depth1,0); // Variable does not exist -> error
+ else { // Variable already exists
+ if (_cimg_mp_is_scalar(arg1)) compile(ss,s,depth1,0); // Variable is not a vector -> error
+ if (_cimg_mp_is_constant(arg2)) { // Constant index -> return corresponding variable slot directly
+ nb = (int)mem[arg2];
+ if (nb>=0 && nb<(int)_cimg_mp_vector_size(arg1)) {
+ arg1+=nb + 1;
+ CImg<ulongT>::vector((ulongT)mp_copy,arg1,arg3).move_to(code);
+ _cimg_mp_return(arg1);
+ }
+ compile(ss,s,depth1,0); // Out-of-bounds reference -> error
+ }
+
+ // Case of non-constant index -> return assigned value + linked reference
+ if (p_ref) {
+ *p_ref = 1;
+ p_ref[1] = arg1;
+ p_ref[2] = arg2;
+ if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2; // Prevent from being used in further optimization
+ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2;
+ }
+ CImg<ulongT>::vector((ulongT)mp_vector_set_off,arg3,arg1,(ulongT)_cimg_mp_vector_size(arg1),
+ arg2,arg3).
+ move_to(code);
+ _cimg_mp_return(arg3);
+ }
+ }
+ }
+
+ // Assign user-defined macro.
+ if (l_variable_name>2 && *ve1==')' && *ss!='(') {
+ s0 = ve1; while (s0>ss && *s0!='(') --s0;
+ is_sth = std::strncmp(variable_name,"debug(",6) &&
+ std::strncmp(variable_name,"print(",6); // is_valid_function_name?
+ if (*ss>='0' && *ss<='9') is_sth = false;
+ else for (ns = ss; ns<s0; ++ns)
+ if (!is_varchar(*ns)) { is_sth = false; break; }
+
+ if (is_sth && s0>ss) { // Looks like a valid function declaration
+ s0 = variable_name._data + (s0 - ss);
+ *s0 = 0;
+ s1 = variable_name._data + l_variable_name - 1; // Pointer to closing parenthesis
+ CImg<charT>(variable_name._data,(unsigned int)(s0 - variable_name._data + 1)).move_to(macro_def,0);
+ ++s; while (*s && (signed char)*s<=' ') ++s;
+ CImg<charT>(s,(unsigned int)(se - s + 1)).move_to(macro_body,0);
+
+ p1 = 1; // Indice of current parsed argument
+ for (s = s0 + 1; s<=s1; ++p1, s = ns + 1) { // Parse function arguments
+ if (p1>24) {
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Too much specified arguments (>24) in macro "
+ "definition '%s()', in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ while (*s && (signed char)*s<=' ') ++s;
+ if (*s==')' && p1==1) break; // Function has no arguments
+
+ s2 = s; // Start of the argument name
+ is_sth = true; // is_valid_argument_name?
+ if (*s>='0' && *s<='9') is_sth = false;
+ else for (ns = s; ns<s1 && *ns!=',' && (signed char)*ns>' '; ++ns)
+ if (!is_varchar(*ns)) { is_sth = false; break; }
+ s3 = ns; // End of the argument name
+ while (*ns && (signed char)*ns<=' ') ++ns;
+ if (!is_sth || s2==s3 || (*ns!=',' && ns!=s1)) {
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: %s name specified for argument %u when defining "
+ "macro '%s()', in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ is_sth?"Empty":"Invalid",p1,
+ variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ if (ns==s1 || *ns==',') { // New argument found
+ *s3 = 0;
+ p2 = (unsigned int)(s3 - s2); // Argument length
+ for (ps = std::strstr(macro_body[0],s2); ps; ps = std::strstr(ps,s2)) { // Replace by arg number
+ if (!((ps>macro_body[0]._data && is_varchar(*(ps - 1))) ||
+ (ps + p2<macro_body[0].end() && is_varchar(*(ps + p2))))) {
+ if (ps>macro_body[0]._data && *(ps - 1)=='#') { // Remove pre-number sign
+ *(ps - 1) = (char)p1;
+ if (ps + p2<macro_body[0].end() && *(ps + p2)=='#') { // Has pre & post number signs
+ std::memmove(ps,ps + p2 + 1,macro_body[0].end() - ps - p2 - 1);
+ macro_body[0]._width-=p2 + 1;
+ } else { // Has pre number sign only
+ std::memmove(ps,ps + p2,macro_body[0].end() - ps - p2);
+ macro_body[0]._width-=p2;
+ }
+ } else if (ps + p2<macro_body[0].end() && *(ps + p2)=='#') { // Remove post-number sign
+ *(ps++) = (char)p1;
+ std::memmove(ps,ps + p2,macro_body[0].end() - ps - p2);
+ macro_body[0]._width-=p2;
+ } else { // Not near a number sign
+ if (p2<3) {
+ ps-=(ulongT)macro_body[0]._data;
+ macro_body[0].resize(macro_body[0]._width - p2 + 3,1,1,1,0);
+ ps+=(ulongT)macro_body[0]._data;
+ } else macro_body[0]._width-=p2 - 3;
+ std::memmove(ps + 3,ps + p2,macro_body[0].end() - ps - 3);
+ *(ps++) = '(';
+ *(ps++) = (char)p1;
+ *(ps++) = ')';
+ }
+ } else ++ps;
+ }
+ }
+ }
+
+ // Store number of arguments.
+ macro_def[0].resize(macro_def[0]._width + 1,1,1,1,0).back() = (char)(p1 - 1);
+
+ // Detect parts of function body inside a string.
+ is_inside_string(macro_body[0]).move_to(macro_body_is_string,0);
+ _cimg_mp_return(_cimg_mp_slot_nan);
+ }
+ }
+
+ // Check if the variable name could be valid. If not, this is probably an lvalue assignment.
+ is_sth = true; // is_valid_variable_name?
+ const bool is_const = l_variable_name>6 && !std::strncmp(variable_name,"const ",6);
+
+ s0 = variable_name._data;
+ if (is_const) {
+ s0+=6; while ((signed char)*s0<=' ') ++s0;
+ variable_name.resize(variable_name.end() - s0,1,1,1,0,0,1);
+ }
+
+ if (*variable_name>='0' && *variable_name<='9') is_sth = false;
+ else for (ns = variable_name._data; *ns; ++ns)
+ if (!is_varchar(*ns)) { is_sth = false; break; }
+
+ // Assign variable (direct).
+ if (is_sth) {
+ arg3 = variable_name[1]?~0U:*variable_name; // One-char variable
+ if (variable_name[1] && !variable_name[2]) { // Two-chars variable
+ c1 = variable_name[0];
+ c2 = variable_name[1];
+ if (c1=='w' && c2=='h') arg3 = 0; // wh
+ else if (c1=='p' && c2=='i') arg3 = 3; // pi
+ else if (c1=='i') {
+ if (c2>='0' && c2<='9') arg3 = 19 + c2 - '0'; // i0...i9
+ else if (c2=='m') arg3 = 4; // im
+ else if (c2=='M') arg3 = 5; // iM
+ else if (c2=='a') arg3 = 6; // ia
+ else if (c2=='v') arg3 = 7; // iv
+ else if (c2=='s') arg3 = 8; // is
+ else if (c2=='p') arg3 = 9; // ip
+ else if (c2=='c') arg3 = 10; // ic
+ } else if (c2=='m') {
+ if (c1=='x') arg3 = 11; // xm
+ else if (c1=='y') arg3 = 12; // ym
+ else if (c1=='z') arg3 = 13; // zm
+ else if (c1=='c') arg3 = 14; // cm
+ } else if (c2=='M') {
+ if (c1=='x') arg3 = 15; // xM
+ else if (c1=='y') arg3 = 16; // yM
+ else if (c1=='z') arg3 = 17; // zM
+ else if (c1=='c') arg3 = 18; // cM
+ }
+ } else if (variable_name[1] && variable_name[2] && !variable_name[3]) { // Three-chars variable
+ c1 = variable_name[0];
+ c2 = variable_name[1];
+ c3 = variable_name[2];
+ if (c1=='w' && c2=='h' && c3=='d') arg3 = 1; // whd
+ } else if (variable_name[1] && variable_name[2] && variable_name[3] &&
+ !variable_name[4]) { // Four-chars variable
+ c1 = variable_name[0];
+ c2 = variable_name[1];
+ c3 = variable_name[2];
+ c4 = variable_name[3];
+ if (c1=='w' && c2=='h' && c3=='d' && c4=='s') arg3 = 2; // whds
+ } else if (!std::strcmp(variable_name,"interpolation")) arg3 = 29; // interpolation
+ else if (!std::strcmp(variable_name,"boundary")) arg3 = 30; // boundary
+
+ arg1 = ~0U;
+ arg2 = compile(s + 1,se,depth1,0);
+ if (is_const) _cimg_mp_check_constant(arg2,2,0);
+
+ if (arg3!=~0U) // One-char variable, or variable in reserved_labels
+ arg1 = reserved_label[arg3];
+ else // Multi-char variable name : check for existing variable with same name
+ cimglist_for(variable_def,i)
+ if (!std::strcmp(variable_name,variable_def[i])) { arg1 = variable_pos[i]; break; }
+
+ if (arg1==~0U) { // Create new variable
+ if (_cimg_mp_is_vector(arg2)) { // Vector variable
+ arg1 = is_comp_vector(arg2)?arg2:vector_copy(arg2);
+ set_variable_vector(arg1);
+ } else { // Scalar variable
+ if (is_const) arg1 = arg2;
+ else {
+ arg1 = _cimg_mp_is_comp(arg2)?arg2:scalar1(mp_copy,arg2);
+ memtype[arg1] = -1;
+ }
+ }
+
+ if (arg3!=~0U) reserved_label[arg3] = arg1;
+ else {
+ if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0);
+ variable_pos[variable_def._width] = arg1;
+ variable_name.move_to(variable_def);
+ }
+
+ } else { // Variable already exists -> assign a new value
+ if (is_const || _cimg_mp_is_constant(arg1)) {
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Invalid assignment of %sconst variable '%s'%s, "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ _cimg_mp_is_constant(arg1)?"already-defined ":"non-",
+ variable_name._data,
+ !_cimg_mp_is_constant(arg1) && is_const?" as a new const variable":"",
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ _cimg_mp_check_type(arg2,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1)) { // Vector
+ if (_cimg_mp_is_vector(arg2)) // From vector
+ CImg<ulongT>::vector((ulongT)mp_vector_copy,arg1,arg2,(ulongT)_cimg_mp_vector_size(arg1)).
+ move_to(code);
+ else // From scalar
+ CImg<ulongT>::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_vector_size(arg1),arg2).
+ move_to(code);
+ } else // Scalar
+ CImg<ulongT>::vector((ulongT)mp_copy,arg1,arg2).move_to(code);
+ }
+ _cimg_mp_return(arg1);
+ }
+
+ // Assign lvalue (variable name was not valid for a direct assignment).
+ arg1 = ~0U;
+ is_sth = (bool)std::strchr(variable_name,'?'); // Contains_ternary_operator?
+ if (is_sth) break; // Do nothing and make ternary operator prioritary over assignment
+
+ if (l_variable_name>2 && (std::strchr(variable_name,'(') || std::strchr(variable_name,'['))) {
+ ref.assign(7);
+ arg1 = compile(ss,s,depth1,ref); // Lvalue slot
+ arg2 = compile(s + 1,se,depth1,0); // Value to assign
+
+ if (*ref==1) { // Vector value (scalar): V[k] = scalar
+ _cimg_mp_check_type(arg2,2,1,0);
+ arg3 = ref[1]; // Vector slot
+ arg4 = ref[2]; // Index
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ CImg<ulongT>::vector((ulongT)mp_vector_set_off,arg2,arg3,(ulongT)_cimg_mp_vector_size(arg3),arg4,arg2).
+ move_to(code);
+ _cimg_mp_return(arg2);
+ }
+
+ if (*ref==2) { // Image value (scalar): i/j[_#ind,off] = scalar
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,1,0);
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // Offset
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg2);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff),
+ arg2,p1,arg3).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg2);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff),
+ arg2,arg3).move_to(code);
+ }
+ _cimg_mp_return(arg2);
+ }
+
+ if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) = scalar
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,1,0);
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // X
+ arg4 = ref[4]; // Y
+ arg5 = ref[5]; // Z
+ arg6 = ref[6]; // C
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg2);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc),
+ arg2,p1,arg3,arg4,arg5,arg6).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg2);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc),
+ arg2,arg3,arg4,arg5,arg6).move_to(code);
+ }
+ _cimg_mp_return(arg2);
+ }
+
+ if (*ref==4) { // Image value (vector): I/J[_#ind,off] = value
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // Offset
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg2);
+ if (_cimg_mp_is_scalar(arg2))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s),
+ arg2,p1,arg3).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v),
+ arg2,p1,arg3,_cimg_mp_vector_size(arg2)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg2);
+ if (_cimg_mp_is_scalar(arg2))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s),
+ arg2,arg3).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v),
+ arg2,arg3,_cimg_mp_vector_size(arg2)).move_to(code);
+ }
+ _cimg_mp_return(arg2);
+ }
+
+ if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) = value
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // X
+ arg4 = ref[4]; // Y
+ arg5 = ref[5]; // Z
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg2);
+ if (_cimg_mp_is_scalar(arg2))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s),
+ arg2,p1,arg3,arg4,arg5).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v),
+ arg2,p1,arg3,arg4,arg5,_cimg_mp_vector_size(arg2)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg2);
+ if (_cimg_mp_is_scalar(arg2))
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s),
+ arg2,arg3,arg4,arg5).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v),
+ arg2,arg3,arg4,arg5,_cimg_mp_vector_size(arg2)).move_to(code);
+ }
+ _cimg_mp_return(arg2);
+ }
+
+ if (_cimg_mp_is_vector(arg1)) { // Vector variable: V = value
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg2)) // From vector
+ CImg<ulongT>::vector((ulongT)mp_vector_copy,arg1,arg2,(ulongT)_cimg_mp_vector_size(arg1)).
+ move_to(code);
+ else // From scalar
+ CImg<ulongT>::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_vector_size(arg1),arg2).
+ move_to(code);
+ _cimg_mp_return(arg1);
+ }
+
+ if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s = scalar
+ _cimg_mp_check_type(arg2,2,1,0);
+ CImg<ulongT>::vector((ulongT)mp_copy,arg1,arg2).move_to(code);
+ _cimg_mp_return(arg1);
+ }
+ }
+
+ // No assignment expressions match -> error
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Invalid %slvalue '%s', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ arg1!=~0U && _cimg_mp_is_constant(arg1)?"const ":"",
+ variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+
+ // Apply unary/binary/ternary operators. The operator precedences should be the same as in C++.
+ for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1
+ if (*s=='=' && (*ps=='*' || *ps=='/' || *ps=='^') && *ns==*ps &&
+ level[s - expr._data]==clevel) { // Self-operators for complex numbers only (**=,//=,^^=)
+ _cimg_mp_op(*ps=='*'?"Operator '**='":*ps=='/'?"Operator '//='":"Operator '^^='");
+
+ ref.assign(7);
+ arg1 = compile(ss,ns,depth1,ref); // Vector slot
+ arg2 = compile(s + 1,se,depth1,0); // Right operand
+ _cimg_mp_check_type(arg1,1,2,2);
+ _cimg_mp_check_type(arg2,2,3,2);
+ if (_cimg_mp_is_vector(arg2)) { // Complex **= complex
+ if (*ps=='*')
+ CImg<ulongT>::vector((ulongT)mp_complex_mul,arg1,arg1,arg2).move_to(code);
+ else if (*ps=='/')
+ CImg<ulongT>::vector((ulongT)mp_complex_div_vv,arg1,arg1,arg2).move_to(code);
+ else
+ CImg<ulongT>::vector((ulongT)mp_complex_pow_vv,arg1,arg1,arg2).move_to(code);
+ } else { // Complex **= scalar
+ if (*ps=='*') {
+ if (arg2==1) _cimg_mp_return(arg1);
+ self_vector_s(arg1,mp_self_mul,arg2);
+ } else if (*ps=='/') {
+ if (arg2==1) _cimg_mp_return(arg1);
+ self_vector_s(arg1,mp_self_div,arg2);
+ } else {
+ if (arg2==1) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)mp_complex_pow_vs,arg1,arg1,arg2).move_to(code);
+ }
+ }
+
+ // Write computed value back in image if necessary.
+ if (*ref==4) { // Image value (vector): I/J[_#ind,off] **= value
+ is_parallelizable = false;
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // Offset
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v),
+ arg1,p1,arg3,_cimg_mp_vector_size(arg1)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v),
+ arg1,arg3,_cimg_mp_vector_size(arg1)).move_to(code);
+ }
+
+ } else if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) **= value
+ is_parallelizable = false;
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // X
+ arg4 = ref[4]; // Y
+ arg5 = ref[5]; // Z
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v),
+ arg1,p1,arg3,arg4,arg5,_cimg_mp_vector_size(arg1)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v),
+ arg1,arg3,arg4,arg5,_cimg_mp_vector_size(arg1)).move_to(code);
+ }
+ }
+
+ _cimg_mp_return(arg1);
+ }
+
+ for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1
+ if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || *ps=='%' ||
+ *ps=='&' || *ps=='^' || *ps=='|' ||
+ (*ps=='>' && *ns=='>') || (*ps=='<' && *ns=='<')) &&
+ level[s - expr._data]==clevel) { // Self-operators (+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=)
+ switch (*ps) {
+ case '+' : op = mp_self_add; _cimg_mp_op("Operator '+='"); break;
+ case '-' : op = mp_self_sub; _cimg_mp_op("Operator '-='"); break;
+ case '*' : op = mp_self_mul; _cimg_mp_op("Operator '*='"); break;
+ case '/' : op = mp_self_div; _cimg_mp_op("Operator '/='"); break;
+ case '%' : op = mp_self_modulo; _cimg_mp_op("Operator '%='"); break;
+ case '<' : op = mp_self_bitwise_left_shift; _cimg_mp_op("Operator '<<='"); break;
+ case '>' : op = mp_self_bitwise_right_shift; _cimg_mp_op("Operator '>>='"); break;
+ case '&' : op = mp_self_bitwise_and; _cimg_mp_op("Operator '&='"); break;
+ case '|' : op = mp_self_bitwise_or; _cimg_mp_op("Operator '|='"); break;
+ default : op = mp_self_pow; _cimg_mp_op("Operator '^='"); break;
+ }
+ s1 = *ps=='>' || *ps=='<'?ns:ps;
+
+ ref.assign(7);
+ arg1 = compile(ss,s1,depth1,ref); // Variable slot
+ arg2 = compile(s + 1,se,depth1,0); // Value to apply
+
+ // Check for particular case to be simplified.
+ if ((op==mp_self_add || op==mp_self_sub) && !arg2) _cimg_mp_return(arg1);
+ if ((op==mp_self_mul || op==mp_self_div) && arg2==1) _cimg_mp_return(arg1);
+
+ // Apply operator on a copy to prevent modifying a constant or a variable.
+ if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) {
+ if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1);
+ else arg1 = scalar1(mp_copy,arg1);
+ }
+
+ if (*ref==1) { // Vector value (scalar): V[k] += scalar
+ _cimg_mp_check_type(arg2,2,1,0);
+ arg3 = ref[1]; // Vector slot
+ arg4 = ref[2]; // Index
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ CImg<ulongT>::vector((ulongT)op,arg1,arg2).move_to(code);
+ CImg<ulongT>::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_vector_size(arg3),arg4,arg1).
+ move_to(code);
+ _cimg_mp_return(arg1);
+ }
+
+ if (*ref==2) { // Image value (scalar): i/j[_#ind,off] += scalar
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,1,0);
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // Offset
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ CImg<ulongT>::vector((ulongT)op,arg1,arg2).move_to(code);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff),
+ arg1,p1,arg3).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff),
+ arg1,arg3).move_to(code);
+ }
+ _cimg_mp_return(arg1);
+ }
+
+ if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) += scalar
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,1,0);
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // X
+ arg4 = ref[4]; // Y
+ arg5 = ref[5]; // Z
+ arg6 = ref[6]; // C
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ CImg<ulongT>::vector((ulongT)op,arg1,arg2).move_to(code);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc),
+ arg1,p1,arg3,arg4,arg5,arg6).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc),
+ arg1,arg3,arg4,arg5,arg6).move_to(code);
+ }
+ _cimg_mp_return(arg1);
+ }
+
+ if (*ref==4) { // Image value (vector): I/J[_#ind,off] += value
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // Offset
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v),
+ arg1,p1,arg3,_cimg_mp_vector_size(arg1)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v),
+ arg1,arg3,_cimg_mp_vector_size(arg1)).move_to(code);
+ }
+ _cimg_mp_return(arg1);
+ }
+
+ if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) += value
+ is_parallelizable = false;
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // X
+ arg4 = ref[4]; // Y
+ arg5 = ref[5]; // Z
+ if (p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ if (_cimg_mp_is_scalar(arg2)) self_vector_s(arg1,op,arg2); else self_vector_v(arg1,op,arg2);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v),
+ arg1,p1,arg3,arg4,arg5,_cimg_mp_vector_size(arg1)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(arg1);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v),
+ arg1,arg3,arg4,arg5,_cimg_mp_vector_size(arg1)).move_to(code);
+ }
+ _cimg_mp_return(arg1);
+ }
+
+ if (_cimg_mp_is_vector(arg1)) { // Vector variable: V += value
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg2)) self_vector_v(arg1,op,arg2); // Vector += vector
+ else self_vector_s(arg1,op,arg2); // Vector += scalar
+ _cimg_mp_return(arg1);
+ }
+
+ if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s += scalar
+ _cimg_mp_check_type(arg2,2,1,0);
+ CImg<ulongT>::vector((ulongT)op,arg1,arg2).move_to(code);
+ _cimg_mp_return(arg1);
+ }
+
+ variable_name.assign(ss,(unsigned int)(s - ss)).back() = 0;
+ cimg::strpare(variable_name,false,true);
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Invalid %slvalue '%s', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ _cimg_mp_is_constant(arg1)?"const ":"",
+ variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+
+ for (s = ss1; s<se1; ++s)
+ if (*s=='?' && level[s - expr._data]==clevel) { // Ternary operator 'cond?expr1:expr2'
+ _cimg_mp_op("Operator '?:'");
+ s1 = s + 1; while (s1<se1 && (*s1!=':' || level[s1 - expr._data]!=clevel)) ++s1;
+ arg1 = compile(ss,s,depth1,0);
+ _cimg_mp_check_type(arg1,1,1,0);
+ if (_cimg_mp_is_constant(arg1)) {
+ if ((bool)mem[arg1]) return compile(s + 1,*s1!=':'?se:s1,depth1,0);
+ else return *s1!=':'?0:compile(++s1,se,depth1,0);
+ }
+ p2 = code._width;
+ arg2 = compile(s + 1,*s1!=':'?se:s1,depth1,0);
+ p3 = code._width;
+ arg3 = *s1==':'?compile(++s1,se,depth1,0):_cimg_mp_is_vector(arg2)?vector(_cimg_mp_vector_size(arg2),0):0;
+ _cimg_mp_check_type(arg3,3,_cimg_mp_is_vector(arg2)?2:1,_cimg_mp_vector_size(arg2));
+ arg4 = _cimg_mp_vector_size(arg2);
+ if (arg4) pos = vector(arg4); else pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_if,pos,arg1,arg2,arg3,
+ p3 - p2,code._width - p3,arg4).move_to(code,p2);
+ _cimg_mp_return(pos);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='|' && *ns=='|' && level[s - expr._data]==clevel) { // Logical or ('||')
+ _cimg_mp_op("Operator '||'");
+ arg1 = compile(ss,s,depth1,0);
+ _cimg_mp_check_type(arg1,1,1,0);
+ if (arg1>0 && arg1<=16) _cimg_mp_return(1);
+ p2 = code._width;
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,1,0);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant(mem[arg1] || mem[arg2]);
+ if (!arg1) _cimg_mp_return(arg2);
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_logical_or,pos,arg1,arg2,code._width - p2).
+ move_to(code,p2);
+ _cimg_mp_return(pos);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='&' && *ns=='&' && level[s - expr._data]==clevel) { // Logical and ('&&')
+ _cimg_mp_op("Operator '&&'");
+ arg1 = compile(ss,s,depth1,0);
+ _cimg_mp_check_type(arg1,1,1,0);
+ if (!arg1) _cimg_mp_return(0);
+ p2 = code._width;
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,1,0);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant(mem[arg1] && mem[arg2]);
+ if (arg1>0 && arg1<=16) _cimg_mp_return(arg2);
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_logical_and,pos,arg1,arg2,code._width - p2).
+ move_to(code,p2);
+ _cimg_mp_return(pos);
+ }
+
+ for (s = se2; s>ss; --s)
+ if (*s=='|' && level[s - expr._data]==clevel) { // Bitwise or ('|')
+ _cimg_mp_op("Operator '|'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_bitwise_or,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) {
+ if (!arg2) _cimg_mp_return(arg1);
+ _cimg_mp_vector2_vs(mp_bitwise_or,arg1,arg2);
+ }
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) {
+ if (!arg1) _cimg_mp_return(arg2);
+ _cimg_mp_vector2_sv(mp_bitwise_or,arg1,arg2);
+ }
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant((longT)mem[arg1] | (longT)mem[arg2]);
+ if (!arg2) _cimg_mp_return(arg1);
+ if (!arg1) _cimg_mp_return(arg2);
+ _cimg_mp_scalar2(mp_bitwise_or,arg1,arg2);
+ }
+
+ for (s = se2; s>ss; --s)
+ if (*s=='&' && level[s - expr._data]==clevel) { // Bitwise and ('&')
+ _cimg_mp_op("Operator '&'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_bitwise_and,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_bitwise_and,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_bitwise_and,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant((longT)mem[arg1] & (longT)mem[arg2]);
+ if (!arg1 || !arg2) _cimg_mp_return(0);
+ _cimg_mp_scalar2(mp_bitwise_and,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='!' && *ns=='=' && level[s - expr._data]==clevel) { // Not equal to ('!=')
+ _cimg_mp_op("Operator '!='");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ if (arg1==arg2) _cimg_mp_return(0);
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = _cimg_mp_vector_size(arg2);
+ if (p1 || p2) {
+ if (p1 && p2 && p1!=p2) _cimg_mp_return(1);
+ _cimg_mp_scalar6(mp_vector_neq,arg1,p1,arg2,p2,11,1);
+ }
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]!=mem[arg2]);
+ _cimg_mp_scalar2(mp_neq,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='=' && *ns=='=' && level[s - expr._data]==clevel) { // Equal to ('==')
+ _cimg_mp_op("Operator '=='");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ if (arg1==arg2) _cimg_mp_return(1);
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = _cimg_mp_vector_size(arg2);
+ if (p1 || p2) {
+ if (p1 && p2 && p1!=p2) _cimg_mp_return(0);
+ _cimg_mp_scalar6(mp_vector_eq,arg1,p1,arg2,p2,11,1);
+ }
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]==mem[arg2]);
+ _cimg_mp_scalar2(mp_eq,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='<' && *ns=='=' && level[s - expr._data]==clevel) { // Less or equal than ('<=')
+ _cimg_mp_op("Operator '<='");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_lte,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_lte,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_lte,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]<=mem[arg2]);
+ if (arg1==arg2) _cimg_mp_return(1);
+ _cimg_mp_scalar2(mp_lte,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='>' && *ns=='=' && level[s - expr._data]==clevel) { // Greater or equal than ('>=')
+ _cimg_mp_op("Operator '>='");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_gte,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_gte,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_gte,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]>=mem[arg2]);
+ if (arg1==arg2) _cimg_mp_return(1);
+ _cimg_mp_scalar2(mp_gte,arg1,arg2);
+ }
+
+ for (s = se2, ns = se1, ps = se3; s>ss; --s, --ns, --ps)
+ if (*s=='<' && *ns!='<' && *ps!='<' && level[s - expr._data]==clevel) { // Less than ('<')
+ _cimg_mp_op("Operator '<'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_lt,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_lt,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_lt,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]<mem[arg2]);
+ if (arg1==arg2) _cimg_mp_return(0);
+ _cimg_mp_scalar2(mp_lt,arg1,arg2);
+ }
+
+ for (s = se2, ns = se1, ps = se3; s>ss; --s, --ns, --ps)
+ if (*s=='>' && *ns!='>' && *ps!='>' && level[s - expr._data]==clevel) { // Greather than ('>')
+ _cimg_mp_op("Operator '>'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_gt,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_gt,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_gt,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]>mem[arg2]);
+ if (arg1==arg2) _cimg_mp_return(0);
+ _cimg_mp_scalar2(mp_gt,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='<' && *ns=='<' && level[s - expr._data]==clevel) { // Left bit shift ('<<')
+ _cimg_mp_op("Operator '<<'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2))
+ _cimg_mp_vector2_vv(mp_bitwise_left_shift,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) {
+ if (!arg2) _cimg_mp_return(arg1);
+ _cimg_mp_vector2_vs(mp_bitwise_left_shift,arg1,arg2);
+ }
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2))
+ _cimg_mp_vector2_sv(mp_bitwise_left_shift,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant((longT)mem[arg1]<<(unsigned int)mem[arg2]);
+ if (!arg1) _cimg_mp_return(0);
+ if (!arg2) _cimg_mp_return(arg1);
+ _cimg_mp_scalar2(mp_bitwise_left_shift,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='>' && *ns=='>' && level[s - expr._data]==clevel) { // Right bit shift ('>>')
+ _cimg_mp_op("Operator '>>'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2))
+ _cimg_mp_vector2_vv(mp_bitwise_right_shift,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) {
+ if (!arg2) _cimg_mp_return(arg1);
+ _cimg_mp_vector2_vs(mp_bitwise_right_shift,arg1,arg2);
+ }
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2))
+ _cimg_mp_vector2_sv(mp_bitwise_right_shift,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant((longT)mem[arg1]>>(unsigned int)mem[arg2]);
+ if (!arg1) _cimg_mp_return(0);
+ if (!arg2) _cimg_mp_return(arg1);
+ _cimg_mp_scalar2(mp_bitwise_right_shift,arg1,arg2);
+ }
+
+ for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps)
+ if (*s=='+' && (*ns!='+' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' &&
+ *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' &&
+ (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' &&
+ *(ps - 1)<='9')))) &&
+ level[s - expr._data]==clevel) { // Addition ('+')
+ _cimg_mp_op("Operator '+'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (!arg2) _cimg_mp_return(arg1);
+ if (!arg1) _cimg_mp_return(arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_add,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_add,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_add,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] + mem[arg2]);
+ if (code) { // Try to spot linear case 'a*b + c'.
+ CImg<ulongT> &pop = code.back();
+ if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) {
+ arg3 = (unsigned int)pop[1];
+ arg4 = (unsigned int)pop[2];
+ arg5 = (unsigned int)pop[3];
+ code.remove();
+ CImg<ulongT>::vector((ulongT)mp_linear_add,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code);
+ _cimg_mp_return(arg3);
+ }
+ }
+ if (arg2==1) _cimg_mp_scalar1(mp_increment,arg1);
+ if (arg1==1) _cimg_mp_scalar1(mp_increment,arg2);
+ _cimg_mp_scalar2(mp_add,arg1,arg2);
+ }
+
+ for (ns = se1, s = se2, ps = pexpr._data + (se3 - expr._data); s>ss; --ns, --s, --ps)
+ if (*s=='-' && (*ns!='-' || ns!=se1) && *ps!='-' && *ps!='+' && *ps!='*' && *ps!='/' && *ps!='%' &&
+ *ps!='&' && *ps!='|' && *ps!='^' && *ps!='!' && *ps!='~' && *ps!='#' &&
+ (*ps!='e' || !(ps - pexpr._data>ss - expr._data && (*(ps - 1)=='.' || (*(ps - 1)>='0' &&
+ *(ps - 1)<='9')))) &&
+ level[s - expr._data]==clevel) { // Subtraction ('-')
+ _cimg_mp_op("Operator '-'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (!arg2) _cimg_mp_return(arg1);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_sub,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_sub,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) {
+ if (!arg1) _cimg_mp_vector1_v(mp_minus,arg2);
+ _cimg_mp_vector2_sv(mp_sub,arg1,arg2);
+ }
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1] - mem[arg2]);
+ if (!arg1) _cimg_mp_scalar1(mp_minus,arg2);
+ if (code) { // Try to spot linear cases 'a*b - c' and 'c - a*b'.
+ CImg<ulongT> &pop = code.back();
+ if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) {
+ arg3 = (unsigned int)pop[1];
+ arg4 = (unsigned int)pop[2];
+ arg5 = (unsigned int)pop[3];
+ code.remove();
+ CImg<ulongT>::vector((ulongT)(arg3==arg1?mp_linear_sub_left:mp_linear_sub_right),
+ arg3,arg4,arg5,arg3==arg1?arg2:arg1).move_to(code);
+ _cimg_mp_return(arg3);
+ }
+ }
+ if (arg2==1) _cimg_mp_scalar1(mp_decrement,arg1);
+ _cimg_mp_scalar2(mp_sub,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='*' && *ns=='*' && level[s - expr._data]==clevel) { // Complex multiplication ('**')
+ _cimg_mp_op("Operator '**'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg1,1,3,2);
+ _cimg_mp_check_type(arg2,2,3,2);
+ if (arg2==1) _cimg_mp_return(arg1);
+ if (arg1==1) _cimg_mp_return(arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) {
+ pos = vector(2);
+ CImg<ulongT>::vector((ulongT)mp_complex_mul,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]);
+ if (!arg1 || !arg2) _cimg_mp_return(0);
+ _cimg_mp_scalar2(mp_mul,arg1,arg2);
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='/' && *ns=='/' && level[s - expr._data]==clevel) { // Complex division ('//')
+ _cimg_mp_op("Operator '//'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg1,1,3,2);
+ _cimg_mp_check_type(arg2,2,3,2);
+ if (arg2==1) _cimg_mp_return(arg1);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) {
+ pos = vector(2);
+ CImg<ulongT>::vector((ulongT)mp_complex_div_vv,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_div,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) {
+ pos = vector(2);
+ CImg<ulongT>::vector((ulongT)mp_complex_div_sv,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]);
+ if (!arg1) _cimg_mp_return(0);
+ _cimg_mp_scalar2(mp_div,arg1,arg2);
+ }
+
+ for (s = se2; s>ss; --s) if (*s=='*' && level[s - expr._data]==clevel) { // Multiplication ('*')
+ _cimg_mp_op("Operator '*'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ p2 = _cimg_mp_vector_size(arg2);
+ if (p2>0 && _cimg_mp_vector_size(arg1)==p2*p2) { // Particular case of matrix multiplication
+ pos = vector(p2);
+ CImg<ulongT>::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,p2,p2,1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (arg2==1) _cimg_mp_return(arg1);
+ if (arg1==1) _cimg_mp_return(arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_mul,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_mul,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_mul,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]*mem[arg2]);
+
+ if (code) { // Try to spot double multiplication 'a*b*c'.
+ CImg<ulongT> &pop = code.back();
+ if (pop[0]==(ulongT)mp_mul && _cimg_mp_is_comp(pop[1]) && (pop[1]==arg1 || pop[1]==arg2)) {
+ arg3 = (unsigned int)pop[1];
+ arg4 = (unsigned int)pop[2];
+ arg5 = (unsigned int)pop[3];
+ code.remove();
+ CImg<ulongT>::vector((ulongT)mp_mul2,arg3,arg4,arg5,arg3==arg2?arg1:arg2).move_to(code);
+ _cimg_mp_return(arg3);
+ }
+ }
+ if (!arg1 || !arg2) _cimg_mp_return(0);
+ _cimg_mp_scalar2(mp_mul,arg1,arg2);
+ }
+
+ for (s = se2; s>ss; --s) if (*s=='/' && level[s - expr._data]==clevel) { // Division ('/')
+ _cimg_mp_op("Operator '/'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (arg2==1) _cimg_mp_return(arg1);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_div,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_div,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_div,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]);
+ if (!arg1) _cimg_mp_return(0);
+ _cimg_mp_scalar2(mp_div,arg1,arg2);
+ }
+
+ for (s = se2, ns = se1; s>ss; --s, --ns)
+ if (*s=='%' && *ns!='^' && level[s - expr._data]==clevel) { // Modulo ('%')
+ _cimg_mp_op("Operator '%'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_modulo,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_modulo,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_modulo,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant(cimg::mod(mem[arg1],mem[arg2]));
+ _cimg_mp_scalar2(mp_modulo,arg1,arg2);
+ }
+
+ if (se1>ss) {
+ if (*ss=='+' && (*ss1!='+' || (ss2<se && *ss2>='0' && *ss2<='9'))) { // Unary plus ('+')
+ _cimg_mp_op("Operator '+'");
+ _cimg_mp_return(compile(ss1,se,depth1,0));
+ }
+
+ if (*ss=='-' && (*ss1!='-' || (ss2<se && *ss2>='0' && *ss2<='9'))) { // Unary minus ('-')
+ _cimg_mp_op("Operator '-'");
+ arg1 = compile(ss1,se,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_minus,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(-mem[arg1]);
+ _cimg_mp_scalar1(mp_minus,arg1);
+ }
+
+ if (*ss=='!') { // Logical not ('!')
+ _cimg_mp_op("Operator '!'");
+ if (*ss1=='!') { // '!!expr' optimized as 'bool(expr)'
+ arg1 = compile(ss2,se,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]);
+ _cimg_mp_scalar1(mp_bool,arg1);
+ }
+ arg1 = compile(ss1,se,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_logical_not,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(!mem[arg1]);
+ _cimg_mp_scalar1(mp_logical_not,arg1);
+ }
+
+ if (*ss=='~') { // Bitwise not ('~')
+ _cimg_mp_op("Operator '~'");
+ arg1 = compile(ss1,se,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bitwise_not,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(~(unsigned int)mem[arg1]);
+ _cimg_mp_scalar1(mp_bitwise_not,arg1);
+ }
+ }
+
+ for (s = se3, ns = se2; s>ss; --s, --ns)
+ if (*s=='^' && *ns=='^' && level[s - expr._data]==clevel) { // Complex power ('^^')
+ _cimg_mp_op("Operator '^^'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 2,se,depth1,0);
+ _cimg_mp_check_type(arg1,1,3,2);
+ _cimg_mp_check_type(arg2,2,3,2);
+ if (arg2==1) _cimg_mp_return(arg1);
+ pos = vector(2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) {
+ CImg<ulongT>::vector((ulongT)mp_complex_pow_vv,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) {
+ CImg<ulongT>::vector((ulongT)mp_complex_pow_vs,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) {
+ CImg<ulongT>::vector((ulongT)mp_complex_pow_sv,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ CImg<ulongT>::vector((ulongT)mp_complex_pow_ss,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ for (s = se2; s>ss; --s)
+ if (*s=='^' && level[s - expr._data]==clevel) { // Power ('^')
+ _cimg_mp_op("Operator '^'");
+ arg1 = compile(ss,s,depth1,0);
+ arg2 = compile(s + 1,se,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (arg2==1) _cimg_mp_return(arg1);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_pow,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_pow,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_pow,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant(std::pow(mem[arg1],mem[arg2]));
+ switch (arg2) {
+ case 0 : _cimg_mp_return(1);
+ case 2 : _cimg_mp_scalar1(mp_sqr,arg1);
+ case 3 : _cimg_mp_scalar1(mp_pow3,arg1);
+ case 4 : _cimg_mp_scalar1(mp_pow4,arg1);
+ default :
+ if (_cimg_mp_is_constant(arg2)) {
+ if (mem[arg2]==0.5) { _cimg_mp_scalar1(mp_sqrt,arg1); }
+ else if (mem[arg2]==0.25) { _cimg_mp_scalar1(mp_pow0_25,arg1); }
+ }
+ _cimg_mp_scalar2(mp_pow,arg1,arg2);
+ }
+ }
+
+ // Percentage computation.
+ if (*se1=='%') {
+ arg1 = compile(ss,se1,depth1,0);
+ arg2 = _cimg_mp_is_constant(arg1)?0:constant(100);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(mp_div,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(mem[arg1]/100);
+ _cimg_mp_scalar2(mp_div,arg1,arg2);
+ }
+
+ is_sth = ss1<se1 && (*ss=='+' || *ss=='-') && *ss1==*ss; // is pre-?
+ if (is_sth || (se2>ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { // Pre/post-decrement and increment
+ if ((is_sth && *ss=='+') || (!is_sth && *se1=='+')) {
+ _cimg_mp_op("Operator '++'");
+ op = mp_self_increment;
+ } else {
+ _cimg_mp_op("Operator '--'");
+ op = mp_self_decrement;
+ }
+ ref.assign(7);
+ arg1 = is_sth?compile(ss2,se,depth1,ref):compile(ss,se2,depth1,ref); // Variable slot
+
+ // Apply operator on a copy to prevent modifying a constant or a variable.
+ if (*ref && (_cimg_mp_is_constant(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_variable(arg1))) {
+ if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1);
+ else arg1 = scalar1(mp_copy,arg1);
+ }
+
+ if (is_sth) pos = arg1; // Determine return indice, depending on pre/post action
+ else {
+ if (_cimg_mp_is_vector(arg1)) pos = vector_copy(arg1);
+ else pos = scalar1(mp_copy,arg1);
+ }
+
+ if (*ref==1) { // Vector value (scalar): V[k]++
+ arg3 = ref[1]; // Vector slot
+ arg4 = ref[2]; // Index
+ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ CImg<ulongT>::vector((ulongT)op,arg1,1).move_to(code);
+ CImg<ulongT>::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_vector_size(arg3),arg4,arg1).
+ move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (*ref==2) { // Image value (scalar): i/j[_#ind,off]++
+ is_parallelizable = false;
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // Offset
+ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ CImg<ulongT>::vector((ulongT)op,arg1).move_to(code);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff),
+ arg1,p1,arg3).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff),
+ arg1,arg3).move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c)++
+ is_parallelizable = false;
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // X
+ arg4 = ref[4]; // Y
+ arg5 = ref[5]; // Z
+ arg6 = ref[6]; // C
+ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ CImg<ulongT>::vector((ulongT)op,arg1).move_to(code);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc),
+ arg1,p1,arg3,arg4,arg5,arg6).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc),
+ arg1,arg3,arg4,arg5,arg6).move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ if (*ref==4) { // Image value (vector): I/J[_#ind,off]++
+ is_parallelizable = false;
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // Offset
+ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v),
+ arg1,p1,arg3,_cimg_mp_vector_size(arg1)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v),
+ arg1,arg3,_cimg_mp_vector_size(arg1)).move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c)++
+ is_parallelizable = false;
+ p1 = ref[1]; // Index
+ is_relative = (bool)ref[2];
+ arg3 = ref[3]; // X
+ arg4 = ref[4]; // Y
+ arg5 = ref[5]; // Z
+ if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int));
+ self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1);
+ if (p1!=~0U) {
+ if (!listout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v),
+ arg1,p1,arg3,arg4,arg5,_cimg_mp_vector_size(arg1)).move_to(code);
+ } else {
+ if (!imgout) _cimg_mp_return(pos);
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v),
+ arg1,arg3,arg4,arg5,_cimg_mp_vector_size(arg1)).move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ if (_cimg_mp_is_vector(arg1)) { // Vector variable: V++
+ self_vector_s(arg1,op==mp_self_increment?mp_self_add:mp_self_sub,1);
+ _cimg_mp_return(pos);
+ }
+
+ if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s++
+ CImg<ulongT>::vector((ulongT)op,arg1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (is_sth) variable_name.assign(ss2,(unsigned int)(se - ss1));
+ else variable_name.assign(ss,(unsigned int)(se1 - ss));
+ variable_name.back() = 0;
+ cimg::strpare(variable_name,false,true);
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Invalid %slvalue '%s', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ _cimg_mp_is_constant(arg1)?"const ":"",
+ variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+
+ // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'.
+ if (*se1==']' && *ss!='[') {
+ _cimg_mp_op("Value accessor '[]'");
+ is_relative = *ss=='j' || *ss=='J';
+ s0 = s1 = std::strchr(ss,'['); do { --s1; } while ((signed char)*s1<=' '); cimg::swap(*s0,*++s1);
+
+ if ((*ss=='I' || *ss=='J') && *ss1=='[' &&
+ (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a vector
+ if (*ss2=='#') { // Index specified
+ s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss3,s0++,depth1,0);
+ _cimg_mp_check_list(false);
+ } else { p1 = ~0U; s0 = ss2; }
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ p2 = 1 + (p1!=~0U);
+ arg1 = compile(s0,s1,depth1,0); // Offset
+ _cimg_mp_check_type(arg1,p2,1,0);
+ arg2 = ~0U;
+ if (s1<se1) {
+ arg2 = compile(++s1,se1,depth1,0); // Boundary
+ _cimg_mp_check_type(arg2,p2 + 1,1,0);
+ }
+ if (p_ref && arg2==~0U) {
+ *p_ref = 4;
+ p_ref[1] = p1;
+ p_ref[2] = (unsigned int)is_relative;
+ p_ref[3] = arg1;
+ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; // Prevent from being used in further optimization
+ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2;
+ }
+ p2 = ~0U; // 'p2' must be the dimension of the vector-valued operand if any
+ if (p1==~0U) p2 = imgin._spectrum;
+ else if (_cimg_mp_is_constant(p1)) {
+ p3 = (unsigned int)cimg::mod((int)mem[p1],listin.width());
+ p2 = listin[p3]._spectrum;
+ }
+ _cimg_mp_check_vector0(p2);
+ pos = vector(p2);
+ if (p1!=~0U) {
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff),
+ pos,p1,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code);
+ } else {
+ need_input_copy = true;
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_Joff:mp_Ioff),
+ pos,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ if ((*ss=='i' || *ss=='j') && *ss1=='[' &&
+ (reserved_label[*ss]==~0U || !_cimg_mp_is_vector(reserved_label[*ss]))) { // Image value as a scalar
+ if (*ss2=='#') { // Index specified
+ s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss3,s0++,depth1,0);
+ } else { p1 = ~0U; s0 = ss2; }
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(s0,s1,depth1,0); // Offset
+ arg2 = s1<se1?compile(++s1,se1,depth1,0):~0U; // Boundary
+ if (p_ref && arg2==~0U) {
+ *p_ref = 2;
+ p_ref[1] = p1;
+ p_ref[2] = (unsigned int)is_relative;
+ p_ref[3] = arg1;
+ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; // Prevent from being used in further optimization
+ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2;
+ }
+ if (p1!=~0U) {
+ if (!listin) _cimg_mp_return(0);
+ pos = scalar3(is_relative?mp_list_joff:mp_list_ioff,p1,arg1,arg2==~0U?_cimg_mp_boundary:arg2);
+ } else {
+ if (!imgin) _cimg_mp_return(0);
+ need_input_copy = true;
+ pos = scalar2(is_relative?mp_joff:mp_ioff,arg1,arg2==~0U?_cimg_mp_boundary:arg2);
+ }
+ memtype[pos] = -2; // Prevent from being used in further optimization
+ _cimg_mp_return(pos);
+ }
+
+ s0 = se1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0;
+ if (s0>ss) { // Vector value
+ arg1 = compile(ss,s0,depth1,0);
+ if (_cimg_mp_is_scalar(arg1)) {
+ variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0;
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Array brackets used on non-vector variable '%s', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+
+ }
+ s1 = s0 + 1; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+
+ if (s1<se1) { // Two arguments -> sub-vector extraction
+ p1 = _cimg_mp_vector_size(arg1);
+ arg2 = compile(++s0,s1,depth1,0); // Starting indice
+ arg3 = compile(++s1,se1,depth1,0); // Length
+ _cimg_mp_check_constant(arg3,2,3);
+ arg3 = (unsigned int)mem[arg3];
+ pos = vector(arg3);
+ CImg<ulongT>::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ // One argument -> vector value reference
+ arg2 = compile(++s0,se1,depth1,0);
+ if (_cimg_mp_is_constant(arg2)) { // Constant index
+ nb = (int)mem[arg2];
+ if (nb>=0 && nb<(int)_cimg_mp_vector_size(arg1)) _cimg_mp_return(arg1 + 1 + nb);
+ variable_name.assign(ss,(unsigned int)(s0 - ss)).back() = 0;
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Out-of-bounds reference '%s[%d]' "
+ "(vector '%s' has dimension %u), "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,
+ variable_name._data,nb,
+ variable_name._data,_cimg_mp_vector_size(arg1),
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ if (p_ref) {
+ *p_ref = 1;
+ p_ref[1] = arg1;
+ p_ref[2] = arg2;
+ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2; // Prevent from being used in further optimization
+ }
+ pos = scalar3(mp_vector_off,arg1,_cimg_mp_vector_size(arg1),arg2);
+ memtype[pos] = -2; // Prevent from being used in further optimization
+ _cimg_mp_return(pos);
+ }
+ }
+
+ // Look for a function call, an access to image value, or a parenthesis.
+ if (*se1==')') {
+ if (*ss=='(') _cimg_mp_return(compile(ss1,se1,depth1,p_ref)); // Simple parentheses
+ _cimg_mp_op("Value accessor '()'");
+ is_relative = *ss=='j' || *ss=='J';
+ s0 = s1 = std::strchr(ss,'('); do { --s1; } while ((signed char)*s1<=' '); cimg::swap(*s0,*++s1);
+
+ // I/J(_#ind,_x,_y,_z,_interpolation,_boundary_conditions)
+ if ((*ss=='I' || *ss=='J') && *ss1=='(') { // Image value as scalar
+ if (*ss2=='#') { // Index specified
+ s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss3,s0++,depth1,0);
+ _cimg_mp_check_list(false);
+ } else { p1 = ~0U; s0 = ss2; }
+ arg1 = is_relative?0U:(unsigned int)_cimg_mp_slot_x;
+ arg2 = is_relative?0U:(unsigned int)_cimg_mp_slot_y;
+ arg3 = is_relative?0U:(unsigned int)_cimg_mp_slot_z;
+ arg4 = arg5 = ~0U;
+ if (s0<se1) {
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(s0,s1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) { // Coordinates specified as a vector
+ p2 = _cimg_mp_vector_size(arg1);
+ ++arg1;
+ if (p2>1) {
+ arg2 = arg1 + 1;
+ if (p2>2) arg3 = arg2 + 1;
+ }
+ if (s1<se1) {
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg4 = compile(s1,s2,depth1,0);
+ arg5 = s2<se1?compile(++s2,se1,depth1,0):~0U;
+ }
+ } else if (s1<se1) {
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(s1,s2,depth1,0);
+ if (s2<se1) {
+ s3 = ++s2; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg3 = compile(s2,s3,depth1,0);
+ if (s3<se1) {
+ s2 = ++s3; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg4 = compile(s3,s2,depth1,0);
+ arg5 = s2<se1?compile(++s2,se1,depth1,0):~0U;
+ }
+ }
+ }
+ }
+ if (p_ref && arg4==~0U && arg5==~0U) {
+ *p_ref = 5;
+ p_ref[1] = p1;
+ p_ref[2] = (unsigned int)is_relative;
+ p_ref[3] = arg1;
+ p_ref[4] = arg2;
+ p_ref[5] = arg3;
+ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; // Prevent from being used in further optimization
+ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2;
+ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2;
+ if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2;
+ }
+ p2 = ~0U; // 'p2' must be the dimension of the vector-valued operand if any
+ if (p1==~0U) p2 = imgin._spectrum;
+ else if (_cimg_mp_is_constant(p1)) {
+ p3 = (unsigned int)cimg::mod((int)mem[p1],listin.width());
+ p2 = listin[p3]._spectrum;
+ }
+ _cimg_mp_check_vector0(p2);
+ pos = vector(p2);
+ if (p1!=~0U)
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_list_Jxyz:mp_list_Ixyz),
+ pos,p1,arg1,arg2,arg3,
+ arg4==~0U?_cimg_mp_interpolation:arg4,
+ arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code);
+ else {
+ need_input_copy = true;
+ CImg<ulongT>::vector((ulongT)(is_relative?mp_Jxyz:mp_Ixyz),
+ pos,arg1,arg2,arg3,
+ arg4==~0U?_cimg_mp_interpolation:arg4,
+ arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ // i/j(_#ind,_x,_y,_z,_c,_interpolation,_boundary_conditions)
+ if ((*ss=='i' || *ss=='j') && *ss1=='(') { // Image value as scalar
+ if (*ss2=='#') { // Index specified
+ s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss3,s0++,depth1,0);
+ } else { p1 = ~0U; s0 = ss2; }
+ arg1 = is_relative?0U:(unsigned int)_cimg_mp_slot_x;
+ arg2 = is_relative?0U:(unsigned int)_cimg_mp_slot_y;
+ arg3 = is_relative?0U:(unsigned int)_cimg_mp_slot_z;
+ arg4 = is_relative?0U:(unsigned int)_cimg_mp_slot_c;
+ arg5 = arg6 = ~0U;
+ if (s0<se1) {
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(s0,s1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) { // Coordinates specified as a vector
+ p2 = _cimg_mp_vector_size(arg1);
+ ++arg1;
+ if (p2>1) {
+ arg2 = arg1 + 1;
+ if (p2>2) {
+ arg3 = arg2 + 1;
+ if (p2>3) arg4 = arg3 + 1;
+ }
+ }
+ if (s1<se1) {
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg5 = compile(s1,s2,depth1,0);
+ arg6 = s2<se1?compile(++s2,se1,depth1,0):~0U;
+ }
+ } else if (s1<se1) {
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(s1,s2,depth1,0);
+ if (s2<se1) {
+ s3 = ++s2; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg3 = compile(s2,s3,depth1,0);
+ if (s3<se1) {
+ s2 = ++s3; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg4 = compile(s3,s2,depth1,0);
+ if (s2<se1) {
+ s3 = ++s2; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg5 = compile(s2,s3,depth1,0);
+ arg6 = s3<se1?compile(++s3,se1,depth1,0):~0U;
+ }
+ }
+ }
+ }
+ }
+ if (p_ref && arg5==~0U && arg6==~0U) {
+ *p_ref = 3;
+ p_ref[1] = p1;
+ p_ref[2] = (unsigned int)is_relative;
+ p_ref[3] = arg1;
+ p_ref[4] = arg2;
+ p_ref[5] = arg3;
+ p_ref[6] = arg4;
+ if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -2; // Prevent from being used in further optimization
+ if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -2;
+ if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -2;
+ if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -2;
+ if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -2;
+ }
+
+ if (p1!=~0U) {
+ if (!listin) _cimg_mp_return(0);
+ pos = scalar7(is_relative?mp_list_jxyzc:mp_list_ixyzc,
+ p1,arg1,arg2,arg3,arg4,
+ arg5==~0U?_cimg_mp_interpolation:arg5,
+ arg6==~0U?_cimg_mp_boundary:arg6);
+ } else {
+ if (!imgin) _cimg_mp_return(0);
+ need_input_copy = true;
+ pos = scalar6(is_relative?mp_jxyzc:mp_ixyzc,
+ arg1,arg2,arg3,arg4,
+ arg5==~0U?_cimg_mp_interpolation:arg5,
+ arg6==~0U?_cimg_mp_boundary:arg6);
+ }
+ memtype[pos] = -2; // Prevent from being used in further optimization
+ _cimg_mp_return(pos);
+ }
+
+ // Mathematical functions.
+ switch (*ss) {
+
+ case '_' :
+ if (*ss1=='(') // Skip arguments
+ _cimg_mp_return(_cimg_mp_slot_nan);
+ break;
+
+ case 'a' :
+ if (!std::strncmp(ss,"abs(",4)) { // Absolute value
+ _cimg_mp_op("Function 'abs()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_abs,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::abs(mem[arg1]));
+ _cimg_mp_scalar1(mp_abs,arg1);
+ }
+
+ if (!std::strncmp(ss,"acos(",5)) { // Arccos
+ _cimg_mp_op("Function 'acos()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_acos,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::acos(mem[arg1]));
+ _cimg_mp_scalar1(mp_acos,arg1);
+ }
+
+ if (!std::strncmp(ss,"arg(",4)) { // Nth argument
+ _cimg_mp_op("Function 'arg()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ _cimg_mp_check_type(arg1,1,1,0);
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(s1,s2,depth1,0);
+ p2 = _cimg_mp_vector_size(arg2);
+ p3 = 3;
+ CImg<ulongT>::vector((ulongT)mp_arg,0,0,p2,arg1,arg2).move_to(_opcode);
+ for (s = ++s2; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ arg3 = compile(s,ns,depth1,0);
+ _cimg_mp_check_type(arg3,p3,p2?2:1,p2);
+ CImg<ulongT>::vector(arg3).move_to(_opcode);
+ ++p3;
+ s = ns;
+ }
+ (_opcode>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ if (_cimg_mp_is_constant(arg1)) {
+ p3-=1; // Number of args
+ arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]);
+ if (arg1<p3) _cimg_mp_return(opcode[4 + arg1]);
+ if (p2) {
+ pos = vector(p2);
+ std::memset(&mem[pos] + 1,0,p2*sizeof(double));
+ _cimg_mp_return(pos);
+ } else _cimg_mp_return(0);
+ }
+ pos = opcode[1] = p2?vector(p2):scalar();
+ opcode.move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"asin(",5)) { // Arcsin
+ _cimg_mp_op("Function 'asin()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_asin,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::asin(mem[arg1]));
+ _cimg_mp_scalar1(mp_asin,arg1);
+ }
+
+ if (!std::strncmp(ss,"atan(",5)) { // Arctan
+ _cimg_mp_op("Function 'atan()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_atan,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::atan(mem[arg1]));
+ _cimg_mp_scalar1(mp_atan,arg1);
+ }
+
+ if (!std::strncmp(ss,"atan2(",6)) { // Arctan2
+ _cimg_mp_op("Function 'atan2()'");
+ s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss6,s1,depth1,0);
+ arg2 = compile(++s1,se1,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_atan2,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_atan2,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_atan2,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant(std::atan2(mem[arg1],mem[arg2]));
+ _cimg_mp_scalar2(mp_atan2,arg1,arg2);
+ }
+ break;
+
+ case 'b' :
+ if (!std::strncmp(ss,"bool(",5)) { // Boolean cast
+ _cimg_mp_op("Function 'bool()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((bool)mem[arg1]);
+ _cimg_mp_scalar1(mp_bool,arg1);
+ }
+
+ if (!std::strncmp(ss,"break(",6)) { // Complex absolute value
+ if (pexpr[se2 - expr._data]=='(') { // no arguments?
+ CImg<ulongT>::vector((ulongT)mp_break,_cimg_mp_slot_nan).move_to(code);
+ _cimg_mp_return(_cimg_mp_slot_nan);
+ }
+ }
+
+ if (!std::strncmp(ss,"breakpoint(",11)) { // Break point (for abort test)
+ _cimg_mp_op("Function 'breakpoint()'");
+ if (pexpr[se2 - expr._data]=='(') { // no arguments?
+ CImg<ulongT>::vector((ulongT)mp_breakpoint,_cimg_mp_slot_nan).move_to(code);
+ _cimg_mp_return(_cimg_mp_slot_nan);
+ }
+ }
+ break;
+
+ case 'c' :
+ if (!std::strncmp(ss,"cabs(",5)) { // Complex absolute value
+ _cimg_mp_op("Function 'cabs()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ _cimg_mp_check_type(arg1,0,2,2);
+ _cimg_mp_scalar2(mp_complex_abs,arg1 + 1,arg1 + 2);
+ }
+
+ if (!std::strncmp(ss,"carg(",5)) { // Complex argument
+ _cimg_mp_op("Function 'carg()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ _cimg_mp_check_type(arg1,0,2,2);
+ _cimg_mp_scalar2(mp_atan2,arg1 + 2,arg1 + 1);
+ }
+
+ if (!std::strncmp(ss,"cbrt(",5)) { // Cubic root
+ _cimg_mp_op("Function 'cbrt()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cbrt,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::cbrt(mem[arg1]));
+ _cimg_mp_scalar1(mp_cbrt,arg1);
+ }
+
+ if (!std::strncmp(ss,"cconj(",6)) { // Complex conjugate
+ _cimg_mp_op("Function 'cconj()'");
+ arg1 = compile(ss6,se1,depth1,0);
+ _cimg_mp_check_type(arg1,0,2,2);
+ pos = vector(2);
+ CImg<ulongT>::vector((ulongT)mp_complex_conj,pos,arg1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"cexp(",5)) { // Complex exponential
+ _cimg_mp_op("Function 'cexp()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ _cimg_mp_check_type(arg1,0,2,2);
+ pos = vector(2);
+ CImg<ulongT>::vector((ulongT)mp_complex_exp,pos,arg1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"clog(",5)) { // Complex logarithm
+ _cimg_mp_op("Function 'clog()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ _cimg_mp_check_type(arg1,0,2,2);
+ pos = vector(2);
+ CImg<ulongT>::vector((ulongT)mp_complex_log,pos,arg1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"continue(",9)) { // Complex absolute value
+ if (pexpr[se2 - expr._data]=='(') { // no arguments?
+ CImg<ulongT>::vector((ulongT)mp_continue,_cimg_mp_slot_nan).move_to(code);
+ _cimg_mp_return(_cimg_mp_slot_nan);
+ }
+ }
+
+ if (!std::strncmp(ss,"copy(",5)) { // Memory copy
+ _cimg_mp_op("Function 'copy()'");
+ ref.assign(14);
+ s1 = ss5; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = p1 = compile(ss5,s1,depth1,ref);
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(s1,s2,depth1,ref._data + 7);
+ arg3 = ~0U; arg4 = arg5 = arg6 = 1;
+ if (s2<se1) {
+ s3 = ++s2; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg3 = compile(s2,s3,depth1,0);
+ if (s3<se1) {
+ s1 = ++s3; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg4 = compile(s3,s1,depth1,0);
+ if (s1<se1) {
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg5 = compile(s1,s2,depth1,0);
+ arg6 = s2<se1?compile(++s2,se1,depth1,0):1;
+ }
+ }
+ }
+ if (_cimg_mp_is_vector(arg1) && !ref[0]) ++arg1;
+ if (_cimg_mp_is_vector(arg2)) {
+ if (arg3==~0U) arg3 = _cimg_mp_vector_size(arg2);
+ if (!ref[7]) ++arg2;
+ }
+ if (arg3==~0U) arg3 = 1;
+ _cimg_mp_check_type(arg3,3,1,0);
+ _cimg_mp_check_type(arg4,4,1,0);
+ _cimg_mp_check_type(arg5,5,1,0);
+ _cimg_mp_check_type(arg6,5,1,0);
+ CImg<ulongT>(1,22).move_to(code);
+ code.back().get_shared_rows(0,7).fill((ulongT)mp_memcopy,p1,arg1,arg2,arg3,arg4,arg5,arg6);
+ code.back().get_shared_rows(8,21).fill(ref);
+ _cimg_mp_return(p1);
+ }
+
+ if (!std::strncmp(ss,"cos(",4)) { // Cosine
+ _cimg_mp_op("Function 'cos()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cos,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cos(mem[arg1]));
+ _cimg_mp_scalar1(mp_cos,arg1);
+ }
+
+ if (!std::strncmp(ss,"cosh(",5)) { // Hyperbolic cosine
+ _cimg_mp_op("Function 'cosh()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_cosh,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::cosh(mem[arg1]));
+ _cimg_mp_scalar1(mp_cosh,arg1);
+ }
+
+ if (!std::strncmp(ss,"crop(",5)) { // Image crop
+ _cimg_mp_op("Function 'crop()'");
+ if (*ss5=='#') { // Index specified
+ s0 = ss6; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss6,s0++,depth1,0);
+ _cimg_mp_check_list(false);
+ } else { p1 = ~0U; s0 = ss5; need_input_copy = true; }
+ pos = 0;
+ is_sth = false; // Coordinates specified as a vector?
+ if (ss5<se1) for (s = s0; s<se; ++s, ++pos) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ arg1 = compile(s,ns,depth1,0);
+ if (!pos && _cimg_mp_is_vector(arg1)) { // Coordinates specified as a vector
+ opcode = CImg<ulongT>::sequence(_cimg_mp_vector_size(arg1),arg1 + 1,
+ arg1 + (ulongT)_cimg_mp_vector_size(arg1));
+ opcode.resize(1,std::min(opcode._height,4U),1,1,0).move_to(_opcode);
+ is_sth = true;
+ } else {
+ _cimg_mp_check_type(arg1,pos + 1,1,0);
+ CImg<ulongT>::vector(arg1).move_to(_opcode);
+ }
+ s = ns;
+ }
+ (_opcode>'y').move_to(opcode);
+
+ arg1 = 0; arg2 = (p1!=~0U);
+ switch (opcode._height) {
+ case 0 : case 1 :
+ CImg<ulongT>::vector(0,0,0,0,~0U,~0U,~0U,~0U,0).move_to(opcode);
+ break;
+ case 2 :
+ CImg<ulongT>::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,_cimg_mp_boundary).move_to(opcode);
+ arg1 = arg2?3:2;
+ break;
+ case 3 :
+ CImg<ulongT>::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode);
+ arg1 = arg2?3:2;
+ break;
+ case 4 :
+ CImg<ulongT>::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,_cimg_mp_boundary).
+ move_to(opcode);
+ arg1 = (is_sth?2:1) + arg2;
+ break;
+ case 5 :
+ CImg<ulongT>::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,opcode[4]).
+ move_to(opcode);
+ arg1 = (is_sth?2:1) + arg2;
+ break;
+ case 6 :
+ CImg<ulongT>::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U,
+ _cimg_mp_boundary).move_to(opcode);
+ arg1 = (is_sth?2:4) + arg2;
+ break;
+ case 7 :
+ CImg<ulongT>::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U,
+ opcode[6]).move_to(opcode);
+ arg1 = (is_sth?2:4) + arg2;
+ break;
+ case 8 :
+ CImg<ulongT>::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4],opcode[5],opcode[6],
+ opcode[7],_cimg_mp_boundary).move_to(opcode);
+ arg1 = (is_sth?2:5) + arg2;
+ break;
+ case 9 :
+ arg1 = (is_sth?2:5) + arg2;
+ break;
+ default : // Error -> too much arguments
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Too much arguments specified, "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+
+ _cimg_mp_check_type((unsigned int)*opcode,arg2 + 1,1,0);
+ _cimg_mp_check_type((unsigned int)opcode[1],arg2 + 1 + (is_sth?0:1),1,0);
+ _cimg_mp_check_type((unsigned int)opcode[2],arg2 + 1 + (is_sth?0:2),1,0);
+ _cimg_mp_check_type((unsigned int)opcode[3],arg2 + 1 + (is_sth?0:3),1,0);
+ if (opcode[4]!=(ulongT)~0U) {
+ _cimg_mp_check_constant((unsigned int)opcode[4],arg1,3);
+ opcode[4] = (ulongT)mem[opcode[4]];
+ }
+ if (opcode[5]!=(ulongT)~0U) {
+ _cimg_mp_check_constant((unsigned int)opcode[5],arg1 + 1,3);
+ opcode[5] = (ulongT)mem[opcode[5]];
+ }
+ if (opcode[6]!=(ulongT)~0U) {
+ _cimg_mp_check_constant((unsigned int)opcode[6],arg1 + 2,3);
+ opcode[6] = (ulongT)mem[opcode[6]];
+ }
+ if (opcode[7]!=(ulongT)~0U) {
+ _cimg_mp_check_constant((unsigned int)opcode[7],arg1 + 3,3);
+ opcode[7] = (ulongT)mem[opcode[7]];
+ }
+ _cimg_mp_check_type((unsigned int)opcode[8],arg1 + 4,1,0);
+
+ if (opcode[4]==(ulongT)~0U || opcode[5]==(ulongT)~0U ||
+ opcode[6]==(ulongT)~0U || opcode[7]==(ulongT)~0U) {
+ if (p1!=~0U) {
+ _cimg_mp_check_constant(p1,1,1);
+ p1 = (unsigned int)cimg::mod((int)mem[p1],listin.width());
+ }
+ const CImg<T> &img = p1!=~0U?listin[p1]:imgin;
+ if (!img) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Cannot crop empty image when "
+ "some xyzc-coordinates are unspecified, in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ if (opcode[4]==(ulongT)~0U) opcode[4] = (ulongT)img._width;
+ if (opcode[5]==(ulongT)~0U) opcode[5] = (ulongT)img._height;
+ if (opcode[6]==(ulongT)~0U) opcode[6] = (ulongT)img._depth;
+ if (opcode[7]==(ulongT)~0U) opcode[7] = (ulongT)img._spectrum;
+ }
+
+ pos = vector((unsigned int)(opcode[4]*opcode[5]*opcode[6]*opcode[7]));
+ CImg<ulongT>::vector((ulongT)mp_image_crop,
+ pos,p1,
+ *opcode,opcode[1],opcode[2],opcode[3],
+ opcode[4],opcode[5],opcode[6],opcode[7],
+ opcode[8]).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"cross(",6)) { // Cross product
+ _cimg_mp_op("Function 'cross()'");
+ s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss6,s1,depth1,0);
+ arg2 = compile(++s1,se1,depth1,0);
+ _cimg_mp_check_type(arg1,1,2,3);
+ _cimg_mp_check_type(arg2,2,2,3);
+ pos = vector(3);
+ CImg<ulongT>::vector((ulongT)mp_cross,pos,arg1,arg2).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"cut(",4)) { // Cut
+ _cimg_mp_op("Function 'cut()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(++s1,s2,depth1,0);
+ arg3 = compile(++s2,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector3_vss(mp_cut,arg1,arg2,arg3);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2) && _cimg_mp_is_constant(arg3)) {
+ val = mem[arg1];
+ val1 = mem[arg2];
+ val2 = mem[arg3];
+ _cimg_mp_constant(val<val1?val1:val>val2?val2:val);
+ }
+ _cimg_mp_scalar3(mp_cut,arg1,arg2,arg3);
+ }
+ break;
+
+ case 'd' :
+ if (!std::strncmp(ss,"d(",2)) { // Image depth
+ _cimg_mp_op("Function 'd()'");
+ if (*ss2=='#') { p1 = compile(ss3,se1,depth1,0); _cimg_mp_check_list(false); } // Index specified
+ else { if (ss2!=se1) break; p1 = ~0U; }
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_image_d,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"date(",5)) { // Date and file date
+ _cimg_mp_op("Function 'date()'");
+ s1 = ss5; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = 0;
+ is_sth = s1!=se1; // is_fdate
+ if (s1==se1 && ss5!=se1 && // Exactly one argument
+ (cimg_sscanf(ss5,"%u%c",&arg1,&sep)!=2 || sep!=')')) is_sth = true;
+ if (is_sth) {
+ if (cimg_sscanf(ss5,"%u%c",&arg1,&sep)!=2 || sep!=',') { arg1 = 0; s1 = ss4; }
+ *se1 = 0; val = (double)cimg::fdate(++s1,arg1); *se1 = ')';
+ } else val = (double)cimg::date(arg1);
+ _cimg_mp_constant(val);
+ }
+
+ if (!std::strncmp(ss,"debug(",6)) { // Print debug info
+ _cimg_mp_op("Function 'debug()'");
+ p1 = code._width;
+ arg1 = compile(ss6,se1,depth1,p_ref);
+ *se1 = 0;
+ variable_name.assign(CImg<charT>::string(ss6,true,true).unroll('y'),true);
+ cimg::strpare(variable_name,false,true);
+ ((CImg<ulongT>::vector((ulongT)mp_debug,arg1,0,code._width - p1),
+ variable_name)>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ opcode.move_to(code,p1);
+ *se1 = ')';
+ _cimg_mp_return(arg1);
+ }
+
+ if (!std::strncmp(ss,"display(",8)) { // Display memory
+ _cimg_mp_op("Function 'display()'");
+ if (pexpr[se2 - expr._data]=='(') { // no arguments?
+ CImg<ulongT>::vector((ulongT)mp_display_memory,_cimg_mp_slot_nan).move_to(code);
+ _cimg_mp_return(_cimg_mp_slot_nan);
+ }
+ s1 = ss8; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss8,s1,depth1,0);
+ arg2 = 0; arg3 = arg4 = arg5 = 1;
+ if (s1<se1) {
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(s1 + 1,s2,depth1,0);
+ if (s2<se1) {
+ s3 = ++s2; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg3 = compile(s2,s3,depth1,0);
+ if (s3<se1) {
+ s2 = ++s3; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg4 = compile(s3,s2,depth1,0);
+ arg5 = s2<se1?compile(++s2,se1,depth1,0):0;
+ }
+ }
+ }
+ _cimg_mp_check_type(arg2,2,1,0);
+ _cimg_mp_check_type(arg3,3,1,0);
+ _cimg_mp_check_type(arg4,4,1,0);
+ _cimg_mp_check_type(arg5,5,1,0);
+
+ c1 = *s1; *s1 = 0;
+ variable_name.assign(CImg<charT>::string(ss8,true,true).unroll('y'),true);
+ cimg::strpare(variable_name,false,true);
+ if (_cimg_mp_is_vector(arg1))
+ ((CImg<ulongT>::vector((ulongT)mp_vector_print,arg1,0,(ulongT)_cimg_mp_vector_size(arg1)),
+ variable_name)>'y').move_to(opcode);
+ else
+ ((CImg<ulongT>::vector((ulongT)mp_print,arg1,0),
+ variable_name)>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ opcode.move_to(code);
+
+ ((CImg<ulongT>::vector((ulongT)mp_display,arg1,0,(ulongT)_cimg_mp_vector_size(arg1),
+ arg2,arg3,arg4,arg5),
+ variable_name)>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ opcode.move_to(code);
+ *s1 = c1;
+ _cimg_mp_return(arg1);
+ }
+
+ if (!std::strncmp(ss,"det(",4)) { // Matrix determinant
+ _cimg_mp_op("Function 'det()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ _cimg_mp_check_matrix_square(arg1,1);
+ p1 = (unsigned int)std::sqrt((float)_cimg_mp_vector_size(arg1));
+ _cimg_mp_scalar2(mp_det,arg1,p1);
+ }
+
+ if (!std::strncmp(ss,"diag(",5)) { // Diagonal matrix
+ _cimg_mp_op("Function 'diag()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ _cimg_mp_check_type(arg1,1,2,0);
+ p1 = _cimg_mp_vector_size(arg1);
+ pos = vector(p1*p1);
+ CImg<ulongT>::vector((ulongT)mp_diag,pos,arg1,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"dot(",4)) { // Dot product
+ _cimg_mp_op("Function 'dot()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ arg2 = compile(++s1,se1,depth1,0);
+ _cimg_mp_check_type(arg1,1,2,0);
+ _cimg_mp_check_type(arg2,2,2,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_scalar3(mp_dot,arg1,arg2,_cimg_mp_vector_size(arg1));
+ _cimg_mp_scalar2(mp_mul,arg1,arg2);
+ }
+
+ if (!std::strncmp(ss,"do(",3) || !std::strncmp(ss,"dowhile(",8)) { // Do..while
+ _cimg_mp_op("Function 'dowhile()'");
+ s0 = *ss2=='('?ss3:ss8;
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = code._width;
+ arg6 = mempos;
+ p1 = compile(s0,s1,depth1,0); // Body
+ arg2 = code._width;
+ p2 = s1<se1?compile(++s1,se1,depth1,0):p1; // Condition
+ _cimg_mp_check_type(p2,2,1,0);
+ CImg<ulongT>::vector((ulongT)mp_dowhile,p1,p2,arg2 - arg1,code._width - arg2,_cimg_mp_vector_size(p1),
+ p1>=arg6 && !_cimg_mp_is_constant(p1),
+ p2>=arg6 && !_cimg_mp_is_constant(p2)).move_to(code,arg1);
+ _cimg_mp_return(p1);
+ }
+
+ if (!std::strncmp(ss,"draw(",5)) { // Draw image
+ is_parallelizable = false;
+ _cimg_mp_op("Function 'draw()'");
+ if (*ss5=='#') { // Index specified
+ s0 = ss6; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss6,s0++,depth1,0);
+ _cimg_mp_check_list(true);
+ } else { p1 = ~0U; s0 = ss5; }
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(s0,s1,depth1,0);
+ arg2 = is_relative?0U:(unsigned int)_cimg_mp_slot_x;
+ arg3 = is_relative?0U:(unsigned int)_cimg_mp_slot_y;
+ arg4 = is_relative?0U:(unsigned int)_cimg_mp_slot_z;
+ arg5 = is_relative?0U:(unsigned int)_cimg_mp_slot_c;
+ s0 = se1;
+ if (s1<se1) {
+ s0 = s1 + 1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ arg2 = compile(++s1,s0,depth1,0);
+ if (_cimg_mp_is_vector(arg2)) { // Coordinates specified as a vector
+ p2 = _cimg_mp_vector_size(arg2);
+ ++arg2;
+ if (p2>1) {
+ arg3 = arg2 + 1;
+ if (p2>2) {
+ arg4 = arg3 + 1;
+ if (p2>3) arg5 = arg4 + 1;
+ }
+ }
+ ++s0;
+ is_sth = true;
+ } else {
+ if (s0<se1) {
+ is_sth = p1!=~0U;
+ s1 = s0 + 1; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg3 = compile(++s0,s1,depth1,0);
+ _cimg_mp_check_type(arg3,is_sth?4:3,1,0);
+ if (s1<se1) {
+ s0 = s1 + 1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ arg4 = compile(++s1,s0,depth1,0);
+ _cimg_mp_check_type(arg4,is_sth?5:4,1,0);
+ if (s0<se1) {
+ s1 = s0 + 1; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg5 = compile(++s0,s1,depth1,0);
+ _cimg_mp_check_type(arg5,is_sth?6:5,1,0);
+ s0 = ++s1;
+ }
+ }
+ }
+ is_sth = false;
+ }
+ }
+
+ CImg<ulongT>::vector((ulongT)mp_image_draw,arg1,(ulongT)_cimg_mp_vector_size(arg1),p1,arg2,arg3,arg4,arg5,
+ 0,0,0,0,1,(ulongT)~0U,0,1).move_to(opcode);
+
+ arg2 = arg3 = arg4 = arg5 = ~0U;
+ p2 = p1!=~0U?0:1;
+ if (s0<se1) {
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg2 = compile(s0,s1,depth1,0);
+ _cimg_mp_check_type(arg2,p2 + (is_sth?3:6),1,0);
+ if (s1<se1) {
+ s0 = s1 + 1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ arg3 = compile(++s1,s0,depth1,0);
+ _cimg_mp_check_type(arg3,p2 + (is_sth?4:7),1,0);
+ if (s0<se1) {
+ s1 = s0 + 1; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg4 = compile(++s0,s1,depth1,0);
+ _cimg_mp_check_type(arg4,p2 + (is_sth?5:8),1,0);
+ if (s1<se1) {
+ s0 = s1 + 1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ arg5 = compile(++s1,s0,depth1,0);
+ _cimg_mp_check_type(arg5,p2 + (is_sth?6:9),1,0);
+ }
+ }
+ }
+ }
+ if (s0<s1) s0 = s1;
+
+ opcode[8] = (ulongT)arg2;
+ opcode[9] = (ulongT)arg3;
+ opcode[10] = (ulongT)arg4;
+ opcode[11] = (ulongT)arg5;
+
+ if (s0<se1) {
+ s1 = s0 + 1; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg6 = compile(++s0,s1,depth1,0);
+ _cimg_mp_check_type(arg6,0,1,0);
+ opcode[12] = arg6;
+ if (s1<se1) {
+ s0 = s1 + 1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p2 = compile(++s1,s0,depth1,0);
+ _cimg_mp_check_type(p2,0,2,0);
+ opcode[13] = p2;
+ opcode[14] = _cimg_mp_vector_size(p2);
+ p3 = s0<se1?compile(++s0,se1,depth1,0):1;
+ _cimg_mp_check_type(p3,0,1,0);
+ opcode[15] = p3;
+ }
+ }
+ opcode.move_to(code);
+ _cimg_mp_return(arg1);
+ }
+ break;
+
+ case 'e' :
+ if (!std::strncmp(ss,"eig(",4)) { // Matrix eigenvalues/eigenvector
+ _cimg_mp_op("Function 'eig()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ _cimg_mp_check_matrix_square(arg1,1);
+ p1 = (unsigned int)std::sqrt((float)_cimg_mp_vector_size(arg1));
+ pos = vector((p1 + 1)*p1);
+ CImg<ulongT>::vector((ulongT)mp_matrix_eig,pos,arg1,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"end(",4)) { // End
+ _cimg_mp_op("Function 'end()'");
+ code.swap(code_end);
+ arg1 = compile(ss4,se1,depth1,p_ref);
+ code.swap(code_end);
+ _cimg_mp_return(arg1);
+ }
+
+ if (!std::strncmp(ss,"exp(",4)) { // Exponential
+ _cimg_mp_op("Function 'exp()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_exp,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::exp(mem[arg1]));
+ _cimg_mp_scalar1(mp_exp,arg1);
+ }
+
+ if (!std::strncmp(ss,"eye(",4)) { // Identity matrix
+ _cimg_mp_op("Function 'eye()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ _cimg_mp_check_constant(arg1,1,3);
+ p1 = (unsigned int)mem[arg1];
+ pos = vector(p1*p1);
+ CImg<ulongT>::vector((ulongT)mp_eye,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 'f' :
+ if (!std::strncmp(ss,"fact(",5)) { // Factorial
+ _cimg_mp_op("Function 'fact()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_factorial,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::factorial(mem[arg1]));
+ _cimg_mp_scalar1(mp_factorial,arg1);
+ }
+
+ if (!std::strncmp(ss,"fibo(",5)) { // Fibonacci
+ _cimg_mp_op("Function 'fibo()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_fibonacci,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::fibonacci(mem[arg1]));
+ _cimg_mp_scalar1(mp_fibonacci,arg1);
+ }
+
+ if (!std::strncmp(ss,"find(",5)) { // Find
+ _cimg_mp_op("Function 'find()'");
+
+ // First argument: data to look at.
+ s0 = ss5; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ if (*ss5=='#') { // Index specified
+ p1 = compile(ss6,s0,depth1,0);
+ _cimg_mp_check_list(false);
+ arg1 = ~0U;
+ } else { // Vector specified
+ arg1 = compile(ss5,s0,depth1,0);
+ _cimg_mp_check_type(arg1,1,2,0);
+ p1 = ~0U;
+ }
+
+ // Second argument: data to find.
+ s1 = ++s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg2 = compile(s0,s1,depth1,0);
+
+ // Third and fourth arguments: search direction and starting index.
+ arg3 = 1; arg4 = _cimg_mp_slot_nan;
+ if (s1<se1) {
+ s0 = s1 + 1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ arg3 = compile(++s1,s0,depth1,0);
+ _cimg_mp_check_type(arg3,3,1,0);
+ if (s0<se1) {
+ arg4 = compile(++s0,se1,depth1,0);
+ _cimg_mp_check_type(arg4,4,1,0);
+ }
+ }
+ if (p1!=~0U) {
+ if (_cimg_mp_is_vector(arg2))
+ _cimg_mp_scalar5(mp_list_find_seq,p1,arg2,_cimg_mp_vector_size(arg2),arg3,arg4);
+ _cimg_mp_scalar4(mp_list_find,p1,arg2,arg3,arg4);
+ }
+ if (_cimg_mp_is_vector(arg2))
+ _cimg_mp_scalar6(mp_find_seq,arg1,_cimg_mp_vector_size(arg1),arg2,_cimg_mp_vector_size(arg2),arg3,arg4);
+ _cimg_mp_scalar5(mp_find,arg1,_cimg_mp_vector_size(arg1),arg2,arg3,arg4);
+ }
+
+ if (*ss1=='o' && *ss2=='r' && *ss3=='(') { // For loop
+ _cimg_mp_op("Function 'for()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ s3 = s2 + 1; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg1 = code._width;
+ p1 = compile(ss4,s1,depth1,0); // Init
+ arg2 = code._width;
+ p2 = compile(++s1,s2,depth1,0); // Cond
+ arg3 = code._width;
+ arg6 = mempos;
+ if (s3<se1) { // Body + post
+ p3 = compile(s3 + 1,se1,depth1,0); // Body
+ arg4 = code._width;
+ pos = compile(++s2,s3,depth1,0); // Post
+ } else {
+ p3 = compile(++s2,se1,depth1,0); // Body only
+ arg4 = pos = code._width;
+ }
+ _cimg_mp_check_type(p2,2,1,0);
+ arg5 = _cimg_mp_vector_size(pos);
+ CImg<ulongT>::vector((ulongT)mp_for,p3,(ulongT)_cimg_mp_vector_size(p3),p2,arg2 - arg1,arg3 - arg2,
+ arg4 - arg3,code._width - arg4,
+ p3>=arg6 && !_cimg_mp_is_constant(p3),
+ p2>=arg6 && !_cimg_mp_is_constant(p2)).move_to(code,arg1);
+ _cimg_mp_return(p3);
+ }
+ break;
+
+ case 'g' :
+ if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function
+ _cimg_mp_op("Function 'gauss()'");
+ s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss6,s1,depth1,0);
+ arg2 = s1<se1?compile(++s1,se1,depth1,0):1;
+ _cimg_mp_check_type(arg2,2,1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(mp_gauss,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) {
+ val1 = mem[arg1];
+ val2 = mem[arg2];
+ _cimg_mp_constant(std::exp(-val1*val1/(2*val2*val2))/std::sqrt(2*val2*val2*cimg::PI));
+ }
+ _cimg_mp_scalar2(mp_gauss,arg1,arg2);
+ }
+ break;
+
+ case 'h' :
+ if (!std::strncmp(ss,"h(",2)) { // Image height
+ _cimg_mp_op("Function 'h()'");
+ if (*ss2=='#') { p1 = compile(ss3,se1,depth1,0); _cimg_mp_check_list(false); } // Index specified
+ else { if (ss2!=se1) break; p1 = ~0U; }
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_image_h,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ case 'i' :
+ if (*ss1=='f' && *ss2=='(') { // If..then[..else.]
+ _cimg_mp_op("Function 'if()'");
+ s1 = ss3; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg1 = compile(ss3,s1,depth1,0);
+ _cimg_mp_check_type(arg1,1,1,0);
+ if (_cimg_mp_is_constant(arg1)) {
+ if ((bool)mem[arg1]) return compile(++s1,s2,depth1,0);
+ else return s2<se1?compile(++s2,se1,depth1,0):0;
+ }
+ p2 = code._width;
+ arg2 = compile(++s1,s2,depth1,0);
+ p3 = code._width;
+ arg3 = s2<se1?compile(++s2,se1,depth1,0):_cimg_mp_is_vector(arg2)?vector(_cimg_mp_vector_size(arg2),0):0;
+ _cimg_mp_check_type(arg3,3,_cimg_mp_is_vector(arg2)?2:1,_cimg_mp_vector_size(arg2));
+ arg4 = _cimg_mp_vector_size(arg2);
+ if (arg4) pos = vector(arg4); else pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_if,pos,arg1,arg2,arg3,
+ p3 - p2,code._width - p3,arg4).move_to(code,p2);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"init(",5)) { // Init
+ _cimg_mp_op("Function 'init()'");
+ code.swap(code_init);
+ arg1 = compile(ss5,se1,depth1,p_ref);
+ code.swap(code_init);
+ _cimg_mp_return(arg1);
+ }
+
+ if (!std::strncmp(ss,"int(",4)) { // Integer cast
+ _cimg_mp_op("Function 'int()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_int,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant((longT)mem[arg1]);
+ _cimg_mp_scalar1(mp_int,arg1);
+ }
+
+ if (!std::strncmp(ss,"inv(",4)) { // Matrix/scalar inversion
+ _cimg_mp_op("Function 'inv()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) {
+ _cimg_mp_check_matrix_square(arg1,1);
+ p1 = (unsigned int)std::sqrt((float)_cimg_mp_vector_size(arg1));
+ pos = vector(p1*p1);
+ CImg<ulongT>::vector((ulongT)mp_matrix_inv,pos,arg1,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(1/mem[arg1]);
+ _cimg_mp_scalar2(mp_div,1,arg1);
+ }
+
+ if (*ss1=='s') { // Family of 'is_?()' functions
+
+ if (!std::strncmp(ss,"isbool(",7)) { // Is boolean?
+ _cimg_mp_op("Function 'isbool()'");
+ if (ss7==se1) _cimg_mp_return(0);
+ arg1 = compile(ss7,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isbool,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return(mem[arg1]==0.0 || mem[arg1]==1.0);
+ _cimg_mp_scalar1(mp_isbool,arg1);
+ }
+
+ if (!std::strncmp(ss,"isdir(",6)) { // Is directory?
+ _cimg_mp_op("Function 'isdir()'");
+ *se1 = 0;
+ is_sth = cimg::is_directory(ss6);
+ *se1 = ')';
+ _cimg_mp_return(is_sth?1U:0U);
+ }
+
+ if (!std::strncmp(ss,"isfile(",7)) { // Is file?
+ _cimg_mp_op("Function 'isfile()'");
+ *se1 = 0;
+ is_sth = cimg::is_file(ss7);
+ *se1 = ')';
+ _cimg_mp_return(is_sth?1U:0U);
+ }
+
+ if (!std::strncmp(ss,"isin(",5)) { // Is in sequence/vector?
+ if (ss5>=se1) _cimg_mp_return(0);
+ _cimg_mp_op("Function 'isin()'");
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_isin,pos,0).move_to(_opcode);
+ for (s = ss5; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ arg1 = compile(s,ns,depth1,0);
+ if (_cimg_mp_is_vector(arg1))
+ CImg<ulongT>::sequence(_cimg_mp_vector_size(arg1),arg1 + 1,
+ arg1 + (ulongT)_cimg_mp_vector_size(arg1)).
+ move_to(_opcode);
+ else CImg<ulongT>::vector(arg1).move_to(_opcode);
+ s = ns;
+ }
+ (_opcode>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ opcode.move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"isinf(",6)) { // Is infinite?
+ _cimg_mp_op("Function 'isinf()'");
+ if (ss6==se1) _cimg_mp_return(0);
+ arg1 = compile(ss6,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isinf,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return((unsigned int)cimg::type<double>::is_inf(mem[arg1]));
+ _cimg_mp_scalar1(mp_isinf,arg1);
+ }
+
+ if (!std::strncmp(ss,"isint(",6)) { // Is integer?
+ _cimg_mp_op("Function 'isint()'");
+ if (ss6==se1) _cimg_mp_return(0);
+ arg1 = compile(ss6,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isint,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return((unsigned int)(cimg::mod(mem[arg1],1.0)==0));
+ _cimg_mp_scalar1(mp_isint,arg1);
+ }
+
+ if (!std::strncmp(ss,"isnan(",6)) { // Is NaN?
+ _cimg_mp_op("Function 'isnan()'");
+ if (ss6==se1) _cimg_mp_return(0);
+ arg1 = compile(ss6,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_isnan,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_return((unsigned int)cimg::type<double>::is_nan(mem[arg1]));
+ _cimg_mp_scalar1(mp_isnan,arg1);
+ }
+
+ if (!std::strncmp(ss,"isval(",6)) { // Is value?
+ _cimg_mp_op("Function 'isval()'");
+ val = 0;
+ if (cimg_sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1);
+ _cimg_mp_return(0);
+ }
+
+ }
+ break;
+
+ case 'l' :
+ if (!std::strncmp(ss,"log(",4)) { // Natural logarithm
+ _cimg_mp_op("Function 'log()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log(mem[arg1]));
+ _cimg_mp_scalar1(mp_log,arg1);
+ }
+
+ if (!std::strncmp(ss,"log2(",5)) { // Base-2 logarithm
+ _cimg_mp_op("Function 'log2()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log2,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::log2(mem[arg1]));
+ _cimg_mp_scalar1(mp_log2,arg1);
+ }
+
+ if (!std::strncmp(ss,"log10(",6)) { // Base-10 logarithm
+ _cimg_mp_op("Function 'log10()'");
+ arg1 = compile(ss6,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_log10,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::log10(mem[arg1]));
+ _cimg_mp_scalar1(mp_log10,arg1);
+ }
+
+ if (!std::strncmp(ss,"lowercase(",10)) { // Lower case
+ _cimg_mp_op("Function 'lowercase()'");
+ arg1 = compile(ss + 10,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_lowercase,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::lowercase(mem[arg1]));
+ _cimg_mp_scalar1(mp_lowercase,arg1);
+ }
+ break;
+
+ case 'm' :
+ if (!std::strncmp(ss,"mul(",4)) { // Matrix multiplication
+ _cimg_mp_op("Function 'mul()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(++s1,s2,depth1,0);
+ arg3 = s2<se1?compile(++s2,se1,depth1,0):1;
+ _cimg_mp_check_type(arg1,1,2,0);
+ _cimg_mp_check_type(arg2,2,2,0);
+ _cimg_mp_check_constant(arg3,3,3);
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = _cimg_mp_vector_size(arg2);
+ p3 = (unsigned int)mem[arg3];
+ arg5 = p2/p3;
+ arg4 = p1/arg5;
+ if (arg4*arg5!=p1 || arg5*p3!=p2) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Types of first and second arguments ('%s' and '%s') "
+ "do not match with third argument 'nb_colsB=%u', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ s_type(arg1)._data,s_type(arg2)._data,p3,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ pos = vector(arg4*p3);
+ CImg<ulongT>::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,arg4,arg5,p3).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 'n' :
+ if (!std::strncmp(ss,"narg(",5)) { // Number of arguments
+ _cimg_mp_op("Function 'narg()'");
+ if (ss5>=se1) _cimg_mp_return(0);
+ arg1 = 0;
+ for (s = ss5; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ ++arg1; s = ns;
+ }
+ _cimg_mp_constant(arg1);
+ }
+
+ if ((cimg_sscanf(ss,"norm%u%c",&(arg1=~0U),&sep)==2 && sep=='(') ||
+ !std::strncmp(ss,"norminf(",8) || !std::strncmp(ss,"norm(",5) ||
+ (!std::strncmp(ss,"norm",4) && ss5<se1 && (s=std::strchr(ss5,'('))!=0)) { // Lp norm
+ _cimg_mp_op("Function 'normP()'");
+ if (*ss4=='(') { arg1 = 2; s = ss5; }
+ else if (*ss4=='i' && *ss5=='n' && *ss6=='f' && *ss7=='(') { arg1 = ~0U; s = ss8; }
+ else if (arg1==~0U) {
+ arg1 = compile(ss4,s++,depth1,0);
+ _cimg_mp_check_constant(arg1,0,2);
+ arg1 = (unsigned int)mem[arg1];
+ } else s = std::strchr(ss4,'(') + 1;
+ pos = scalar();
+ switch (arg1) {
+ case 0 :
+ CImg<ulongT>::vector((ulongT)mp_norm0,pos,0).move_to(_opcode); break;
+ case 1 :
+ CImg<ulongT>::vector((ulongT)mp_norm1,pos,0).move_to(_opcode); break;
+ case 2 :
+ CImg<ulongT>::vector((ulongT)mp_norm2,pos,0).move_to(_opcode); break;
+ case ~0U :
+ CImg<ulongT>::vector((ulongT)mp_norminf,pos,0).move_to(_opcode); break;
+ default :
+ CImg<ulongT>::vector((ulongT)mp_normp,pos,0,(ulongT)(arg1==~0U?-1:(int)arg1)).
+ move_to(_opcode);
+ }
+ for ( ; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ arg2 = compile(s,ns,depth1,0);
+ if (_cimg_mp_is_vector(arg2))
+ CImg<ulongT>::sequence(_cimg_mp_vector_size(arg2),arg2 + 1,
+ arg2 + (ulongT)_cimg_mp_vector_size(arg2)).
+ move_to(_opcode);
+ else CImg<ulongT>::vector(arg2).move_to(_opcode);
+ s = ns;
+ }
+
+ (_opcode>'y').move_to(opcode);
+ if (arg1>0 && opcode._height==4) // Special case with one argument and p>=1
+ _cimg_mp_scalar1(mp_abs,opcode[3]);
+ opcode[2] = opcode._height;
+ opcode.move_to(code);
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 'p' :
+ if (!std::strncmp(ss,"permut(",7)) { // Number of permutations
+ _cimg_mp_op("Function 'permut()'");
+ s1 = ss7; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg1 = compile(ss7,s1,depth1,0);
+ arg2 = compile(++s1,s2,depth1,0);
+ arg3 = compile(++s2,se1,depth1,0);
+ _cimg_mp_check_type(arg1,1,1,0);
+ _cimg_mp_check_type(arg2,2,1,0);
+ _cimg_mp_check_type(arg3,3,1,0);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2) && _cimg_mp_is_constant(arg3))
+ _cimg_mp_constant(cimg::permutations(mem[arg1],mem[arg2],(bool)mem[arg3]));
+ _cimg_mp_scalar3(mp_permutations,arg1,arg2,arg3);
+ }
+
+ if (!std::strncmp(ss,"pseudoinv(",10)) { // Matrix/scalar pseudo-inversion
+ _cimg_mp_op("Function 'pseudoinv()'");
+ s1 = ss + 10; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss + 10,s1,depth1,0);
+ arg2 = s1<se1?compile(++s1,se1,depth1,0):1;
+ _cimg_mp_check_type(arg1,1,2,0);
+ _cimg_mp_check_constant(arg2,2,3);
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = (unsigned int)mem[arg2];
+ p3 = p1/p2;
+ if (p3*p2!=p1) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Type of first argument ('%s') "
+ "does not match with second argument 'nb_colsA=%u', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ s_type(arg1)._data,p2,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ pos = vector(p1);
+ CImg<ulongT>::vector((ulongT)mp_matrix_pseudoinv,pos,arg1,p2,p3).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"print(",6)) { // Print expressions
+ _cimg_mp_op("Function 'print()'");
+ for (s = ss6; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ pos = compile(s,ns,depth1,p_ref);
+ c1 = *ns; *ns = 0;
+ variable_name.assign(CImg<charT>::string(s,true,true).unroll('y'),true);
+ cimg::strpare(variable_name,false,true);
+ if (_cimg_mp_is_vector(pos)) // Vector
+ ((CImg<ulongT>::vector((ulongT)mp_vector_print,pos,0,(ulongT)_cimg_mp_vector_size(pos)),
+ variable_name)>'y').move_to(opcode);
+ else // Scalar
+ ((CImg<ulongT>::vector((ulongT)mp_print,pos,0),
+ variable_name)>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ opcode.move_to(code);
+ *ns = c1; s = ns;
+ }
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 'r' :
+ if (!std::strncmp(ss,"resize(",7)) { // Vector or image resize
+ _cimg_mp_op("Function 'resize()'");
+ if (*ss7!='#') { // Vector
+ s1 = ss7; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss7,s1,depth1,0);
+ s2 = ++s1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(s1,s2,depth1,0);
+ arg3 = 1;
+ arg4 = 0;
+ if (s2<se1) {
+ s1 = ++s2; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg3 = compile(s2,s1,depth1,0);
+ arg4 = s1<se1?compile(++s1,se1,depth1,0):0;
+ }
+ _cimg_mp_check_constant(arg2,2,3);
+ arg2 = (unsigned int)mem[arg2];
+ _cimg_mp_check_type(arg3,3,1,0);
+ _cimg_mp_check_type(arg4,4,1,0);
+ pos = vector(arg2);
+ CImg<ulongT>::vector((ulongT)mp_vector_resize,pos,arg2,arg1,(ulongT)_cimg_mp_vector_size(arg1),
+ arg3,arg4).move_to(code);
+ _cimg_mp_return(pos);
+
+ } else { // Image
+ is_parallelizable = false;
+ s0 = ss8; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ p1 = compile(ss8,s0++,depth1,0);
+ _cimg_mp_check_list(true);
+ CImg<ulongT>::vector((ulongT)mp_image_resize,_cimg_mp_slot_nan,p1,~0U,~0U,~0U,~0U,1,0,0,0,0,0).
+ move_to(opcode);
+ pos = 0;
+ for (s = s0; s<se && pos<10; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ arg1 = compile(s,ns,depth1,0);
+ _cimg_mp_check_type(arg1,pos + 2,1,0);
+ opcode[pos + 3] = arg1;
+ s = ns;
+ ++pos;
+ }
+ if (pos<1 || pos>10) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: %s arguments, in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ pos<1?"Missing":"Too much",
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ opcode.move_to(code);
+ _cimg_mp_return(_cimg_mp_slot_nan);
+ }
+ }
+
+ if (!std::strncmp(ss,"reverse(",8)) { // Vector reverse
+ _cimg_mp_op("Function 'reverse()'");
+ arg1 = compile(ss8,se1,depth1,0);
+ if (!_cimg_mp_is_vector(arg1)) _cimg_mp_return(arg1);
+ p1 = _cimg_mp_vector_size(arg1);
+ pos = vector(p1);
+ CImg<ulongT>::vector((ulongT)mp_vector_reverse,pos,arg1,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"rol(",4) || !std::strncmp(ss,"ror(",4)) { // Bitwise rotation
+ _cimg_mp_op(ss[2]=='l'?"Function 'rol()'":"Function 'ror()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1-expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ arg2 = s1<se1?compile(++s1,se1,depth1,0):1;
+ _cimg_mp_check_type(arg2,2,1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector2_vs(*ss2=='l'?mp_rol:mp_ror,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant(*ss2=='l'?cimg::rol(mem[arg1],(unsigned int)mem[arg2]):
+ cimg::ror(mem[arg1],(unsigned int)mem[arg2]));
+ _cimg_mp_scalar2(*ss2=='l'?mp_rol:mp_ror,arg1,arg2);
+ }
+
+ if (!std::strncmp(ss,"rot(",4)) { // 2d/3d rotation matrix
+ _cimg_mp_op("Function 'rot()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ if (s1<se1) { // 3d rotation
+ _cimg_mp_check_type(arg1,1,3,3);
+ is_sth = false; // Is coordinates as vector?
+ if (_cimg_mp_is_vector(arg1)) { // Coordinates specified as a vector
+ is_sth = true;
+ p2 = _cimg_mp_vector_size(arg1);
+ ++arg1;
+ arg2 = arg3 = 0;
+ if (p2>1) {
+ arg2 = arg1 + 1;
+ if (p2>2) arg3 = arg2 + 1;
+ }
+ arg4 = compile(++s1,se1,depth1,0);
+ } else {
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(++s1,s2,depth1,0);
+ s3 = s2 + 1; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg3 = compile(++s2,s3,depth1,0);
+ arg4 = compile(++s3,se1,depth1,0);
+ _cimg_mp_check_type(arg2,2,1,0);
+ _cimg_mp_check_type(arg3,3,1,0);
+ }
+ _cimg_mp_check_type(arg4,is_sth?2:4,1,0);
+ pos = vector(9);
+ CImg<ulongT>::vector((ulongT)mp_rot3d,pos,arg1,arg2,arg3,arg4).move_to(code);
+ } else { // 2d rotation
+ _cimg_mp_check_type(arg1,1,1,0);
+ pos = vector(4);
+ CImg<ulongT>::vector((ulongT)mp_rot2d,pos,arg1).move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"round(",6)) { // Value rounding
+ _cimg_mp_op("Function 'round()'");
+ s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss6,s1,depth1,0);
+ arg2 = 1;
+ arg3 = 0;
+ if (s1<se1) {
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(++s1,s2,depth1,0);
+ arg3 = s2<se1?compile(++s2,se1,depth1,0):0;
+ }
+ _cimg_mp_check_type(arg2,2,1,0);
+ _cimg_mp_check_type(arg3,3,1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector3_vss(mp_round,arg1,arg2,arg3);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2) && _cimg_mp_is_constant(arg3))
+ _cimg_mp_constant(cimg::round(mem[arg1],mem[arg2],(int)mem[arg3]));
+ _cimg_mp_scalar3(mp_round,arg1,arg2,arg3);
+ }
+ break;
+
+ case 's' :
+ if (!std::strncmp(ss,"s(",2)) { // Image spectrum
+ _cimg_mp_op("Function 's()'");
+ if (*ss2=='#') { p1 = compile(ss3,se1,depth1,0); _cimg_mp_check_list(false); } // Index specified
+ else { if (ss2!=se1) break; p1 = ~0U; }
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_image_s,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"same(",5)) { // Test if operands have the same values
+ _cimg_mp_op("Function 'same()'");
+ s1 = ss5; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss5,s1,depth1,0);
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(++s1,s2,depth1,0);
+ arg3 = 11;
+ arg4 = 1;
+ if (s2<se1) {
+ s3 = s2 + 1; while (s3<se1 && (*s3!=',' || level[s3 - expr._data]!=clevel1)) ++s3;
+ arg3 = compile(++s2,s3,depth1,0);
+ _cimg_mp_check_type(arg3,3,1,0);
+ arg4 = s3<se1?compile(++s3,se1,depth1,0):1;
+ }
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = _cimg_mp_vector_size(arg2);
+ _cimg_mp_scalar6(mp_vector_eq,arg1,p1,arg2,p2,arg3,arg4);
+ }
+
+ if (!std::strncmp(ss,"sign(",5)) { // Sign
+ _cimg_mp_op("Function 'sign()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sign,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sign(mem[arg1]));
+ _cimg_mp_scalar1(mp_sign,arg1);
+ }
+
+ if (!std::strncmp(ss,"sin(",4)) { // Sine
+ _cimg_mp_op("Function 'sin()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sin,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sin(mem[arg1]));
+ _cimg_mp_scalar1(mp_sin,arg1);
+ }
+
+ if (!std::strncmp(ss,"sinc(",5)) { // Sine cardinal
+ _cimg_mp_op("Function 'sinc()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinc,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sinc(mem[arg1]));
+ _cimg_mp_scalar1(mp_sinc,arg1);
+ }
+
+ if (!std::strncmp(ss,"single(",7)) { // Force single thread execution
+ _cimg_mp_op("Function 'single()'");
+ p1 = code._width;
+ arg1 = compile(ss7,se1,depth1,p_ref);
+ CImg<ulongT>::vector((ulongT)mp_single,arg1,code._width - p1).move_to(code,p1);
+ _cimg_mp_return(arg1);
+ }
+
+ if (!std::strncmp(ss,"sinh(",5)) { // Hyperbolic sine
+ _cimg_mp_op("Function 'sinh()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sinh,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sinh(mem[arg1]));
+ _cimg_mp_scalar1(mp_sinh,arg1);
+ }
+
+ if (!std::strncmp(ss,"size(",5)) { // Vector size.
+ _cimg_mp_op("Function 'size()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ _cimg_mp_constant(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_vector_size(arg1));
+ }
+
+ if (!std::strncmp(ss,"solve(",6)) { // Solve linear system
+ _cimg_mp_op("Function 'solve()'");
+ s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss6,s1,depth1,0);
+ s2 = s1 + 1; while (s2<se1 && (*s2!=',' || level[s2 - expr._data]!=clevel1)) ++s2;
+ arg2 = compile(++s1,s2,depth1,0);
+ arg3 = s2<se1?compile(++s2,se1,depth1,0):1;
+ _cimg_mp_check_type(arg1,1,2,0);
+ _cimg_mp_check_type(arg2,2,2,0);
+ _cimg_mp_check_constant(arg3,3,3);
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = _cimg_mp_vector_size(arg2);
+ p3 = (unsigned int)mem[arg3];
+ arg5 = p2/p3;
+ arg4 = p1/arg5;
+ if (arg4*arg5!=p1 || arg5*p3!=p2) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Types of first and second arguments ('%s' and '%s') "
+ "do not match with third argument 'nb_colsB=%u', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ s_type(arg1)._data,s_type(arg2)._data,p3,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ pos = vector(arg4*p3);
+ CImg<ulongT>::vector((ulongT)mp_solve,pos,arg1,arg2,arg4,arg5,p3).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"sort(",5)) { // Sort vector
+ _cimg_mp_op("Function 'sort()'");
+ s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss5,s1,depth1,0);
+ arg2 = arg3 = 1;
+ if (s1<se1) {
+ s0 = ++s1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0;
+ arg2 = compile(s1,s0,depth1,0);
+ arg3 = s0<se1?compile(++s0,se1,depth1,0):1;
+ }
+ _cimg_mp_check_type(arg1,1,2,0);
+ _cimg_mp_check_type(arg2,2,1,0);
+ _cimg_mp_check_constant(arg3,3,3);
+ arg3 = (unsigned int)mem[arg3];
+ p1 = _cimg_mp_vector_size(arg1);
+ if (p1%arg3) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Invalid specified chunk size (%u) for first argument "
+ "('%s'), in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ arg3,s_type(arg1)._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ pos = vector(p1);
+ CImg<ulongT>::vector((ulongT)mp_sort,pos,arg1,p1,arg2,arg3).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"sqr(",4)) { // Square
+ _cimg_mp_op("Function 'sqr()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqr,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::sqr(mem[arg1]));
+ _cimg_mp_scalar1(mp_sqr,arg1);
+ }
+
+ if (!std::strncmp(ss,"sqrt(",5)) { // Square root
+ _cimg_mp_op("Function 'sqrt()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_sqrt,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::sqrt(mem[arg1]));
+ _cimg_mp_scalar1(mp_sqrt,arg1);
+ }
+
+ if (!std::strncmp(ss,"stod(",5)) { // String to double
+ _cimg_mp_op("Function 'stod()'");
+ s1 = ss5; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss5,s1,depth1,0);
+ arg2 = s1<se1?compile(++s1,se1,depth1,0):0;
+ _cimg_mp_check_type(arg2,2,1,0);
+ p1 = _cimg_mp_vector_size(arg1);
+ _cimg_mp_scalar3(mp_stod,arg1,p1,arg2);
+ }
+
+ if (!std::strncmp(ss,"svd(",4)) { // Matrix SVD
+ _cimg_mp_op("Function 'svd()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ arg2 = s1<se1?compile(++s1,se1,depth1,0):1;
+ _cimg_mp_check_type(arg1,1,2,0);
+ _cimg_mp_check_constant(arg2,2,3);
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = (unsigned int)mem[arg2];
+ p3 = p1/p2;
+ if (p3*p2!=p1) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Type of first argument ('%s') "
+ "does not match with second argument 'nb_colsA=%u', "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ s_type(arg1)._data,p2,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ pos = vector(p1 + p2 + p2*p2);
+ CImg<ulongT>::vector((ulongT)mp_matrix_svd,pos,arg1,p2,p3).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 't' :
+ if (!std::strncmp(ss,"tan(",4)) { // Tangent
+ _cimg_mp_op("Function 'tan()'");
+ arg1 = compile(ss4,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tan,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tan(mem[arg1]));
+ _cimg_mp_scalar1(mp_tan,arg1);
+ }
+
+ if (!std::strncmp(ss,"tanh(",5)) { // Hyperbolic tangent
+ _cimg_mp_op("Function 'tanh()'");
+ arg1 = compile(ss5,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_tanh,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::tanh(mem[arg1]));
+ _cimg_mp_scalar1(mp_tanh,arg1);
+ }
+
+ if (!std::strncmp(ss,"trace(",6)) { // Matrix trace
+ _cimg_mp_op("Function 'trace()'");
+ arg1 = compile(ss6,se1,depth1,0);
+ _cimg_mp_check_matrix_square(arg1,1);
+ p1 = (unsigned int)std::sqrt((float)_cimg_mp_vector_size(arg1));
+ _cimg_mp_scalar2(mp_trace,arg1,p1);
+ }
+
+ if (!std::strncmp(ss,"transp(",7)) { // Matrix transpose
+ _cimg_mp_op("Function 'transp()'");
+ s1 = ss7; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss7,s1,depth1,0);
+ arg2 = compile(++s1,se1,depth1,0);
+ _cimg_mp_check_type(arg1,1,2,0);
+ _cimg_mp_check_constant(arg2,2,3);
+ p1 = _cimg_mp_vector_size(arg1);
+ p2 = (unsigned int)mem[arg2];
+ p3 = p1/p2;
+ if (p2*p3!=p1) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Size of first argument ('%s') does not match "
+ "second argument 'nb_cols=%u', in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ s_type(arg1)._data,p2,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ pos = vector(p3*p2);
+ CImg<ulongT>::vector((ulongT)mp_transp,pos,arg1,p2,p3).move_to(code);
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 'u' :
+ if (*ss1=='(') { // Random value with uniform distribution
+ _cimg_mp_op("Function 'u()'");
+ if (*ss2==')') _cimg_mp_scalar2(mp_u,0,1);
+ s1 = ss2; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss2,s1,depth1,0);
+ if (s1<se1) arg2 = compile(++s1,se1,depth1,0); else { arg2 = arg1; arg1 = 0; }
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_u,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_u,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_u,arg1,arg2);
+ _cimg_mp_scalar2(mp_u,arg1,arg2);
+ }
+
+ if (!std::strncmp(ss,"unref(",6)) { // Un-reference variable
+ _cimg_mp_op("Function 'unref()'");
+ arg1 = ~0U;
+ for (s0 = ss6; s0<se1; s0 = s1) {
+ if (s0>ss6 && *s0==',') ++s0;
+ s1 = s0; while (s1<se1 && *s1!=',') ++s1;
+ c1 = *s1;
+ if (s1>s0) {
+ *s1 = 0;
+ arg2 = arg3 = ~0U;
+ if (s0[0]=='w' && s0[1]=='h' && !s0[2]) arg1 = reserved_label[arg3 = 0];
+ else if (s0[0]=='w' && s0[1]=='h' && s0[2]=='d' && !s0[3]) arg1 = reserved_label[arg3 = 1];
+ else if (s0[0]=='w' && s0[1]=='h' && s0[2]=='d' && s0[3]=='s' && !s0[4])
+ arg1 = reserved_label[arg3 = 2];
+ else if (s0[0]=='p' && s0[1]=='i' && !s0[2]) arg1 = reserved_label[arg3 = 3];
+ else if (s0[0]=='i' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 4];
+ else if (s0[0]=='i' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 5];
+ else if (s0[0]=='i' && s0[1]=='a' && !s0[2]) arg1 = reserved_label[arg3 = 6];
+ else if (s0[0]=='i' && s0[1]=='v' && !s0[2]) arg1 = reserved_label[arg3 = 7];
+ else if (s0[0]=='i' && s0[1]=='s' && !s0[2]) arg1 = reserved_label[arg3 = 8];
+ else if (s0[0]=='i' && s0[1]=='p' && !s0[2]) arg1 = reserved_label[arg3 = 9];
+ else if (s0[0]=='i' && s0[1]=='c' && !s0[2]) arg1 = reserved_label[arg3 = 10];
+ else if (s0[0]=='x' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 11];
+ else if (s0[0]=='y' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 12];
+ else if (s0[0]=='z' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 13];
+ else if (s0[0]=='c' && s0[1]=='m' && !s0[2]) arg1 = reserved_label[arg3 = 14];
+ else if (s0[0]=='x' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 15];
+ else if (s0[0]=='y' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 16];
+ else if (s0[0]=='z' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 17];
+ else if (s0[0]=='c' && s0[1]=='M' && !s0[2]) arg1 = reserved_label[arg3 = 18];
+ else if (s0[0]=='i' && s0[1]>='0' && s0[1]<='9' && !s0[2])
+ arg1 = reserved_label[arg3 = 19 + s0[1] - '0'];
+ else if (!std::strcmp(s0,"interpolation")) arg1 = reserved_label[arg3 = 29];
+ else if (!std::strcmp(s0,"boundary")) arg1 = reserved_label[arg3 = 30];
+ else if (s0[1]) { // Multi-char variable
+ cimglist_for(variable_def,i) if (!std::strcmp(s0,variable_def[i])) {
+ arg1 = variable_pos[i]; arg2 = i; break;
+ }
+ } else arg1 = reserved_label[arg3 = *s0]; // Single-char variable
+
+ if (arg1!=~0U) {
+ if (arg2==~0U) { if (arg3!=~0U) reserved_label[arg3] = ~0U; }
+ else {
+ variable_def.remove(arg2);
+ if (arg2<variable_pos._width - 1)
+ std::memmove(variable_pos._data + arg2,variable_pos._data + arg2 + 1,
+ sizeof(uintT)*(variable_pos._width - arg2 - 1));
+ --variable_pos._width;
+ }
+ }
+ *s1 = c1;
+ } else compile(s0,s1,depth1,0); // Will throw a 'missing argument' exception
+ }
+ _cimg_mp_return(arg1!=~0U?arg1:_cimg_mp_slot_nan); // Return value of last specified variable.
+ }
+
+ if (!std::strncmp(ss,"uppercase(",10)) { // Upper case
+ _cimg_mp_op("Function 'uppercase()'");
+ arg1 = compile(ss + 10,se1,depth1,0);
+ if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_uppercase,arg1);
+ if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::uppercase(mem[arg1]));
+ _cimg_mp_scalar1(mp_uppercase,arg1);
+ }
+ break;
+
+ case 'v' :
+ if ((cimg_sscanf(ss,"vector%u%c",&(arg1=~0U),&sep)==2 && sep=='(' && arg1>0) ||
+ !std::strncmp(ss,"vector(",7) ||
+ (!std::strncmp(ss,"vector",6) && ss7<se1 && (s=std::strchr(ss7,'('))!=0)) { // Vector
+ _cimg_mp_op("Function 'vector()'");
+ arg2 = 0; // Number of specified values.
+ if (arg1==~0U && *ss6!='(') {
+ arg1 = compile(ss6,s++,depth1,0);
+ _cimg_mp_check_constant(arg1,0,3);
+ arg1 = (unsigned int)mem[arg1];
+ } else s = std::strchr(ss6,'(') + 1;
+
+ if (s<se1 || arg1==~0U) for ( ; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ arg3 = compile(s,ns,depth1,0);
+ if (_cimg_mp_is_vector(arg3)) {
+ arg4 = _cimg_mp_vector_size(arg3);
+ CImg<ulongT>::sequence(arg4,arg3 + 1,arg3 + arg4).move_to(_opcode);
+ arg2+=arg4;
+ } else { CImg<ulongT>::vector(arg3).move_to(_opcode); ++arg2; }
+ s = ns;
+ }
+ if (arg1==~0U) arg1 = arg2;
+ _cimg_mp_check_vector0(arg1);
+ pos = vector(arg1);
+ _opcode.insert(CImg<ulongT>::vector((ulongT)mp_vector_init,pos,0,arg1),0);
+ (_opcode>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ opcode.move_to(code);
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 'w' :
+ if (!std::strncmp(ss,"w(",2)) { // Image width
+ _cimg_mp_op("Function 'w()'");
+ if (*ss2=='#') { p1 = compile(ss3,se1,depth1,0); _cimg_mp_check_list(false); } // Index specified
+ else { if (ss2!=se1) break; p1 = ~0U; }
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_image_w,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"wh(",3)) { // Image width*height
+ _cimg_mp_op("Function 'wh()'");
+ if (*ss3=='#') { p1 = compile(ss4,se1,depth1,0); _cimg_mp_check_list(false); } // Index specified
+ else { if (ss3!=se1) break; p1 = ~0U; }
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_image_wh,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"whd(",4)) { // Image width*height*depth
+ _cimg_mp_op("Function 'whd()'");
+ if (*ss4=='#') { p1 = compile(ss5,se1,depth1,0); _cimg_mp_check_list(false); } // Index specified
+ else { if (ss4!=se1) break; p1 = ~0U; }
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_image_whd,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"whds(",5)) { // Image width*height*depth*spectrum
+ _cimg_mp_op("Function 'whds()'");
+ if (*ss5=='#') { p1 = compile(ss6,se1,depth1,0); _cimg_mp_check_list(false); } // Index specified
+ else { if (ss5!=se1) break; p1 = ~0U; }
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)mp_image_whds,pos,p1).move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ if (!std::strncmp(ss,"while(",6) || !std::strncmp(ss,"whiledo(",8)) { // While...do
+ _cimg_mp_op("Function 'whiledo()'");
+ s0 = *ss5=='('?ss6:ss8;
+ s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ p1 = code._width;
+ arg1 = compile(s0,s1,depth1,0);
+ p2 = code._width;
+ arg6 = mempos;
+ pos = compile(++s1,se1,depth1,0);
+ _cimg_mp_check_type(arg1,1,1,0);
+ arg2 = _cimg_mp_vector_size(pos);
+ CImg<ulongT>::vector((ulongT)mp_whiledo,pos,arg1,p2 - p1,code._width - p2,arg2,
+ pos>=arg6 && !_cimg_mp_is_constant(pos),
+ arg1>=arg6 && !_cimg_mp_is_constant(arg1)).move_to(code,p1);
+ _cimg_mp_return(pos);
+ }
+ break;
+
+ case 'x' :
+ if (!std::strncmp(ss,"xor(",4)) { // Xor
+ _cimg_mp_op("Function 'xor()'");
+ s1 = ss4; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1;
+ arg1 = compile(ss4,s1,depth1,0);
+ arg2 = compile(++s1,se1,depth1,0);
+ _cimg_mp_check_type(arg2,2,3,_cimg_mp_vector_size(arg1));
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_bitwise_xor,arg1,arg2);
+ if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) _cimg_mp_vector2_vs(mp_bitwise_xor,arg1,arg2);
+ if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_sv(mp_bitwise_xor,arg1,arg2);
+ if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2))
+ _cimg_mp_constant((longT)mem[arg1] ^ (longT)mem[arg2]);
+ _cimg_mp_scalar2(mp_bitwise_xor,arg1,arg2);
+ }
+ break;
+
+ }
+
+ if (!std::strncmp(ss,"min(",4) || !std::strncmp(ss,"max(",4) ||
+ !std::strncmp(ss,"med(",4) || !std::strncmp(ss,"kth(",4) ||
+ !std::strncmp(ss,"sum(",4) ||
+ !std::strncmp(ss,"std(",4) || !std::strncmp(ss,"variance(",9) ||
+ !std::strncmp(ss,"prod(",5) || !std::strncmp(ss,"mean(",5) ||
+ !std::strncmp(ss,"argmin(",7) || !std::strncmp(ss,"argmax(",7)) { // Multi-argument functions
+ _cimg_mp_op(*ss=='a'?(ss[3]=='('?"Function 'arg()'":ss[4]=='i'?"Function 'argmin()'":
+ "Function 'argmax()'"):
+ *ss=='s'?(ss[1]=='u'?"Function 'sum()'":"Function 'std()'"):
+ *ss=='k'?"Function 'kth()'":
+ *ss=='p'?"Function 'prod()'":
+ *ss=='v'?"Function 'variance()'":
+ ss[1]=='i'?"Function 'min()'":
+ ss[1]=='a'?"Function 'max()'":
+ ss[2]=='a'?"Function 'mean()'":"Function 'med()'");
+ op = *ss=='a'?(ss[3]=='('?mp_arg:ss[4]=='i'?mp_argmin:mp_argmax):
+ *ss=='s'?(ss[1]=='u'?mp_sum:mp_std):
+ *ss=='k'?mp_kth:
+ *ss=='p'?mp_prod:
+ *ss=='v'?mp_variance:
+ ss[1]=='i'?mp_min:
+ ss[1]=='a'?mp_max:
+ ss[2]=='a'?mp_mean:
+ mp_median;
+ is_sth = true; // Tell if all arguments are constant
+ pos = scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,0).move_to(_opcode);
+ for (s = std::strchr(ss,'(') + 1; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ arg2 = compile(s,ns,depth1,0);
+ if (_cimg_mp_is_vector(arg2))
+ CImg<ulongT>::sequence(_cimg_mp_vector_size(arg2),arg2 + 1,
+ arg2 + (ulongT)_cimg_mp_vector_size(arg2)).
+ move_to(_opcode);
+ else CImg<ulongT>::vector(arg2).move_to(_opcode);
+ is_sth&=_cimg_mp_is_constant(arg2);
+ s = ns;
+ }
+ (_opcode>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ if (is_sth) _cimg_mp_constant(op(*this));
+ opcode.move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ // No corresponding built-in function -> Look for a user-defined macro call.
+ s0 = strchr(ss,'(');
+ if (s0) {
+ variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0;
+
+ // Count number of specified arguments.
+ p1 = 0;
+ for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) {
+ while (*s && (signed char)*s<=' ') ++s;
+ if (*s==')' && !p1) break;
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ }
+
+ arg3 = 0; // Number of possible name matches
+ cimglist_for(macro_def,l) if (!std::strcmp(macro_def[l],variable_name) && ++arg3 &&
+ macro_def[l].back()==(char)p1) {
+ p2 = (unsigned int)macro_def[l].back(); // Number of required arguments
+ CImg<charT> _expr = macro_body[l]; // Expression to be substituted
+
+ p1 = 1; // Indice of current parsed argument
+ for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { // Parse function arguments
+ while (*s && (signed char)*s<=' ') ++s;
+ if (*s==')' && p1==1) break; // Function has no arguments
+ if (p1>p2) { ++p1; break; }
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns;
+ variable_name.assign(s,(unsigned int)(ns - s + 1)).back() = 0; // Argument to write
+ arg2 = 0;
+ cimg_forX(_expr,k) {
+ if (_expr[k]==(char)p1) { // Perform argument substitution
+ arg1 = _expr._width;
+ _expr.resize(arg1 + variable_name._width - 2,1,1,1,0);
+ std::memmove(_expr._data + k + variable_name._width - 1,_expr._data + k + 1,arg1 - k - 1);
+ std::memcpy(_expr._data + k,variable_name,variable_name._width - 1);
+ k+=variable_name._width - 2;
+ }
+ ++arg2;
+ }
+ }
+
+ // Recompute 'pexpr' and 'level' for evaluating substituted expression.
+ CImg<charT> _pexpr(_expr._width);
+ ns = _pexpr._data;
+ for (ps = _expr._data, c1 = ' '; *ps; ++ps) {
+ if ((signed char)*ps>' ') c1 = *ps;
+ *(ns++) = c1;
+ }
+ *ns = 0;
+
+ CImg<uintT> _level = get_level(_expr);
+ expr.swap(_expr);
+ pexpr.swap(_pexpr);
+ level.swap(_level);
+ s0 = user_macro;
+ user_macro = macro_def[l];
+ pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref);
+ user_macro = s0;
+ level.swap(_level);
+ pexpr.swap(_pexpr);
+ expr.swap(_expr);
+ _cimg_mp_return(pos);
+ }
+
+ if (arg3) { // Macro name matched but number of arguments does not
+ CImg<uintT> sig_nargs(arg3);
+ arg1 = 0;
+ cimglist_for(macro_def,l) if (!std::strcmp(macro_def[l],variable_name))
+ sig_nargs[arg1++] = (unsigned int)macro_def[l].back();
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ if (sig_nargs._width>1) {
+ sig_nargs.sort();
+ arg1 = sig_nargs.back();
+ --sig_nargs._width;
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Function '%s()': Number of specified arguments (%u) "
+ "does not match macro declaration (defined for %s or %u arguments), "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,variable_name._data,
+ p1,sig_nargs.value_string()._data,arg1,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ } else
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Function '%s()': Number of specified arguments (%u) "
+ "does not match macro declaration (defined for %u argument%s), "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,variable_name._data,
+ p1,*sig_nargs,*sig_nargs!=1?"s":"",
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ }
+ } // if (se1==')')
+
+ // Char / string initializer.
+ if (*se1=='\'' &&
+ ((se1>ss && *ss=='\'') ||
+ (se1>ss1 && *ss=='_' && *ss1=='\''))) {
+ if (*ss=='_') { _cimg_mp_op("Char initializer"); s1 = ss2; }
+ else { _cimg_mp_op("String initializer"); s1 = ss1; }
+ arg1 = (unsigned int)(se1 - s1); // Original string length.
+ if (arg1) {
+ CImg<charT>(s1,arg1 + 1).move_to(variable_name).back() = 0;
+ cimg::strunescape(variable_name);
+ arg1 = (unsigned int)std::strlen(variable_name);
+ }
+ if (!arg1) _cimg_mp_return(0); // Empty string -> 0
+ if (*ss=='_') {
+ if (arg1==1) _cimg_mp_constant(*variable_name);
+ *se = saved_char;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s: Literal %s contains more than one character, "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,
+ ss1,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ pos = vector(arg1);
+ CImg<ulongT>::vector((ulongT)mp_string_init,pos,arg1).move_to(_opcode);
+ CImg<ulongT>(1,arg1/sizeof(ulongT) + (arg1%sizeof(ulongT)?1:0)).move_to(_opcode);
+ std::memcpy((char*)_opcode[1]._data,variable_name,arg1);
+ (_opcode>'y').move_to(code);
+ _cimg_mp_return(pos);
+ }
+
+ // Vector initializer [ ... ].
+ if (*ss=='[' && *se1==']') {
+ _cimg_mp_op("Vector initializer");
+ s1 = ss1; while (s1<se2 && (signed char)*s1<=' ') ++s1;
+ s2 = se2; while (s2>s1 && (signed char)*s2<=' ') --s2;
+ if (s2>s1 && *s1=='\'' && *s2=='\'') { // Vector values provided as a string
+ arg1 = (unsigned int)(s2 - s1 - 1); // Original string length.
+ if (arg1) {
+ CImg<charT>(s1 + 1,arg1 + 1).move_to(variable_name).back() = 0;
+ cimg::strunescape(variable_name);
+ arg1 = (unsigned int)std::strlen(variable_name);
+ }
+ if (!arg1) _cimg_mp_return(0); // Empty string -> 0
+ pos = vector(arg1);
+ CImg<ulongT>::vector((ulongT)mp_string_init,pos,arg1).move_to(_opcode);
+ CImg<ulongT>(1,arg1/sizeof(ulongT) + (arg1%sizeof(ulongT)?1:0)).move_to(_opcode);
+ std::memcpy((char*)_opcode[1]._data,variable_name,arg1);
+ (_opcode>'y').move_to(code);
+ } else { // Vector values provided as list of items
+ arg1 = 0; // Number of specified values.
+ if (*ss1!=']') for (s = ss1; s<se; ++s) {
+ ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) &&
+ (*ns!=']' || level[ns - expr._data]!=clevel)) ++ns;
+ arg2 = compile(s,ns,depth1,0);
+ if (_cimg_mp_is_vector(arg2)) {
+ arg3 = _cimg_mp_vector_size(arg2);
+ CImg<ulongT>::sequence(arg3,arg2 + 1,arg2 + arg3).move_to(_opcode);
+ arg1+=arg3;
+ } else { CImg<ulongT>::vector(arg2).move_to(_opcode); ++arg1; }
+ s = ns;
+ }
+ _cimg_mp_check_vector0(arg1);
+ pos = vector(arg1);
+ _opcode.insert(CImg<ulongT>::vector((ulongT)mp_vector_init,pos,0,arg1),0);
+ (_opcode>'y').move_to(opcode);
+ opcode[2] = opcode._height;
+ opcode.move_to(code);
+ }
+ _cimg_mp_return(pos);
+ }
+
+ // Variables related to the input list of images.
+ if (*ss1=='#' && ss2<se) {
+ arg1 = compile(ss2,se,depth1,0);
+ p1 = (unsigned int)(listin._width && _cimg_mp_is_constant(arg1)?cimg::mod((int)mem[arg1],listin.width()):~0U);
+ switch (*ss) {
+ case 'w' : // w#ind
+ if (!listin) _cimg_mp_return(0);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._width);
+ _cimg_mp_scalar1(mp_list_width,arg1);
+ case 'h' : // h#ind
+ if (!listin) _cimg_mp_return(0);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._height);
+ _cimg_mp_scalar1(mp_list_height,arg1);
+ case 'd' : // d#ind
+ if (!listin) _cimg_mp_return(0);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._depth);
+ _cimg_mp_scalar1(mp_list_depth,arg1);
+ case 'r' : // r#ind
+ if (!listin) _cimg_mp_return(0);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._is_shared);
+ _cimg_mp_scalar1(mp_list_is_shared,arg1);
+ case 's' : // s#ind
+ if (!listin) _cimg_mp_return(0);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._spectrum);
+ _cimg_mp_scalar1(mp_list_spectrum,arg1);
+ case 'i' : // i#ind
+ if (!listin) _cimg_mp_return(0);
+ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,_cimg_mp_slot_c,
+ 0,_cimg_mp_boundary);
+ case 'I' : // I#ind
+ p2 = p1!=~0U?listin[p1]._spectrum:listin._width?~0U:0;
+ _cimg_mp_check_vector0(p2);
+ pos = vector(p2);
+ CImg<ulongT>::vector((ulongT)mp_list_Joff,pos,p1,0,0,p2).move_to(code);
+ _cimg_mp_return(pos);
+ case 'R' : // R#ind
+ if (!listin) _cimg_mp_return(0);
+ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,
+ 0,_cimg_mp_boundary);
+ case 'G' : // G#ind
+ if (!listin) _cimg_mp_return(0);
+ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,
+ 0,_cimg_mp_boundary);
+ case 'B' : // B#ind
+ if (!listin) _cimg_mp_return(0);
+ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,
+ 0,_cimg_mp_boundary);
+ case 'A' : // A#ind
+ if (!listin) _cimg_mp_return(0);
+ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,
+ 0,_cimg_mp_boundary);
+ }
+ }
+
+ if (*ss1 && *ss2=='#' && ss3<se) {
+ arg1 = compile(ss3,se,depth1,0);
+ p1 = (unsigned int)(listin._width && _cimg_mp_is_constant(arg1)?cimg::mod((int)mem[arg1],listin.width()):~0U);
+ if (*ss=='w' && *ss1=='h') { // wh#ind
+ if (!listin) _cimg_mp_return(0);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._width*listin[p1]._height);
+ _cimg_mp_scalar1(mp_list_wh,arg1);
+ }
+ arg2 = ~0U;
+
+ if (*ss=='i') {
+ if (*ss1=='c') { // ic#ind
+ if (!listin) _cimg_mp_return(0);
+ if (_cimg_mp_is_constant(arg1)) {
+ if (!list_median) list_median.assign(listin._width);
+ if (!list_median[p1]) CImg<doubleT>::vector(listin[p1].median()).move_to(list_median[p1]);
+ _cimg_mp_constant(*list_median[p1]);
+ }
+ _cimg_mp_scalar1(mp_list_median,arg1);
+ }
+ if (*ss1>='0' && *ss1<='9') { // i0#ind...i9#ind
+ if (!listin) _cimg_mp_return(0);
+ _cimg_mp_scalar7(mp_list_ixyzc,arg1,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,*ss1 - '0',
+ 0,_cimg_mp_boundary);
+ }
+ switch (*ss1) {
+ case 'm' : arg2 = 0; break; // im#ind
+ case 'M' : arg2 = 1; break; // iM#ind
+ case 'a' : arg2 = 2; break; // ia#ind
+ case 'v' : arg2 = 3; break; // iv#ind
+ case 's' : arg2 = 12; break; // is#ind
+ case 'p' : arg2 = 13; break; // ip#ind
+ }
+ } else if (*ss1=='m') switch (*ss) {
+ case 'x' : arg2 = 4; break; // xm#ind
+ case 'y' : arg2 = 5; break; // ym#ind
+ case 'z' : arg2 = 6; break; // zm#ind
+ case 'c' : arg2 = 7; break; // cm#ind
+ } else if (*ss1=='M') switch (*ss) {
+ case 'x' : arg2 = 8; break; // xM#ind
+ case 'y' : arg2 = 9; break; // yM#ind
+ case 'z' : arg2 = 10; break; // zM#ind
+ case 'c' : arg2 = 11; break; // cM#ind
+ }
+ if (arg2!=~0U) {
+ if (!listin) _cimg_mp_return(0);
+ if (_cimg_mp_is_constant(arg1)) {
+ if (!list_stats) list_stats.assign(listin._width);
+ if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(listin[p1].get_stats(),false);
+ _cimg_mp_constant(list_stats(p1,arg2));
+ }
+ _cimg_mp_scalar2(mp_list_stats,arg1,arg2);
+ }
+ }
+
+ if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4<se) { // whd#ind
+ arg1 = compile(ss4,se,depth1,0);
+ if (!listin) _cimg_mp_return(0);
+ p1 = (unsigned int)(_cimg_mp_is_constant(arg1)?cimg::mod((int)mem[arg1],listin.width()):~0U);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._width*listin[p1]._height*listin[p1]._depth);
+ _cimg_mp_scalar1(mp_list_whd,arg1);
+ }
+ if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s' && *ss4=='#' && ss5<se) { // whds#ind
+ arg1 = compile(ss5,se,depth1,0);
+ if (!listin) _cimg_mp_return(0);
+ p1 = (unsigned int)(_cimg_mp_is_constant(arg1)?cimg::mod((int)mem[arg1],listin.width()):~0U);
+ if (p1!=~0U) _cimg_mp_constant(listin[p1]._width*listin[p1]._height*listin[p1]._depth*listin[p1]._spectrum);
+ _cimg_mp_scalar1(mp_list_whds,arg1);
+ }
+
+ if (!std::strcmp(ss,"interpolation")) _cimg_mp_return(_cimg_mp_interpolation); // interpolation
+ if (!std::strcmp(ss,"boundary")) _cimg_mp_return(_cimg_mp_boundary); // boundary
+
+ // No known item found, assuming this is an already initialized variable.
+ variable_name.assign(ss,(unsigned int)(se - ss + 1)).back() = 0;
+ if (variable_name[1]) { // Multi-char variable
+ cimglist_for(variable_def,i) if (!std::strcmp(variable_name,variable_def[i]))
+ _cimg_mp_return(variable_pos[i]);
+ } else if (reserved_label[*variable_name]!=~0U) // Single-char variable
+ _cimg_mp_return(reserved_label[*variable_name]);
+
+ // Reached an unknown item -> error.
+ is_sth = true; // is_valid_variable_name
+ if (*variable_name>='0' && *variable_name<='9') is_sth = false;
+ else for (ns = variable_name._data; *ns; ++ns)
+ if (!is_varchar(*ns)) { is_sth = false; break; }
+
+ *se = saved_char;
+ c1 = *se1;
+ cimg::strellipsize(variable_name,64);
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ if (is_sth)
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Undefined variable '%s' in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,
+ variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ s1 = std::strchr(ss,'(');
+ s_op = s1 && c1==')'?"function call":"item";
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,
+ s_op,variable_name._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+
+ // Evaluation procedure.
+ double operator()(const double x, const double y, const double z, const double c) {
+ mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c;
+ for (p_code = code; p_code<p_code_end; ++p_code) {
+ opcode._data = p_code->_data;
+ const ulongT target = opcode[1];
+ mem[target] = _cimg_mp_defunc(*this);
+ }
+ return *result;
+ }
+
+ // Evaluation procedure (return output values in vector 'output').
+ template<typename t>
+ void operator()(const double x, const double y, const double z, const double c, t *const output) {
+ mem[_cimg_mp_slot_x] = x; mem[_cimg_mp_slot_y] = y; mem[_cimg_mp_slot_z] = z; mem[_cimg_mp_slot_c] = c;
+ for (p_code = code; p_code<p_code_end; ++p_code) {
+ opcode._data = p_code->_data;
+ const ulongT target = opcode[1];
+ mem[target] = _cimg_mp_defunc(*this);
+ }
+ if (result_dim) {
+ const double *ptrs = result + 1;
+ t *ptrd = output;
+ for (unsigned int k = 0; k<result_dim; ++k) *(ptrd++) = (t)*(ptrs++);
+ } else *output = (t)*result;
+ }
+
+ // Evaluation procedure for the end() blocks.
+ void end() {
+ if (code_end.is_empty()) return;
+ if (imgin) {
+ mem[_cimg_mp_slot_x] = imgin._width - 1.0;
+ mem[_cimg_mp_slot_y] = imgin._height - 1.0f;
+ mem[_cimg_mp_slot_z] = imgin._depth - 1.0f;
+ mem[_cimg_mp_slot_c] = imgin._spectrum - 1.0f;
+ } else mem[_cimg_mp_slot_x] = mem[_cimg_mp_slot_y] = mem[_cimg_mp_slot_z] = mem[_cimg_mp_slot_c] = 0;
+ p_code_end = code_end.end();
+ for (p_code = code_end; p_code<p_code_end; ++p_code) {
+ opcode._data = p_code->_data;
+ const ulongT target = opcode[1];
+ mem[target] = _cimg_mp_defunc(*this);
+ }
+ }
+
+ // Return type of a memory element as a string.
+ CImg<charT> s_type(const unsigned int arg) const {
+ CImg<charT> res;
+ if (_cimg_mp_is_vector(arg)) { // Vector
+ CImg<charT>::string("vectorXXXXXXXXXXXXXXXX").move_to(res);
+ std::sprintf(res._data + 6,"%u",_cimg_mp_vector_size(arg));
+ } else CImg<charT>::string("scalar").move_to(res);
+ return res;
+ }
+
+ // Insert constant value in memory.
+ unsigned int constant(const double val) {
+
+ // Search for built-in constant.
+ if (val==(double)(int)val) {
+ if (val>=0 && val<=10) return (unsigned int)val;
+ if (val<0 && val>=-5) return (unsigned int)(10 - val);
+ }
+ if (val==0.5) return 16;
+ if (cimg::type<double>::is_nan(val)) return _cimg_mp_slot_nan;
+
+ // Search for constant already requested before (in const cache).
+ unsigned int ind = ~0U;
+ if (constcache_size<1024) {
+ if (!constcache_size) {
+ constcache_vals.assign(16,1,1,1,0);
+ constcache_inds.assign(16,1,1,1,0);
+ *constcache_vals = val;
+ constcache_size = 1;
+ ind = 0;
+ } else { // Dichotomic search
+ const double val_beg = *constcache_vals, val_end = constcache_vals[constcache_size - 1];
+ if (val_beg>=val) ind = 0;
+ else if (val_end==val) ind = constcache_size - 1;
+ else if (val_end<val) ind = constcache_size;
+ else {
+ unsigned int i0 = 1, i1 = constcache_size - 2;
+ while (i0<=i1) {
+ const unsigned int mid = (i0 + i1)/2;
+ if (constcache_vals[mid]==val) { i0 = mid; break; }
+ else if (constcache_vals[mid]<val) i0 = mid + 1;
+ else i1 = mid - 1;
+ }
+ ind = i0;
+ }
+
+ if (ind>=constcache_size || constcache_vals[ind]!=val) {
+ ++constcache_size;
+ if (constcache_size>constcache_vals._width) {
+ constcache_vals.resize(-200,1,1,1,0);
+ constcache_inds.resize(-200,1,1,1,0);
+ }
+ const int l = constcache_size - (int)ind - 1;
+ if (l>0) {
+ std::memmove(&constcache_vals[ind + 1],&constcache_vals[ind],l*sizeof(double));
+ std::memmove(&constcache_inds[ind + 1],&constcache_inds[ind],l*sizeof(unsigned int));
+ }
+ constcache_vals[ind] = val;
+ constcache_inds[ind] = 0;
+ }
+ }
+ if (constcache_inds[ind]) return constcache_inds[ind];
+ }
+
+ // Insert new constant in memory if necessary.
+ if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(-200,1,1,1,0); }
+ const unsigned int pos = mempos++;
+ mem[pos] = val;
+ memtype[pos] = 1; // Set constant property
+ if (ind!=~0U) constcache_inds[ind] = pos;
+ return pos;
+ }
+
+ // Insert code instructions for processing scalars.
+ unsigned int scalar() { // Insert new scalar in memory.
+ if (mempos>=mem._width) { mem.resize(-200,1,1,1,0); memtype.resize(mem._width,1,1,1,0); }
+ return mempos++;
+ }
+
+ unsigned int scalar0(const mp_func op) {
+ const unsigned int pos = scalar();
+ CImg<ulongT>::vector((ulongT)op,pos).move_to(code);
+ return pos;
+ }
+
+ unsigned int scalar1(const mp_func op, const unsigned int arg1) {
+ const unsigned int pos =
+ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1) && op!=mp_copy?arg1:scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,arg1).move_to(code);
+ return pos;
+ }
+
+ unsigned int scalar2(const mp_func op, const unsigned int arg1, const unsigned int arg2) {
+ const unsigned int pos =
+ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1:
+ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2:scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,arg1,arg2).move_to(code);
+ return pos;
+ }
+
+ unsigned int scalar3(const mp_func op,
+ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3) {
+ const unsigned int pos =
+ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1:
+ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2:
+ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3:scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,arg1,arg2,arg3).move_to(code);
+ return pos;
+ }
+
+ unsigned int scalar4(const mp_func op,
+ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3,
+ const unsigned int arg4) {
+ const unsigned int pos =
+ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1:
+ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2:
+ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3:
+ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4:scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,arg1,arg2,arg3,arg4).move_to(code);
+ return pos;
+ }
+
+ unsigned int scalar5(const mp_func op,
+ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3,
+ const unsigned int arg4, const unsigned int arg5) {
+ const unsigned int pos =
+ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1:
+ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2:
+ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3:
+ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4:
+ arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5:scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,arg1,arg2,arg3,arg4,arg5).move_to(code);
+ return pos;
+ }
+
+ unsigned int scalar6(const mp_func op,
+ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3,
+ const unsigned int arg4, const unsigned int arg5, const unsigned int arg6) {
+ const unsigned int pos =
+ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1:
+ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2:
+ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3:
+ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4:
+ arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5:
+ arg6>_cimg_mp_slot_c && _cimg_mp_is_comp(arg6)?arg6:scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,arg1,arg2,arg3,arg4,arg5,arg6).move_to(code);
+ return pos;
+ }
+
+ unsigned int scalar7(const mp_func op,
+ const unsigned int arg1, const unsigned int arg2, const unsigned int arg3,
+ const unsigned int arg4, const unsigned int arg5, const unsigned int arg6,
+ const unsigned int arg7) {
+ const unsigned int pos =
+ arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1:
+ arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2:
+ arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3:
+ arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4:
+ arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5:
+ arg6>_cimg_mp_slot_c && _cimg_mp_is_comp(arg6)?arg6:
+ arg7>_cimg_mp_slot_c && _cimg_mp_is_comp(arg7)?arg7:scalar();
+ CImg<ulongT>::vector((ulongT)op,pos,arg1,arg2,arg3,arg4,arg5,arg6,arg7).move_to(code);
+ return pos;
+ }
+
+ // Return a string that defines the calling function + the user-defined function scope.
+ CImg<charT> calling_function_s() const {
+ CImg<charT> res;
+ const unsigned int
+ l1 = calling_function?(unsigned int)std::strlen(calling_function):0U,
+ l2 = user_macro?(unsigned int)std::strlen(user_macro):0U;
+ if (l2) {
+ res.assign(l1 + l2 + 48);
+ cimg_snprintf(res,res._width,"%s(): When substituting function '%s()'",calling_function,user_macro);
+ } else {
+ res.assign(l1 + l2 + 4);
+ cimg_snprintf(res,res._width,"%s()",calling_function);
+ }
+ return res;
+ }
+
+ // Return true if specified argument can be a part of an allowed variable name.
+ bool is_varchar(const char c) const {
+ return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_';
+ }
+
+ // Insert code instructions for processing vectors.
+ bool is_comp_vector(const unsigned int arg) const {
+ unsigned int siz = _cimg_mp_vector_size(arg);
+ if (siz>8) return false;
+ const int *ptr = memtype.data(arg + 1);
+ bool is_tmp = true;
+ while (siz-->0) if (*(ptr++)) { is_tmp = false; break; }
+ return is_tmp;
+ }
+
+ void set_variable_vector(const unsigned int arg) {
+ unsigned int siz = _cimg_mp_vector_size(arg);
+ int *ptr = memtype.data(arg + 1);
+ while (siz-->0) *(ptr++) = -1;
+ }
+
+ unsigned int vector(const unsigned int siz) { // Insert new vector of specified size in memory
+ if (mempos + siz>=mem._width) {
+ mem.resize(2*mem._width + siz,1,1,1,0);
+ memtype.resize(mem._width,1,1,1,0);
+ }
+ const unsigned int pos = mempos++;
+ mem[pos] = cimg::type<double>::nan();
+ memtype[pos] = siz + 1;
+ mempos+=siz;
+ return pos;
+ }
+
+ unsigned int vector(const unsigned int siz, const double value) { // Insert new initialized vector
+ const unsigned int pos = vector(siz);
+ double *ptr = &mem[pos] + 1;
+ for (unsigned int i = 0; i<siz; ++i) *(ptr++) = value;
+ return pos;
+ }
+
+ unsigned int vector_copy(const unsigned int arg) { // Insert new copy of specified vector in memory
+ const unsigned int
+ siz = _cimg_mp_vector_size(arg),
+ pos = vector(siz);
+ CImg<ulongT>::vector((ulongT)mp_vector_copy,pos,arg,siz).move_to(code);
+ return pos;
+ }
+
+ void self_vector_s(const unsigned int pos, const mp_func op, const unsigned int arg1) {
+ const unsigned int siz = _cimg_mp_vector_size(pos);
+ if (siz>24) CImg<ulongT>::vector((ulongT)mp_self_map_vector_s,pos,siz,(ulongT)op,arg1).move_to(code);
+ else {
+ code.insert(siz);
+ for (unsigned int k = 1; k<=siz; ++k)
+ CImg<ulongT>::vector((ulongT)op,pos + k,arg1).move_to(code[code._width - 1 - siz + k]);
+ }
+ }
+
+ void self_vector_v(const unsigned int pos, const mp_func op, const unsigned int arg1) {
+ const unsigned int siz = _cimg_mp_vector_size(pos);
+ if (siz>24) CImg<ulongT>::vector((ulongT)mp_self_map_vector_v,pos,siz,(ulongT)op,arg1).move_to(code);
+ else {
+ code.insert(siz);
+ for (unsigned int k = 1; k<=siz; ++k)
+ CImg<ulongT>::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]);
+ }
+ }
+
+ unsigned int vector1_v(const mp_func op, const unsigned int arg1) {
+ const unsigned int
+ siz = _cimg_mp_vector_size(arg1),
+ pos = is_comp_vector(arg1)?arg1:vector(siz);
+ if (siz>24) CImg<ulongT>::vector((ulongT)mp_vector_map_v,pos,siz,(ulongT)op,arg1).move_to(code);
+ else {
+ code.insert(siz);
+ for (unsigned int k = 1; k<=siz; ++k)
+ CImg<ulongT>::vector((ulongT)op,pos + k,arg1 + k).move_to(code[code._width - 1 - siz + k]);
+ }
+ return pos;
+ }
+
+ unsigned int vector2_vv(const mp_func op, const unsigned int arg1, const unsigned int arg2) {
+ const unsigned int
+ siz = _cimg_mp_vector_size(arg1),
+ pos = is_comp_vector(arg1)?arg1:is_comp_vector(arg2)?arg2:vector(siz);
+ if (siz>24) CImg<ulongT>::vector((ulongT)mp_vector_map_vv,pos,siz,(ulongT)op,arg1,arg2).move_to(code);
+ else {
+ code.insert(siz);
+ for (unsigned int k = 1; k<=siz; ++k)
+ CImg<ulongT>::vector((ulongT)op,pos + k,arg1 + k,arg2 + k).move_to(code[code._width - 1 - siz + k]);
+ }
+ return pos;
+ }
+
+ unsigned int vector2_vs(const mp_func op, const unsigned int arg1, const unsigned int arg2) {
+ const unsigned int
+ siz = _cimg_mp_vector_size(arg1),
+ pos = is_comp_vector(arg1)?arg1:vector(siz);
+ if (siz>24) CImg<ulongT>::vector((ulongT)mp_vector_map_vs,pos,siz,(ulongT)op,arg1,arg2).move_to(code);
+ else {
+ code.insert(siz);
+ for (unsigned int k = 1; k<=siz; ++k)
+ CImg<ulongT>::vector((ulongT)op,pos + k,arg1 + k,arg2).move_to(code[code._width - 1 - siz + k]);
+ }
+ return pos;
+ }
+
+ unsigned int vector2_sv(const mp_func op, const unsigned int arg1, const unsigned int arg2) {
+ const unsigned int
+ siz = _cimg_mp_vector_size(arg2),
+ pos = is_comp_vector(arg2)?arg2:vector(siz);
+ if (siz>24) CImg<ulongT>::vector((ulongT)mp_vector_map_sv,pos,siz,(ulongT)op,arg1,arg2).move_to(code);
+ else {
+ code.insert(siz);
+ for (unsigned int k = 1; k<=siz; ++k)
+ CImg<ulongT>::vector((ulongT)op,pos + k,arg1,arg2 + k).move_to(code[code._width - 1 - siz + k]);
+ }
+ return pos;
+ }
+
+ unsigned int vector3_vss(const mp_func op, const unsigned int arg1, const unsigned int arg2,
+ const unsigned int arg3) {
+ const unsigned int
+ siz = _cimg_mp_vector_size(arg1),
+ pos = is_comp_vector(arg1)?arg1:vector(siz);
+ if (siz>24) CImg<ulongT>::vector((ulongT)mp_vector_map_vss,pos,siz,(ulongT)op,arg1,arg2,arg3).move_to(code);
+ else {
+ code.insert(siz);
+ for (unsigned int k = 1; k<=siz; ++k)
+ CImg<ulongT>::vector((ulongT)op,pos + k,arg1 + k,arg2,arg3).move_to(code[code._width - 1 - siz + k]);
+ }
+ return pos;
+ }
+
+ // Check if a memory slot is a positive integer constant scalar value.
+ // 'mode' can be:
+ // { 0=constant | 1=integer constant | 2=positive integer constant | 3=strictly-positive integer constant }
+ void check_constant(const unsigned int arg, const unsigned int n_arg,
+ const unsigned int mode,
+ char *const ss, char *const se, const char saved_char) {
+ _cimg_mp_check_type(arg,n_arg,1,0);
+ if (!(_cimg_mp_is_constant(arg) &&
+ (!mode || (double)(int)mem[arg]==mem[arg]) &&
+ (mode<2 || mem[arg]>=(mode==3)))) {
+ const char *s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ":
+ n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth ":
+ n_arg==9?"Ninth ":"One of the ";
+ *se = saved_char;
+ char *const s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s%s %s%s (of type '%s') is not a%s constant, "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",
+ s_arg,*s_arg?"argument":"Argument",s_type(arg)._data,
+ !mode?"":mode==1?"n integer":
+ mode==2?" positive integer":" strictly positive integer",
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ }
+
+ // Check a matrix is square.
+ void check_matrix_square(const unsigned int arg, const unsigned int n_arg,
+ char *const ss, char *const se, const char saved_char) {
+ _cimg_mp_check_type(arg,n_arg,2,0);
+ const unsigned int
+ siz = _cimg_mp_vector_size(arg),
+ n = (unsigned int)std::sqrt((float)siz);
+ if (n*n!=siz) {
+ const char *s_arg;
+ if (*s_op!='F') s_arg = !n_arg?"":n_arg==1?"Left-hand ":"Right-hand ";
+ else s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ":"One ";
+ *se = saved_char;
+ char *const s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s%s %s%s (of type '%s') "
+ "cannot be considered as a square matrix, in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",
+ s_arg,*s_op=='F'?(*s_arg?"argument":"Argument"):(*s_arg?"operand":"Operand"),
+ s_type(arg)._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ }
+
+ // Check type compatibility for one argument.
+ // Bits of 'mode' tells what types are allowed:
+ // { 1 = scalar | 2 = vectorN }.
+ // If 'N' is not zero, it also restricts the vectors to be of size N only.
+ void check_type(const unsigned int arg, const unsigned int n_arg,
+ const unsigned int mode, const unsigned int N,
+ char *const ss, char *const se, const char saved_char) {
+ const bool
+ is_scalar = _cimg_mp_is_scalar(arg),
+ is_vector = _cimg_mp_is_vector(arg) && (!N || _cimg_mp_vector_size(arg)==N);
+ bool cond = false;
+ if (mode&1) cond|=is_scalar;
+ if (mode&2) cond|=is_vector;
+ if (!cond) {
+ const char *s_arg;
+ if (*s_op!='F') s_arg = !n_arg?"":n_arg==1?"Left-hand ":"Right-hand ";
+ else s_arg = !n_arg?"":n_arg==1?"First ":n_arg==2?"Second ":n_arg==3?"Third ":
+ n_arg==4?"Fourth ":n_arg==5?"Fifth ":n_arg==6?"Sixth ":n_arg==7?"Seventh ":n_arg==8?"Eighth":
+ n_arg==9?"Ninth":"One of the ";
+ CImg<charT> sb_type(32);
+ if (mode==1) cimg_snprintf(sb_type,sb_type._width,"'scalar'");
+ else if (mode==2) {
+ if (N) cimg_snprintf(sb_type,sb_type._width,"'vector%u'",N);
+ else cimg_snprintf(sb_type,sb_type._width,"'vector'");
+ } else {
+ if (N) cimg_snprintf(sb_type,sb_type._width,"'scalar' or 'vector%u'",N);
+ else cimg_snprintf(sb_type,sb_type._width,"'scalar' or 'vector'");
+ }
+ *se = saved_char;
+ char *const s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s%s %s%s has invalid type '%s' (should be %s), "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",
+ s_arg,*s_op=='F'?(*s_arg?"argument":"Argument"):(*s_arg?"operand":"Operand"),
+ s_type(arg)._data,sb_type._data,
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ }
+
+ // Check that listin or listout are not empty.
+ void check_list(const bool is_out,
+ char *const ss, char *const se, const char saved_char) {
+ if ((!is_out && !listin) || (is_out && !listout)) {
+ *se = saved_char;
+ char *const s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s%s Invalid call with an empty image list, "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ }
+
+ // Check a vector is not 0-dimensional, or with unknown dimension at compile time.
+ void check_vector0(const unsigned int dim,
+ char *const ss, char *const se, const char saved_char) {
+ char *s0 = 0;
+ if (!dim) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s%s Invalid construction of a 0-dimensional vector, "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ } else if (dim==~0U) {
+ *se = saved_char;
+ s0 = ss - 4>expr._data?ss - 4:expr._data;
+ cimg::strellipsize(s0,64);
+ throw CImgArgumentException("[_cimg_math_parser] "
+ "CImg<%s>::%s: %s%s Invalid construction of a vector with dynamic size, "
+ "in expression '%s%s%s'.",
+ pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",
+ s0!=expr._data?"...":"",s0,se<&expr.back()?"...":"");
+ }
+ }
+
+ // Evaluation functions, known by the parser.
+ // Defining these functions 'static' ensures that sizeof(mp_func)==sizeof(ulongT),
+ // so we can store pointers to them directly in the opcode vectors.
+#ifdef _mp_arg
+#undef _mp_arg
+#endif
+#define _mp_arg(x) mp.mem[mp.opcode[x]]
+
+ static double mp_abs(_cimg_math_parser& mp) {
+ return cimg::abs(_mp_arg(2));
+ }
+
+ static double mp_add(_cimg_math_parser& mp) {
+ return _mp_arg(2) + _mp_arg(3);
+ }
+
+ static double mp_acos(_cimg_math_parser& mp) {
+ return std::acos(_mp_arg(2));
+ }
+
+ static double mp_arg(_cimg_math_parser& mp) {
+ const int _ind = (int)_mp_arg(4);
+ const unsigned int
+ nb_args = (unsigned int)mp.opcode[2] - 4,
+ ind = _ind<0?_ind + nb_args:(unsigned int)_ind,
+ siz = (unsigned int)mp.opcode[3];
+ if (siz>0) {
+ if (ind>=nb_args) std::memset(&_mp_arg(1) + 1,0,siz*sizeof(double));
+ else std::memcpy(&_mp_arg(1) + 1,&_mp_arg(ind + 4) + 1,siz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+ if (ind>=nb_args) return 0;
+ return _mp_arg(ind + 4);
+ }
+
+ static double mp_argmin(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ double val = _mp_arg(3);
+ unsigned int argval = 0;
+ for (unsigned int i = 4; i<i_end; ++i) {
+ const double _val = _mp_arg(i);
+ if (_val<val) { val = _val; argval = i - 3; }
+ }
+ return (double)argval;
+ }
+
+ static double mp_argmax(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ double val = _mp_arg(3);
+ unsigned int argval = 0;
+ for (unsigned int i = 4; i<i_end; ++i) {
+ const double _val = _mp_arg(i);
+ if (_val>val) { val = _val; argval = i - 3; }
+ }
+ return (double)argval;
+ }
+
+ static double mp_asin(_cimg_math_parser& mp) {
+ return std::asin(_mp_arg(2));
+ }
+
+ static double mp_atan(_cimg_math_parser& mp) {
+ return std::atan(_mp_arg(2));
+ }
+
+ static double mp_atan2(_cimg_math_parser& mp) {
+ return std::atan2(_mp_arg(2),_mp_arg(3));
+ }
+
+ static double mp_bitwise_and(_cimg_math_parser& mp) {
+ return (double)((longT)_mp_arg(2) & (longT)_mp_arg(3));
+ }
+
+ static double mp_bitwise_left_shift(_cimg_math_parser& mp) {
+ return (double)((longT)_mp_arg(2)<<(unsigned int)_mp_arg(3));
+ }
+
+ static double mp_bitwise_not(_cimg_math_parser& mp) {
+ // Limit result to 32bits such that it can be entirely represented as a 'double'.
+ return (double)~(unsigned int)_mp_arg(2);
+ }
+
+ static double mp_bitwise_or(_cimg_math_parser& mp) {
+ return (double)((longT)_mp_arg(2) | (longT)_mp_arg(3));
+ }
+
+ static double mp_bitwise_right_shift(_cimg_math_parser& mp) {
+ return (double)((longT)_mp_arg(2)>>(unsigned int)_mp_arg(3));
+ }
+
+ static double mp_bitwise_xor(_cimg_math_parser& mp) {
+ return (double)((longT)_mp_arg(2) ^ (longT)_mp_arg(3));
+ }
+
+ static double mp_bool(_cimg_math_parser& mp) {
+ return (double)(bool)_mp_arg(2);
+ }
+
+ static double mp_break(_cimg_math_parser& mp) {
+ mp.break_type = 1;
+ mp.p_code = mp.p_break - 1;
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_breakpoint(_cimg_math_parser& mp) {
+ cimg_abort_test();
+ cimg::unused(mp);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_cbrt(_cimg_math_parser& mp) {
+ return cimg::cbrt(_mp_arg(2));
+ }
+
+ static double mp_complex_abs(_cimg_math_parser& mp) {
+ return cimg::_hypot(_mp_arg(2),_mp_arg(3));
+ }
+
+ static double mp_complex_conj(_cimg_math_parser& mp) {
+ const double *ptrs = &_mp_arg(2) + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ *(ptrd++) = *(ptrs++);
+ *ptrd = -*(ptrs);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_div_sv(_cimg_math_parser& mp) {
+ const double
+ *ptr2 = &_mp_arg(3) + 1,
+ r1 = _mp_arg(2),
+ r2 = *(ptr2++), i2 = *ptr2;
+ double *ptrd = &_mp_arg(1) + 1;
+ const double denom = r2*r2 + i2*i2;
+ *(ptrd++) = r1*r2/denom;
+ *ptrd = -r1*i2/denom;
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_div_vv(_cimg_math_parser& mp) {
+ const double
+ *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1,
+ r1 = *(ptr1++), i1 = *ptr1,
+ r2 = *(ptr2++), i2 = *ptr2;
+ double *ptrd = &_mp_arg(1) + 1;
+ const double denom = r2*r2 + i2*i2;
+ *(ptrd++) = (r1*r2 + i1*i2)/denom;
+ *ptrd = (r2*i1 - r1*i2)/denom;
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_exp(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs), er = std::exp(r);
+ *(ptrd++) = er*std::cos(i);
+ *(ptrd++) = er*std::sin(i);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_log(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptrs = &_mp_arg(2) + 1, r = *(ptrs++), i = *(ptrs);
+ *(ptrd++) = 0.5*std::log(r*r + i*i);
+ *(ptrd++) = std::atan2(i,r);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_mul(_cimg_math_parser& mp) {
+ const double
+ *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1,
+ r1 = *(ptr1++), i1 = *ptr1,
+ r2 = *(ptr2++), i2 = *ptr2;
+ double *ptrd = &_mp_arg(1) + 1;
+ *(ptrd++) = r1*r2 - i1*i2;
+ *(ptrd++) = r1*i2 + r2*i1;
+ return cimg::type<double>::nan();
+ }
+
+ static void _mp_complex_pow(const double r1, const double i1,
+ const double r2, const double i2,
+ double *ptrd) {
+ double ro, io;
+ if (cimg::abs(i2)<1e-15) { // Exponent is real
+ if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) {
+ if (cimg::abs(r2)<1e-15) { ro = 1; io = 0; }
+ else ro = io = 0;
+ } else {
+ const double
+ mod1_2 = r1*r1 + i1*i1,
+ phi1 = std::atan2(i1,r1),
+ modo = std::pow(mod1_2,0.5*r2),
+ phio = r2*phi1;
+ ro = modo*std::cos(phio);
+ io = modo*std::sin(phio);
+ }
+ } else { // Exponent is complex
+ if (cimg::abs(r1)<1e-15 && cimg::abs(i1)<1e-15) ro = io = 0;
+ const double
+ mod1_2 = r1*r1 + i1*i1,
+ phi1 = std::atan2(i1,r1),
+ modo = std::pow(mod1_2,0.5*r2)*std::exp(-i2*phi1),
+ phio = r2*phi1 + 0.5*i2*std::log(mod1_2);
+ ro = modo*std::cos(phio);
+ io = modo*std::sin(phio);
+ }
+ *(ptrd++) = ro;
+ *ptrd = io;
+ }
+
+ static double mp_complex_pow_ss(_cimg_math_parser& mp) {
+ const double val1 = _mp_arg(2), val2 = _mp_arg(3);
+ double *ptrd = &_mp_arg(1) + 1;
+ _mp_complex_pow(val1,0,val2,0,ptrd);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_pow_sv(_cimg_math_parser& mp) {
+ const double val1 = _mp_arg(2), *ptr2 = &_mp_arg(3) + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ _mp_complex_pow(val1,0,ptr2[0],ptr2[1],ptrd);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_pow_vs(_cimg_math_parser& mp) {
+ const double *ptr1 = &_mp_arg(2) + 1, val2 = _mp_arg(3);
+ double *ptrd = &_mp_arg(1) + 1;
+ _mp_complex_pow(ptr1[0],ptr1[1],val2,0,ptrd);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_complex_pow_vv(_cimg_math_parser& mp) {
+ const double *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ _mp_complex_pow(ptr1[0],ptr1[1],ptr2[0],ptr2[1],ptrd);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_continue(_cimg_math_parser& mp) {
+ mp.break_type = 2;
+ mp.p_code = mp.p_break - 1;
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_cos(_cimg_math_parser& mp) {
+ return std::cos(_mp_arg(2));
+ }
+
+ static double mp_cosh(_cimg_math_parser& mp) {
+ return std::cosh(_mp_arg(2));
+ }
+
+ static double mp_cross(_cimg_math_parser& mp) {
+ CImg<doubleT>
+ vout(&_mp_arg(1) + 1,1,3,1,1,true),
+ v1(&_mp_arg(2) + 1,1,3,1,1,true),
+ v2(&_mp_arg(3) + 1,1,3,1,1,true);
+ (vout = v1).cross(v2);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_cut(_cimg_math_parser& mp) {
+ double val = _mp_arg(2), cmin = _mp_arg(3), cmax = _mp_arg(4);
+ return val<cmin?cmin:val>cmax?cmax:val;
+ }
+
+ static double mp_debug(_cimg_math_parser& mp) {
+ CImg<charT> expr(mp.opcode[2] - 4);
+ const ulongT *ptrs = mp.opcode._data + 4;
+ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++);
+ cimg::strellipsize(expr);
+ const ulongT g_target = mp.opcode[1];
+
+#ifndef cimg_use_openmp
+ const unsigned int n_thread = 0;
+#else
+ const unsigned int n_thread = omp_get_thread_num();
+#endif
+ cimg_pragma_openmp(critical)
+ {
+ std::fprintf(cimg::output(),
+ "\n[_cimg_math_parser] %p[thread #%u]:%*c"
+ "Start debugging expression '%s', code length %u -> mem[%u] (memsize: %u)",
+ (void*)&mp,n_thread,mp.debug_indent,' ',
+ expr._data,(unsigned int)mp.opcode[3],(unsigned int)g_target,mp.mem._width);
+ std::fflush(cimg::output());
+ mp.debug_indent+=3;
+ }
+ const CImg<ulongT> *const p_end = (++mp.p_code) + mp.opcode[3];
+ CImg<ulongT> _op;
+ for ( ; mp.p_code<p_end; ++mp.p_code) {
+ const CImg<ulongT> &op = *mp.p_code;
+ mp.opcode._data = op._data;
+
+ _op.assign(1,op._height - 1);
+ const ulongT *ptrs = op._data + 1;
+ for (ulongT *ptrd = _op._data, *const ptrde = _op._data + _op._height; ptrd<ptrde; ++ptrd)
+ *ptrd = *(ptrs++);
+
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ cimg_pragma_openmp(critical)
+ {
+ std::fprintf(cimg::output(),
+ "\n[_cimg_math_parser] %p[thread #%u]:%*c"
+ "Opcode %p = [ %p,%s ] -> mem[%u] = %g",
+ (void*)&mp,n_thread,mp.debug_indent,' ',
+ (void*)mp.opcode._data,(void*)*mp.opcode,_op.value_string().data(),
+ (unsigned int)target,mp.mem[target]);
+ std::fflush(cimg::output());
+ }
+ }
+ cimg_pragma_openmp(critical)
+ {
+ mp.debug_indent-=3;
+ std::fprintf(cimg::output(),
+ "\n[_cimg_math_parser] %p[thread #%u]:%*c"
+ "End debugging expression '%s' -> mem[%u] = %g (memsize: %u)",
+ (void*)&mp,n_thread,mp.debug_indent,' ',
+ expr._data,(unsigned int)g_target,mp.mem[g_target],mp.mem._width);
+ std::fflush(cimg::output());
+ }
+ --mp.p_code;
+ return mp.mem[g_target];
+ }
+
+ static double mp_decrement(_cimg_math_parser& mp) {
+ return _mp_arg(2) - 1;
+ }
+
+ static double mp_det(_cimg_math_parser& mp) {
+ const double *ptrs = &_mp_arg(2) + 1;
+ const unsigned int k = (unsigned int)mp.opcode[3];
+ return CImg<double>(ptrs,k,k,1,1,true).det();
+ }
+
+ static double mp_diag(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptrs = &_mp_arg(2) + 1;
+ const unsigned int k = (unsigned int)mp.opcode[3];
+ CImg<double>(ptrd,k,k,1,1,true) = CImg<double>(ptrs,1,k,1,1,true).get_diagonal();
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_display_memory(_cimg_math_parser& mp) {
+ cimg::unused(mp);
+ std::fputc('\n',cimg::output());
+ mp.mem.display("[_cimg_math_parser] Memory snapshot");
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_display(_cimg_math_parser& mp) {
+ const unsigned int
+ _siz = (unsigned int)mp.opcode[3],
+ siz = _siz?_siz:1;
+ const double *const ptr = &_mp_arg(1) + (_siz?1:0);
+ const int
+ w = (int)_mp_arg(4),
+ h = (int)_mp_arg(5),
+ d = (int)_mp_arg(6),
+ s = (int)_mp_arg(7);
+ CImg<double> img;
+ if (w>0 && h>0 && d>0 && s>0) {
+ if ((unsigned int)w*h*d*s<=siz) img.assign(ptr,w,h,d,s,true);
+ else img.assign(ptr,siz).resize(w,h,d,s,-1);
+ } else img.assign(ptr,1,siz,1,1,true);
+
+ CImg<charT> expr(mp.opcode[2] - 8);
+ const ulongT *ptrs = mp.opcode._data + 8;
+ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++);
+ ((CImg<charT>::string("[_cimg_math_parser] ",false,true),expr)>'x').move_to(expr);
+ cimg::strellipsize(expr);
+ std::fputc('\n',cimg::output());
+ img.display(expr._data);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_div(_cimg_math_parser& mp) {
+ return _mp_arg(2)/_mp_arg(3);
+ }
+
+ static double mp_dot(_cimg_math_parser& mp) {
+ const unsigned int siz = (unsigned int)mp.opcode[4];
+ return CImg<doubleT>(&_mp_arg(2) + 1,1,siz,1,1,true).
+ dot(CImg<doubleT>(&_mp_arg(3) + 1,1,siz,1,1,true));
+ }
+
+ static double mp_dowhile(_cimg_math_parser& mp) {
+ const ulongT
+ mem_body = mp.opcode[1],
+ mem_cond = mp.opcode[2];
+ const CImg<ulongT>
+ *const p_body = ++mp.p_code,
+ *const p_cond = p_body + mp.opcode[3],
+ *const p_end = p_cond + mp.opcode[4];
+ const unsigned int vsiz = (unsigned int)mp.opcode[5];
+ if (mp.opcode[6]) { // Set default value for result and condition if necessary
+ if (vsiz) CImg<doubleT>(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type<double>::nan());
+ else mp.mem[mem_body] = cimg::type<double>::nan();
+ }
+ if (mp.opcode[7]) mp.mem[mem_cond] = 0;
+
+ const unsigned int _break_type = mp.break_type;
+ mp.break_type = 0;
+ do {
+ for (mp.p_code = p_body; mp.p_code<p_cond; ++mp.p_code) { // Evaluate body
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0;
+ for (mp.p_code = p_cond; mp.p_code<p_end; ++mp.p_code) { // Evaluate condition
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0;
+ } while (mp.mem[mem_cond]);
+ mp.break_type = _break_type;
+ mp.p_code = p_end - 1;
+ return mp.mem[mem_body];
+ }
+
+ static double mp_eq(_cimg_math_parser& mp) {
+ return (double)(_mp_arg(2)==_mp_arg(3));
+ }
+
+ static double mp_exp(_cimg_math_parser& mp) {
+ return std::exp(_mp_arg(2));
+ }
+
+ static double mp_eye(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int k = (unsigned int)mp.opcode[2];
+ CImg<double>(ptrd,k,k,1,1,true).identity_matrix();
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_factorial(_cimg_math_parser& mp) {
+ return cimg::factorial(_mp_arg(2));
+ }
+
+ static double mp_fibonacci(_cimg_math_parser& mp) {
+ return cimg::fibonacci((int)_mp_arg(2));
+ }
+
+ static double mp_find(_cimg_math_parser& mp) {
+ const bool is_forward = (bool)_mp_arg(5);
+ const ulongT siz = (ulongT)mp.opcode[3];
+ longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz - 1);
+ if (ind<0 || ind>=(longT)siz) return -1.;
+ const double
+ *const ptrb = &_mp_arg(2) + 1,
+ *const ptre = ptrb + siz,
+ val = _mp_arg(4),
+ *ptr = ptrb + ind;
+
+ // Forward search
+ if (is_forward) {
+ while (ptr<ptre && *ptr!=val) ++ptr;
+ return ptr==ptre?-1.:(double)(ptr - ptrb);
+ }
+
+ // Backward search.
+ while (ptr>=ptrb && *ptr!=val) --ptr;
+ return ptr<ptrb?-1.:(double)(ptr - ptrb);
+ }
+
+ static double mp_find_seq(_cimg_math_parser& mp) {
+ const bool is_forward = (bool)_mp_arg(6);
+ const ulongT
+ siz1 = (ulongT)mp.opcode[3],
+ siz2 = (ulongT)mp.opcode[5];
+ longT ind = (longT)(mp.opcode[7]!=_cimg_mp_slot_nan?_mp_arg(7):is_forward?0:siz1 - 1);
+ if (ind<0 || ind>=(longT)siz1) return -1.;
+ const double
+ *const ptr1b = &_mp_arg(2) + 1,
+ *const ptr1e = ptr1b + siz1,
+ *const ptr2b = &_mp_arg(4) + 1,
+ *const ptr2e = ptr2b + siz2,
+ *ptr1 = ptr1b + ind,
+ *p1 = 0,
+ *p2 = 0;
+
+ // Forward search.
+ if (is_forward) {
+ do {
+ while (ptr1<ptr1e && *ptr1!=*ptr2b) ++ptr1;
+ p1 = ptr1 + 1;
+ p2 = ptr2b + 1;
+ while (p1<ptr1e && p2<ptr2e && *p1==*p2) { ++p1; ++p2; }
+ } while (p2<ptr2e && ++ptr1<ptr1e);
+ return p2<ptr2e?-1.0:(double)(ptr1 - ptr1b);
+ }
+
+ // Backward search.
+ do {
+ while (ptr1>=ptr1b && *ptr1!=*ptr2b) --ptr1;
+ p1 = ptr1 + 1;
+ p2 = ptr2b + 1;
+ while (p1<ptr1e && p2<ptr2e && *p1==*p2) { ++p1; ++p2; }
+ } while (p2<ptr2e && --ptr1>=ptr1b);
+ return p2<ptr2e?-1.0:(double)(ptr1 - ptr1b);
+ }
+
+ static double mp_for(_cimg_math_parser& mp) {
+ const ulongT
+ mem_body = mp.opcode[1],
+ mem_cond = mp.opcode[3];
+ const CImg<ulongT>
+ *const p_init = ++mp.p_code,
+ *const p_cond = p_init + mp.opcode[4],
+ *const p_body = p_cond + mp.opcode[5],
+ *const p_post = p_body + mp.opcode[6],
+ *const p_end = p_post + mp.opcode[7];
+ const unsigned int vsiz = (unsigned int)mp.opcode[2];
+ bool is_cond = false;
+ if (mp.opcode[8]) { // Set default value for result and condition if necessary
+ if (vsiz) CImg<doubleT>(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type<double>::nan());
+ else mp.mem[mem_body] = cimg::type<double>::nan();
+ }
+ if (mp.opcode[9]) mp.mem[mem_cond] = 0;
+ const unsigned int _break_type = mp.break_type;
+ mp.break_type = 0;
+
+ for (mp.p_code = p_init; mp.p_code<p_cond; ++mp.p_code) { // Evaluate init
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+
+ if (!mp.break_type) do {
+ for (mp.p_code = p_cond; mp.p_code<p_body; ++mp.p_code) { // Evaluate condition
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.break_type==1) break;
+
+ is_cond = (bool)mp.mem[mem_cond];
+ if (is_cond && !mp.break_type) {
+ for (mp.p_code = p_body; mp.p_code<p_post; ++mp.p_code) { // Evaluate body
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0;
+
+ for (mp.p_code = p_post; mp.p_code<p_end; ++mp.p_code) { // Evaluate post-code
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0;
+ }
+ } while (is_cond);
+
+ mp.break_type = _break_type;
+ mp.p_code = p_end - 1;
+ return mp.mem[mem_body];
+ }
+
+ static double mp_g(_cimg_math_parser& mp) {
+ cimg::unused(mp);
+ return cimg::grand();
+ }
+
+ static double mp_gauss(_cimg_math_parser& mp) {
+ const double x = _mp_arg(2), s = _mp_arg(3);
+ return std::exp(-x*x/(2*s*s))/std::sqrt(2*s*s*cimg::PI);
+ }
+
+ static double mp_gt(_cimg_math_parser& mp) {
+ return (double)(_mp_arg(2)>_mp_arg(3));
+ }
+
+ static double mp_gte(_cimg_math_parser& mp) {
+ return (double)(_mp_arg(2)>=_mp_arg(3));
+ }
+
+ static double mp_i(_cimg_math_parser& mp) {
+ return (double)mp.imgin.atXYZC((int)mp.mem[_cimg_mp_slot_x],(int)mp.mem[_cimg_mp_slot_y],
+ (int)mp.mem[_cimg_mp_slot_z],(int)mp.mem[_cimg_mp_slot_c],(T)0);
+ }
+
+ static double mp_if(_cimg_math_parser& mp) {
+ const bool is_cond = (bool)_mp_arg(2);
+ const ulongT
+ mem_left = mp.opcode[3],
+ mem_right = mp.opcode[4];
+ const CImg<ulongT>
+ *const p_right = ++mp.p_code + mp.opcode[5],
+ *const p_end = p_right + mp.opcode[6];
+ const unsigned int vtarget = (unsigned int)mp.opcode[1], vsiz = (unsigned int)mp.opcode[7];
+ if (is_cond) for ( ; mp.p_code<p_right; ++mp.p_code) {
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ else for (mp.p_code = p_right; mp.p_code<p_end; ++mp.p_code) {
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.p_code==mp.p_break) --mp.p_code;
+ else mp.p_code = p_end - 1;
+ if (vsiz) std::memcpy(&mp.mem[vtarget] + 1,&mp.mem[is_cond?mem_left:mem_right] + 1,sizeof(double)*vsiz);
+ return mp.mem[is_cond?mem_left:mem_right];
+ }
+
+ static double mp_image_crop(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), z = (int)_mp_arg(5), c = (int)_mp_arg(6);
+ const unsigned int
+ dx = (unsigned int)mp.opcode[7],
+ dy = (unsigned int)mp.opcode[8],
+ dz = (unsigned int)mp.opcode[9],
+ dc = (unsigned int)mp.opcode[10];
+ const bool boundary_conditions = (bool)_mp_arg(11);
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ if (!img) std::memset(ptrd,0,dx*dy*dz*dc*sizeof(double));
+ else CImg<double>(ptrd,dx,dy,dz,dc,true) = img.get_crop(x,y,z,c,
+ x + dx - 1,y + dy - 1,
+ z + dz - 1,c + dc - 1,
+ boundary_conditions);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_image_d(_cimg_math_parser& mp) {
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ return (double)img.depth();
+ }
+
+ static double mp_image_draw(_cimg_math_parser& mp) {
+ const int x = (int)_mp_arg(4), y = (int)_mp_arg(5), z = (int)_mp_arg(6), c = (int)_mp_arg(7);
+ unsigned int ind = (unsigned int)mp.opcode[3];
+
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.listin.width());
+ CImg<T> &img = ind==~0U?mp.imgout:mp.listout[ind];
+ unsigned int
+ dx = (unsigned int)mp.opcode[8],
+ dy = (unsigned int)mp.opcode[9],
+ dz = (unsigned int)mp.opcode[10],
+ dc = (unsigned int)mp.opcode[11];
+ dx = dx==~0U?img._width:(unsigned int)_mp_arg(8);
+ dy = dy==~0U?img._height:(unsigned int)_mp_arg(9);
+ dz = dz==~0U?img._depth:(unsigned int)_mp_arg(10);
+ dc = dc==~0U?img._spectrum:(unsigned int)_mp_arg(11);
+
+ const ulongT sizS = mp.opcode[2];
+ if (sizS<(ulongT)dx*dy*dz*dc)
+ throw CImgArgumentException("[_cimg_math_parser] CImg<%s>: Function 'draw()': "
+ "Sprite dimension (%lu values) and specified sprite geometry (%u,%u,%u,%u) "
+ "(%lu values) do not match.",
+ mp.imgin.pixel_type(),sizS,dx,dy,dz,dc,(ulongT)dx*dy*dz*dc);
+ CImg<double> S(&_mp_arg(1) + 1,dx,dy,dz,dc,true);
+ const float opacity = (float)_mp_arg(12);
+
+ if (img._data) {
+ if (mp.opcode[13]!=~0U) { // Opacity mask specified
+ const ulongT sizM = mp.opcode[14];
+ if (sizM<(ulongT)dx*dy*dz)
+ throw CImgArgumentException("[_cimg_math_parser] CImg<%s>: Function 'draw()': "
+ "Mask dimension (%lu values) and specified sprite geometry (%u,%u,%u,%u) "
+ "(%lu values) do not match.",
+ mp.imgin.pixel_type(),sizS,dx,dy,dz,dc,(ulongT)dx*dy*dz*dc);
+ const CImg<double> M(&_mp_arg(13) + 1,dx,dy,dz,(unsigned int)(sizM/(dx*dy*dz)),true);
+ img.draw_image(x,y,z,c,S,M,opacity,(float)_mp_arg(15));
+ } else img.draw_image(x,y,z,c,S,opacity);
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_image_h(_cimg_math_parser& mp) {
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ return (double)img.height();
+ }
+
+ static double mp_image_resize(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width());
+ cimg::mutex(6);
+ CImg<T> &img = mp.listout[ind];
+ const double
+ _w = mp.opcode[3]==~0U?-100:_mp_arg(3),
+ _h = mp.opcode[4]==~0U?-100:_mp_arg(4),
+ _d = mp.opcode[5]==~0U?-100:_mp_arg(5),
+ _s = mp.opcode[6]==~0U?-100:_mp_arg(6);
+ const unsigned int
+ w = (unsigned int)(_w>=0?_w:-_w*img.width()/100),
+ h = (unsigned int)(_h>=0?_h:-_h*img.height()/100),
+ d = (unsigned int)(_d>=0?_d:-_d*img.depth()/100),
+ s = (unsigned int)(_s>=0?_s:-_s*img.spectrum()/100),
+ interp = (int)_mp_arg(7);
+ if (mp.is_fill && img._data==mp.imgout._data) {
+ cimg::mutex(6,0);
+ throw CImgArgumentException("[_cimg_math_parser] CImg<%s>: Function 'resize()': "
+ "Cannot both fill and resize image (%u,%u,%u,%u) "
+ "to new dimensions (%u,%u,%u,%u).",
+ img.pixel_type(),img._width,img._height,img._depth,img._spectrum,w,h,d,s);
+ }
+ const unsigned int
+ boundary = (int)_mp_arg(8);
+ const float
+ cx = (float)_mp_arg(9),
+ cy = (float)_mp_arg(10),
+ cz = (float)_mp_arg(11),
+ cc = (float)_mp_arg(12);
+ img.resize(w,h,d,s,interp,boundary,cx,cy,cz,cc);
+ cimg::mutex(6,0);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_image_s(_cimg_math_parser& mp) {
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ return (double)img.spectrum();
+ }
+
+ static double mp_image_w(_cimg_math_parser& mp) {
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ return (double)img.width();
+ }
+
+ static double mp_image_wh(_cimg_math_parser& mp) {
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ return (double)img.width()*img.height();
+ }
+
+ static double mp_image_whd(_cimg_math_parser& mp) {
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ return (double)img.width()*img.height()*img.depth();
+ }
+
+ static double mp_image_whds(_cimg_math_parser& mp) {
+ unsigned int ind = (unsigned int)mp.opcode[2];
+ if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[ind];
+ return (double)img.width()*img.height()*img.depth()*img.spectrum();
+ }
+
+ static double mp_increment(_cimg_math_parser& mp) {
+ return _mp_arg(2) + 1;
+ }
+
+ static double mp_int(_cimg_math_parser& mp) {
+ return (double)(longT)_mp_arg(2);
+ }
+
+ static double mp_ioff(_cimg_math_parser& mp) {
+ const unsigned int
+ boundary_conditions = (unsigned int)_mp_arg(3);
+ const CImg<T> &img = mp.imgin;
+ const longT
+ off = (longT)_mp_arg(2),
+ whds = (longT)img.size();
+ if (off>=0 && off<whds) return (double)img[off];
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whds2 = 2*whds, moff = cimg::mod(off,whds2);
+ return (double)img[moff<whds?moff:whds2 - moff - 1];
+ }
+ case 2 : // Periodic
+ return (double)img[cimg::mod(off,whds)];
+ case 1 : // Neumann
+ return (double)img[off<0?0:whds - 1];
+ default : // Dirichlet
+ return 0;
+ }
+ return 0;
+ }
+
+ static double mp_isbool(_cimg_math_parser& mp) {
+ const double val = _mp_arg(2);
+ return (double)(val==0.0 || val==1.0);
+ }
+
+ static double mp_isin(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ const double val = _mp_arg(3);
+ for (unsigned int i = 4; i<i_end; ++i)
+ if (val==_mp_arg(i)) return 1.0;
+ return 0.0;
+ }
+
+ static double mp_isinf(_cimg_math_parser& mp) {
+ return (double)cimg::type<double>::is_inf(_mp_arg(2));
+ }
+
+ static double mp_isint(_cimg_math_parser& mp) {
+ return (double)(cimg::mod(_mp_arg(2),1.0)==0);
+ }
+
+ static double mp_isnan(_cimg_math_parser& mp) {
+ return (double)cimg::type<double>::is_nan(_mp_arg(2));
+ }
+
+ static double mp_ixyzc(_cimg_math_parser& mp) {
+ const unsigned int
+ interpolation = (unsigned int)_mp_arg(6),
+ boundary_conditions = (unsigned int)_mp_arg(7);
+ const CImg<T> &img = mp.imgin;
+ const double
+ x = _mp_arg(2), y = _mp_arg(3),
+ z = _mp_arg(4), c = _mp_arg(5);
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2),
+ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2);
+ return (double)img(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img(cimg::mod((int)x,img.width()),
+ cimg::mod((int)y,img.height()),
+ cimg::mod((int)z,img.depth()),
+ cimg::mod((int)c,img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._atXYZC((int)x,(int)y,(int)z,(int)c);
+ default : // Dirichlet
+ return (double)img.atXYZC((int)x,(int)y,(int)z,(int)c,(T)0);
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(), s2 = 2.0f*img.spectrum(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2),
+ mz = cimg::mod((float)z,d2), mc = cimg::mod((float)c,s2);
+ return (double)img._linear_atXYZC(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img._linear_atXYZC(cimg::mod((float)x,(float)img.width()),
+ cimg::mod((float)y,(float)img.height()),
+ cimg::mod((float)z,(float)img.depth()),
+ cimg::mod((float)c,(float)img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._linear_atXYZC((float)x,(float)y,(float)z,(float)c);
+ default : // Dirichlet
+ return (double)img.linear_atXYZC((float)x,(float)y,(float)z,(float)c,(T)0);
+ }
+ }
+
+ static double mp_joff(_cimg_math_parser& mp) {
+ const unsigned int
+ boundary_conditions = (unsigned int)_mp_arg(3);
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const CImg<T> &img = mp.imgin;
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2),
+ whds = (longT)img.size();
+ if (off>=0 && off<whds) return (double)img[off];
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whds2 = 2*whds, moff = cimg::mod(off,whds2);
+ return (double)img[moff<whds?moff:whds2 - moff - 1];
+ }
+ case 2 : // Periodic
+ return (double)img[cimg::mod(off,whds)];
+ case 1 : // Neumann
+ return (double)img[off<0?0:whds - 1];
+ default : // Dirichlet
+ return 0;
+ }
+ return 0;
+ }
+
+ static double mp_jxyzc(_cimg_math_parser& mp) {
+ const unsigned int
+ interpolation = (unsigned int)_mp_arg(6),
+ boundary_conditions = (unsigned int)_mp_arg(7);
+ const CImg<T> &img = mp.imgin;
+ const double
+ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y],
+ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c],
+ x = ox + _mp_arg(2), y = oy + _mp_arg(3),
+ z = oz + _mp_arg(4), c = oc + _mp_arg(5);
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2),
+ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2);
+ return (double)img(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img(cimg::mod((int)x,img.width()),
+ cimg::mod((int)y,img.height()),
+ cimg::mod((int)z,img.depth()),
+ cimg::mod((int)c,img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._atXYZC((int)x,(int)y,(int)z,(int)c);
+ default : // Dirichlet
+ return (double)img.atXYZC((int)x,(int)y,(int)z,(int)c,(T)0);
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(), s2 = 2.0f*img.spectrum(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2),
+ mz = cimg::mod((float)z,d2), mc = cimg::mod((float)c,s2);
+ return (double)img._linear_atXYZC(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img._linear_atXYZC(cimg::mod((float)x,(float)img.width()),
+ cimg::mod((float)y,(float)img.height()),
+ cimg::mod((float)z,(float)img.depth()),
+ cimg::mod((float)c,(float)img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._linear_atXYZC((float)x,(float)y,(float)z,(float)c);
+ default : // Dirichlet
+ return (double)img.linear_atXYZC((float)x,(float)y,(float)z,(float)c,(T)0);
+ }
+ }
+
+ static double mp_kth(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ CImg<doubleT> vals(i_end - 4);
+ double *p = vals.data();
+ for (unsigned int i = 4; i<i_end; ++i) *(p++) = _mp_arg(i);
+ int ind = (int)cimg::round(_mp_arg(3));
+ if (ind<0) ind+=vals.width() + 1;
+ ind = std::max(1,std::min(vals.width(),ind));
+ return vals.kth_smallest(ind - 1);
+ }
+
+ static double mp_linear_add(_cimg_math_parser& mp) {
+ return _mp_arg(2)*_mp_arg(3) + _mp_arg(4);
+ }
+
+ static double mp_linear_sub_left(_cimg_math_parser& mp) {
+ return _mp_arg(2)*_mp_arg(3) - _mp_arg(4);
+ }
+
+ static double mp_linear_sub_right(_cimg_math_parser& mp) {
+ return _mp_arg(4) - _mp_arg(2)*_mp_arg(3);
+ }
+
+ static double mp_list_depth(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._depth;
+ }
+
+ static double mp_list_find(_cimg_math_parser& mp) {
+ const unsigned int
+ indi = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = mp.listin[indi];
+ const bool is_forward = (bool)_mp_arg(4);
+ const ulongT siz = (ulongT)img.size();
+ longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):is_forward?0:siz - 1);
+ if (ind<0 || ind>=(longT)siz) return -1.;
+ const T
+ *const ptrb = img.data(),
+ *const ptre = img.end(),
+ *ptr = ptrb + ind;
+ const double val = _mp_arg(3);
+
+ // Forward search
+ if (is_forward) {
+ while (ptr<ptre && (double)*ptr!=val) ++ptr;
+ return ptr==ptre?-1.:(double)(ptr - ptrb);
+ }
+
+ // Backward search.
+ while (ptr>=ptrb && (double)*ptr!=val) --ptr;
+ return ptr<ptrb?-1.:(double)(ptr - ptrb);
+ }
+
+ static double mp_list_find_seq(_cimg_math_parser& mp) {
+ const unsigned int
+ indi = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ const CImg<T> &img = mp.listin[indi];
+ const bool is_forward = (bool)_mp_arg(5);
+ const ulongT
+ siz1 = (ulongT)img.size(),
+ siz2 = (ulongT)mp.opcode[4];
+ longT ind = (longT)(mp.opcode[6]!=_cimg_mp_slot_nan?_mp_arg(6):is_forward?0:siz1 - 1);
+ if (ind<0 || ind>=(longT)siz1) return -1.;
+ const T
+ *const ptr1b = img.data(),
+ *const ptr1e = ptr1b + siz1,
+ *ptr1 = ptr1b + ind,
+ *p1 = 0;
+ const double
+ *const ptr2b = &_mp_arg(3) + 1,
+ *const ptr2e = ptr2b + siz2,
+ *p2 = 0;
+
+ // Forward search.
+ if (is_forward) {
+ do {
+ while (ptr1<ptr1e && *ptr1!=*ptr2b) ++ptr1;
+ p1 = ptr1 + 1;
+ p2 = ptr2b + 1;
+ while (p1<ptr1e && p2<ptr2e && *p1==*p2) { ++p1; ++p2; }
+ } while (p2<ptr2e && ++ptr1<ptr1e);
+ return p2<ptr2e?-1.0:(double)(ptr1 - ptr1b);
+ }
+
+ // Backward search.
+ do {
+ while (ptr1>=ptr1b && *ptr1!=*ptr2b) --ptr1;
+ p1 = ptr1 + 1;
+ p2 = ptr2b + 1;
+ while (p1<ptr1e && p2<ptr2e && *p1==*p2) { ++p1; ++p2; }
+ } while (p2<ptr2e && --ptr1>=ptr1b);
+ return p2<ptr2e?-1.0:(double)(ptr1 - ptr1b);
+ }
+
+ static double mp_list_height(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._height;
+ }
+
+ static double mp_list_ioff(_cimg_math_parser& mp) {
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ boundary_conditions = (unsigned int)_mp_arg(4);
+ const CImg<T> &img = mp.listin[ind];
+ const longT
+ off = (longT)_mp_arg(3),
+ whds = (longT)img.size();
+ if (off>=0 && off<whds) return (double)img[off];
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whds2 = 2*whds, moff = cimg::mod(off,whds2);
+ return (double)img[moff<whds?moff:whds2 - moff - 1];
+ }
+ case 2 : // Periodic
+ return (double)img[cimg::mod(off,whds)];
+ case 1 : // Neumann
+ return (double)img[off<0?0:whds - 1];
+ default : // Dirichlet
+ return 0;
+ }
+ return 0;
+ }
+
+ static double mp_list_is_shared(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._is_shared;
+ }
+
+ static double mp_list_ixyzc(_cimg_math_parser& mp) {
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ interpolation = (unsigned int)_mp_arg(7),
+ boundary_conditions = (unsigned int)_mp_arg(8);
+ const CImg<T> &img = mp.listin[ind];
+ const double
+ x = _mp_arg(3), y = _mp_arg(4),
+ z = _mp_arg(5), c = _mp_arg(6);
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2),
+ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2);
+ return (double)img(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img(cimg::mod((int)x,img.width()),
+ cimg::mod((int)y,img.height()),
+ cimg::mod((int)z,img.depth()),
+ cimg::mod((int)c,img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._atXYZC((int)x,(int)y,(int)z,(int)c);
+ default : // Dirichlet
+ return (double)img.atXYZC((int)x,(int)y,(int)z,(int)c,(T)0);
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(), s2 = 2.0f*img.spectrum(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2),
+ mz = cimg::mod((float)z,d2), mc = cimg::mod((float)c,s2);
+ return (double)img._linear_atXYZC(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img._linear_atXYZC(cimg::mod((float)x,(float)img.width()),
+ cimg::mod((float)y,(float)img.height()),
+ cimg::mod((float)z,(float)img.depth()),
+ cimg::mod((float)c,(float)img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._linear_atXYZC((float)x,(float)y,(float)z,(float)c);
+ default : // Dirichlet
+ return (double)img.linear_atXYZC((float)x,(float)y,(float)z,(float)c,(T)0);
+ }
+ }
+
+ static double mp_list_joff(_cimg_math_parser& mp) {
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ boundary_conditions = (unsigned int)_mp_arg(4);
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const CImg<T> &img = mp.listin[ind];
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3),
+ whds = (longT)img.size();
+ if (off>=0 && off<whds) return (double)img[off];
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whds2 = 2*whds, moff = cimg::mod(off,whds2);
+ return (double)img[moff<whds?moff:whds2 - moff - 1];
+ }
+ case 2 : // Periodic
+ return (double)img[cimg::mod(off,whds)];
+ case 1 : // Neumann
+ return (double)img[off<0?0:whds - 1];
+ default : // Dirichlet
+ return 0;
+ }
+ return 0;
+ }
+
+ static double mp_list_jxyzc(_cimg_math_parser& mp) {
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ interpolation = (unsigned int)_mp_arg(7),
+ boundary_conditions = (unsigned int)_mp_arg(8);
+ const CImg<T> &img = mp.listin[ind];
+ const double
+ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y],
+ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c],
+ x = ox + _mp_arg(3), y = oy + _mp_arg(4),
+ z = oz + _mp_arg(5), c = oc + _mp_arg(6);
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2),
+ mz = cimg::mod((int)z,d2), mc = cimg::mod((int)c,s2);
+ return (double)img(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img(cimg::mod((int)x,img.width()),
+ cimg::mod((int)y,img.height()),
+ cimg::mod((int)z,img.depth()),
+ cimg::mod((int)c,img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._atXYZC((int)x,(int)y,(int)z,(int)c);
+ default : // Dirichlet
+ return (double)img.atXYZC((int)x,(int)y,(int)z,(int)c,(T)0);
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(), s2 = 2.0f*img.spectrum(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2),
+ mz = cimg::mod((float)z,d2), mc = cimg::mod((float)c,s2);
+ return (double)img._linear_atXYZC(mx<img.width()?mx:w2 - mx - 1,
+ my<img.height()?my:h2 - my - 1,
+ mz<img.depth()?mz:d2 - mz - 1,
+ mc<img.spectrum()?mc:s2 - mc - 1);
+ }
+ case 2 : // Periodic
+ return (double)img._linear_atXYZC(cimg::mod((float)x,(float)img.width()),
+ cimg::mod((float)y,(float)img.height()),
+ cimg::mod((float)z,(float)img.depth()),
+ cimg::mod((float)c,(float)img.spectrum()));
+ case 1 : // Neumann
+ return (double)img._linear_atXYZC((float)x,(float)y,(float)z,(float)c);
+ default : // Dirichlet
+ return (double)img.linear_atXYZC((float)x,(float)y,(float)z,(float)c,(T)0);
+ }
+ }
+
+ static double mp_list_median(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ if (!mp.list_median) mp.list_median.assign(mp.listin._width);
+ if (!mp.list_median[ind]) CImg<doubleT>::vector(mp.listin[ind].median()).move_to(mp.list_median[ind]);
+ return *mp.list_median[ind];
+ }
+
+ static double mp_list_set_ioff(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const longT
+ off = (longT)_mp_arg(3),
+ whds = (longT)img.size();
+ const double val = _mp_arg(1);
+ if (off>=0 && off<whds) img[off] = (T)val;
+ return val;
+ }
+
+ static double mp_list_set_ixyzc(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const int
+ x = (int)_mp_arg(3), y = (int)_mp_arg(4),
+ z = (int)_mp_arg(5), c = (int)_mp_arg(6);
+ const double val = _mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() &&
+ z>=0 && z<img.depth() && c>=0 && c<img.spectrum())
+ img(x,y,z,c) = (T)val;
+ return val;
+ }
+
+ static double mp_list_set_joff(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3),
+ whds = (longT)img.size();
+ const double val = _mp_arg(1);
+ if (off>=0 && off<whds) img[off] = (T)val;
+ return val;
+ }
+
+ static double mp_list_set_jxyzc(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const double
+ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y],
+ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c];
+ const int
+ x = (int)(ox + _mp_arg(3)), y = (int)(oy + _mp_arg(4)),
+ z = (int)(oz + _mp_arg(5)), c = (int)(oc + _mp_arg(6));
+ const double val = _mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() &&
+ z>=0 && z<img.depth() && c>=0 && c<img.spectrum())
+ img(x,y,z,c) = (T)val;
+ return val;
+ }
+
+ static double mp_list_set_Ioff_s(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const longT
+ off = (longT)_mp_arg(3),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T val = (T)_mp_arg(1);
+ if (off>=0 && off<whd) {
+ T *ptrd = &img[off];
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_list_set_Ioff_v(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const longT
+ off = (longT)_mp_arg(3),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (off>=0 && off<whd) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[4];
+ T *ptrd = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_list_set_Ixyz_s(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const int
+ x = (int)_mp_arg(3),
+ y = (int)_mp_arg(4),
+ z = (int)_mp_arg(5);
+ const T val = (T)_mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_list_set_Ixyz_v(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const int
+ x = (int)_mp_arg(3),
+ y = (int)_mp_arg(4),
+ z = (int)_mp_arg(5);
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[6];
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_list_set_Joff_s(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T val = (T)_mp_arg(1);
+ if (off>=0 && off<whd) {
+ T *ptrd = &img[off];
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_list_set_Joff_v(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (off>=0 && off<whd) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[4];
+ T *ptrd = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_list_set_Jxyz_s(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z];
+ const int
+ x = (int)(ox + _mp_arg(3)),
+ y = (int)(oy + _mp_arg(4)),
+ z = (int)(oz + _mp_arg(5));
+ const T val = (T)_mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_list_set_Jxyz_v(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ CImg<T> &img = mp.listout[ind];
+ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z];
+ const int
+ x = (int)(ox + _mp_arg(3)),
+ y = (int)(oy + _mp_arg(4)),
+ z = (int)(oz + _mp_arg(5));
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[6];
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_list_spectrum(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._spectrum;
+ }
+
+ static double mp_list_stats(_cimg_math_parser& mp) {
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ k = (unsigned int)_mp_arg(3);
+ if (!mp.list_stats) mp.list_stats.assign(mp.listin._width);
+ if (!mp.list_stats[ind]) mp.list_stats[ind].assign(1,14,1,1,0).fill(mp.listin[ind].get_stats(),false);
+ return mp.list_stats(ind,k);
+ }
+
+ static double mp_list_wh(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._width*mp.listin[ind]._height;
+ }
+
+ static double mp_list_whd(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth;
+ }
+
+ static double mp_list_whds(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._width*mp.listin[ind]._height*mp.listin[ind]._depth*mp.listin[ind]._spectrum;
+ }
+
+ static double mp_list_width(_cimg_math_parser& mp) {
+ const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width());
+ return (double)mp.listin[ind]._width;
+ }
+
+ static double mp_list_Ioff(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ boundary_conditions = (unsigned int)_mp_arg(4),
+ vsiz = (unsigned int)mp.opcode[5];
+ const CImg<T> &img = mp.listin[ind];
+ const longT
+ off = (longT)_mp_arg(3),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T *ptrs;
+ if (off>=0 && off<whd) {
+ ptrs = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2);
+ ptrs = &img[moff<whd?moff:whd2 - moff - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ case 2 : // Periodic
+ ptrs = &img[cimg::mod(off,whd)];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ case 1 : // Neumann
+ ptrs = off<0?&img[0]:&img[whd - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ default : // Dirichlet
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_list_Ixyz(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ interpolation = (unsigned int)_mp_arg(6),
+ boundary_conditions = (unsigned int)_mp_arg(7),
+ vsiz = (unsigned int)mp.opcode[8];
+ const CImg<T> &img = mp.listin[ind];
+ const double x = _mp_arg(3), y = _mp_arg(4), z = _mp_arg(5);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ const T *ptrs;
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 2 : { // Periodic
+ const int
+ cx = cimg::mod((int)x,img.width()),
+ cy = cimg::mod((int)y,img.height()),
+ cz = cimg::mod((int)z,img.depth());
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 1 : { // Neumann
+ ptrs = &img._atXYZ((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ default : // Dirichlet
+ if (img.containsXYZC(x,y,z)) {
+ ptrs = &img((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } else std::memset(ptrd,0,vsiz*sizeof(double));
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 2 : { // Periodic
+ const float
+ cx = cimg::mod((float)x,(float)img.width()),
+ cy = cimg::mod((float)y,(float)img.height()),
+ cz = cimg::mod((float)z,(float)img.depth());
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 1 : // Neumann
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ((float)x,(float)y,(float)z,c);
+ break;
+ case 0 : // Dirichlet
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img.linear_atXYZ((float)x,(float)y,(float)z,c,(T)0);
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_list_Joff(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ boundary_conditions = (unsigned int)_mp_arg(4),
+ vsiz = (unsigned int)mp.opcode[5];
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y], oz = (int)mp.mem[_cimg_mp_slot_z];
+ const CImg<T> &img = mp.listin[ind];
+ const longT
+ off = img.offset(ox,oy,oz) + (longT)_mp_arg(3),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T *ptrs;
+ if (off>=0 && off<whd) {
+ ptrs = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2);
+ ptrs = &img[moff<whd?moff:whd2 - moff - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ case 2 : // Periodic
+ ptrs = &img[cimg::mod(off,whd)];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ case 1 : // Neumann
+ ptrs = off<0?&img[0]:&img[whd - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ default : // Dirichlet
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_list_Jxyz(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()),
+ interpolation = (unsigned int)_mp_arg(6),
+ boundary_conditions = (unsigned int)_mp_arg(7),
+ vsiz = (unsigned int)mp.opcode[8];
+ const CImg<T> &img = mp.listin[ind];
+ const double
+ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z],
+ x = ox + _mp_arg(3), y = oy + _mp_arg(4), z = oz + _mp_arg(5);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ const T *ptrs;
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 2 : { // Periodic
+ const int
+ cx = cimg::mod((int)x,img.width()),
+ cy = cimg::mod((int)y,img.height()),
+ cz = cimg::mod((int)z,img.depth());
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 1 : { // Neumann
+ ptrs = &img._atXYZ((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ default : // Dirichlet
+ if (img.containsXYZC(x,y,z)) {
+ ptrs = &img((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } else std::memset(ptrd,0,vsiz*sizeof(double));
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 2 : { // Periodic
+ const float
+ cx = cimg::mod((float)x,(float)img.width()),
+ cy = cimg::mod((float)y,(float)img.height()),
+ cz = cimg::mod((float)z,(float)img.depth());
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 1 : // Neumann
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ((float)x,(float)y,(float)z,c);
+ break;
+ default : // Dirichlet
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img.linear_atXYZ((float)x,(float)y,(float)z,c,(T)0);
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_log(_cimg_math_parser& mp) {
+ return std::log(_mp_arg(2));
+ }
+
+ static double mp_log10(_cimg_math_parser& mp) {
+ return std::log10(_mp_arg(2));
+ }
+
+ static double mp_log2(_cimg_math_parser& mp) {
+ return cimg::log2(_mp_arg(2));
+ }
+
+ static double mp_logical_and(_cimg_math_parser& mp) {
+ const bool val_left = (bool)_mp_arg(2);
+ const CImg<ulongT> *const p_end = ++mp.p_code + mp.opcode[4];
+ if (!val_left) { mp.p_code = p_end - 1; return 0; }
+ const ulongT mem_right = mp.opcode[3];
+ for ( ; mp.p_code<p_end; ++mp.p_code) {
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ --mp.p_code;
+ return (double)(bool)mp.mem[mem_right];
+ }
+
+ static double mp_logical_not(_cimg_math_parser& mp) {
+ return (double)!_mp_arg(2);
+ }
+
+ static double mp_logical_or(_cimg_math_parser& mp) {
+ const bool val_left = (bool)_mp_arg(2);
+ const CImg<ulongT> *const p_end = ++mp.p_code + mp.opcode[4];
+ if (val_left) { mp.p_code = p_end - 1; return 1; }
+ const ulongT mem_right = mp.opcode[3];
+ for ( ; mp.p_code<p_end; ++mp.p_code) {
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ --mp.p_code;
+ return (double)(bool)mp.mem[mem_right];
+ }
+
+ static double mp_lowercase(_cimg_math_parser& mp) {
+ return cimg::lowercase(_mp_arg(2));
+ }
+
+ static double mp_lt(_cimg_math_parser& mp) {
+ return (double)(_mp_arg(2)<_mp_arg(3));
+ }
+
+ static double mp_lte(_cimg_math_parser& mp) {
+ return (double)(_mp_arg(2)<=_mp_arg(3));
+ }
+
+ static double mp_matrix_eig(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptr1 = &_mp_arg(2) + 1;
+ const unsigned int k = (unsigned int)mp.opcode[3];
+ CImg<double> val, vec;
+ CImg<double>(ptr1,k,k,1,1,true).symmetric_eigen(val,vec);
+ CImg<double>(ptrd,1,k,1,1,true) = val;
+ CImg<double>(ptrd + k,k,k,1,1,true) = vec.get_transpose();
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_matrix_inv(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptr1 = &_mp_arg(2) + 1;
+ const unsigned int k = (unsigned int)mp.opcode[3];
+ CImg<double>(ptrd,k,k,1,1,true) = CImg<double>(ptr1,k,k,1,1,true).get_invert();
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_matrix_mul(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double
+ *ptr1 = &_mp_arg(2) + 1,
+ *ptr2 = &_mp_arg(3) + 1;
+ const unsigned int
+ k = (unsigned int)mp.opcode[4],
+ l = (unsigned int)mp.opcode[5],
+ m = (unsigned int)mp.opcode[6];
+ CImg<double>(ptrd,m,k,1,1,true) = CImg<double>(ptr1,l,k,1,1,true)*CImg<double>(ptr2,m,l,1,1,true);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_matrix_pseudoinv(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptr1 = &_mp_arg(2) + 1;
+ const unsigned int
+ k = (unsigned int)mp.opcode[3],
+ l = (unsigned int)mp.opcode[4];
+ CImg<double>(ptrd,l,k,1,1,true) = CImg<double>(ptr1,k,l,1,1,true).get_pseudoinvert();
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_matrix_svd(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptr1 = &_mp_arg(2) + 1;
+ const unsigned int
+ k = (unsigned int)mp.opcode[3],
+ l = (unsigned int)mp.opcode[4];
+ CImg<double> U, S, V;
+ CImg<double>(ptr1,k,l,1,1,true).SVD(U,S,V);
+ CImg<double>(ptrd,k,l,1,1,true) = U;
+ CImg<double>(ptrd + k*l,1,k,1,1,true) = S;
+ CImg<double>(ptrd + k*l + k,k,k,1,1,true) = V;
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_max(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ double val = _mp_arg(3);
+ for (unsigned int i = 4; i<i_end; ++i) val = std::max(val,_mp_arg(i));
+ return val;
+ }
+
+ static double* _mp_memcopy_double(_cimg_math_parser& mp, const unsigned int ind, const ulongT *const p_ref,
+ const longT siz, const long inc) {
+ const longT
+ off = *p_ref?p_ref[1] + (longT)mp.mem[(longT)p_ref[2]] + 1:ind,
+ eoff = off + (siz - 1)*inc;
+ if (off<0 || eoff>=mp.mem.width())
+ throw CImgArgumentException("[_cimg_math_parser] CImg<%s>: Function 'copy()': "
+ "Out-of-bounds variable pointer "
+ "(length: %ld, increment: %ld, offset start: %ld, "
+ "offset end: %ld, offset max: %u).",
+ mp.imgin.pixel_type(),siz,inc,off,eoff,mp.mem._width - 1);
+ return &mp.mem[off];
+ }
+
+ static float* _mp_memcopy_float(_cimg_math_parser& mp, const ulongT *const p_ref,
+ const longT siz, const long inc) {
+ const unsigned ind = (unsigned int)p_ref[1];
+ const CImg<T> &img = ind==~0U?mp.imgin:mp.listin[cimg::mod((int)mp.mem[ind],mp.listin.width())];
+ const bool is_relative = (bool)p_ref[2];
+ int ox, oy, oz, oc;
+ longT off = 0;
+ if (is_relative) {
+ ox = (int)mp.mem[_cimg_mp_slot_x];
+ oy = (int)mp.mem[_cimg_mp_slot_y];
+ oz = (int)mp.mem[_cimg_mp_slot_z];
+ oc = (int)mp.mem[_cimg_mp_slot_c];
+ off = img.offset(ox,oy,oz,oc);
+ }
+ if ((*p_ref)%2) {
+ const int
+ x = (int)mp.mem[p_ref[3]],
+ y = (int)mp.mem[p_ref[4]],
+ z = (int)mp.mem[p_ref[5]],
+ c = *p_ref==5?0:(int)mp.mem[p_ref[6]];
+ off+=img.offset(x,y,z,c);
+ } else off+=(longT)mp.mem[p_ref[3]];
+ const longT eoff = off + (siz - 1)*inc;
+ if (off<0 || eoff>=(longT)img.size())
+ throw CImgArgumentException("[_cimg_math_parser] CImg<%s>: Function 'copy()': "
+ "Out-of-bounds image pointer "
+ "(length: %ld, increment: %ld, offset start: %ld, "
+ "offset end: %ld, offset max: %lu).",
+ mp.imgin.pixel_type(),siz,inc,off,eoff,img.size() - 1);
+ return (float*)&img[off];
+ }
+
+ static double mp_memcopy(_cimg_math_parser& mp) {
+ longT siz = (longT)_mp_arg(4);
+ const longT inc_d = (longT)_mp_arg(5), inc_s = (longT)_mp_arg(6);
+ const float
+ _opacity = (float)_mp_arg(7),
+ opacity = (float)cimg::abs(_opacity),
+ omopacity = 1 - std::max(_opacity,0.0f);
+ if (siz>0) {
+ const bool
+ is_doubled = mp.opcode[8]<=1,
+ is_doubles = mp.opcode[15]<=1;
+ if (is_doubled && is_doubles) { // (double*) <- (double*)
+ double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d);
+ const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s);
+ if (inc_d==1 && inc_s==1 && _opacity>=1) {
+ if (ptrs + siz - 1<ptrd || ptrs>ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(double));
+ else std::memmove(ptrd,ptrs,siz*sizeof(double));
+ } else {
+ if (ptrs + (siz - 1)*inc_s<ptrd || ptrs>ptrd + (siz - 1)*inc_d) {
+ if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; }
+ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; }
+ } else { // Overlapping buffers
+ CImg<double> buf((unsigned int)siz);
+ cimg_for(buf,ptr,double) { *ptr = *ptrs; ptrs+=inc_s; }
+ ptrs = buf;
+ if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; }
+ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; }
+ }
+ }
+ } else if (is_doubled && !is_doubles) { // (double*) <- (float*)
+ double *ptrd = _mp_memcopy_double(mp,(unsigned int)mp.opcode[2],&mp.opcode[8],siz,inc_d);
+ const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s);
+ if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; }
+ else while (siz-->0) { *ptrd = omopacity**ptrd + _opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; }
+ } else if (!is_doubled && is_doubles) { // (float*) <- (double*)
+ float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d);
+ const double *ptrs = _mp_memcopy_double(mp,(unsigned int)mp.opcode[3],&mp.opcode[15],siz,inc_s);
+ if (_opacity>=1) while (siz-->0) { *ptrd = (float)*ptrs; ptrd+=inc_d; ptrs+=inc_s; }
+ else while (siz-->0) { *ptrd = (float)(omopacity**ptrd + opacity**ptrs); ptrd+=inc_d; ptrs+=inc_s; }
+ } else { // (float*) <- (float*)
+ float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d);
+ const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s);
+ if (inc_d==1 && inc_s==1 && _opacity>=1) {
+ if (ptrs + siz - 1<ptrd || ptrs>ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(float));
+ else std::memmove(ptrd,ptrs,siz*sizeof(float));
+ } else {
+ if (ptrs + (siz - 1)*inc_s<ptrd || ptrs>ptrd + (siz - 1)*inc_d) {
+ if (_opacity>=1) while (siz-->0) { *ptrd = *ptrs; ptrd+=inc_d; ptrs+=inc_s; }
+ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**ptrs; ptrd+=inc_d; ptrs+=inc_s; }
+ } else { // Overlapping buffers
+ CImg<floatT> buf((unsigned int)siz);
+ cimg_for(buf,ptr,float) { *ptr = *ptrs; ptrs+=inc_s; }
+ ptrs = buf;
+ if (_opacity>=1) while (siz-->0) { *ptrd = *(ptrs++); ptrd+=inc_d; }
+ else while (siz-->0) { *ptrd = omopacity**ptrd + opacity**(ptrs++); ptrd+=inc_d; }
+ }
+ }
+ }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_min(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ double val = _mp_arg(3);
+ for (unsigned int i = 4; i<i_end; ++i) val = std::min(val,_mp_arg(i));
+ return val;
+ }
+
+ static double mp_minus(_cimg_math_parser& mp) {
+ return -_mp_arg(2);
+ }
+
+ static double mp_mean(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ double val = _mp_arg(3);
+ for (unsigned int i = 4; i<i_end; ++i) val+=_mp_arg(i);
+ return val/(i_end - 3);
+ }
+
+ static double mp_median(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ switch (i_end - 3) {
+ case 1 : return _mp_arg(3);
+ case 2 : return cimg::median(_mp_arg(3),_mp_arg(4));
+ case 3 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5));
+ case 5 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7));
+ case 7 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7),_mp_arg(8),_mp_arg(9));
+ case 9 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7),_mp_arg(8),_mp_arg(9),
+ _mp_arg(10),_mp_arg(11));
+ case 13 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7),_mp_arg(8),_mp_arg(9),
+ _mp_arg(10),_mp_arg(11),_mp_arg(12),_mp_arg(13),_mp_arg(14),_mp_arg(15));
+ }
+ CImg<doubleT> vals(i_end - 3);
+ double *p = vals.data();
+ for (unsigned int i = 3; i<i_end; ++i) *(p++) = _mp_arg(i);
+ return vals.median();
+ }
+
+ static double mp_modulo(_cimg_math_parser& mp) {
+ return cimg::mod(_mp_arg(2),_mp_arg(3));
+ }
+
+ static double mp_mul(_cimg_math_parser& mp) {
+ return _mp_arg(2)*_mp_arg(3);
+ }
+
+ static double mp_mul2(_cimg_math_parser& mp) {
+ return _mp_arg(2)*_mp_arg(3)*_mp_arg(4);
+ }
+
+ static double mp_neq(_cimg_math_parser& mp) {
+ return (double)(_mp_arg(2)!=_mp_arg(3));
+ }
+
+ static double mp_norm0(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ switch (i_end - 3) {
+ case 1 : return _mp_arg(3)!=0;
+ case 2 : return (_mp_arg(3)!=0) + (_mp_arg(4)!=0);
+ }
+ double res = 0;
+ for (unsigned int i = 3; i<i_end; ++i)
+ res+=_mp_arg(i)==0?0:1;
+ return res;
+ }
+
+ static double mp_norm1(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ switch (i_end - 3) {
+ case 1 : return cimg::abs(_mp_arg(3));
+ case 2 : return cimg::abs(_mp_arg(3)) + cimg::abs(_mp_arg(4));
+ }
+ double res = 0;
+ for (unsigned int i = 3; i<i_end; ++i)
+ res+=cimg::abs(_mp_arg(i));
+ return res;
+ }
+
+ static double mp_norm2(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ switch (i_end - 3) {
+ case 1 : return cimg::abs(_mp_arg(3));
+ case 2 : return cimg::_hypot(_mp_arg(3),_mp_arg(4));
+ }
+ double res = 0;
+ for (unsigned int i = 3; i<i_end; ++i)
+ res+=cimg::sqr(_mp_arg(i));
+ return std::sqrt(res);
+ }
+
+ static double mp_norminf(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ switch (i_end - 3) {
+ case 1 : return cimg::abs(_mp_arg(3));
+ case 2 : return std::max(cimg::abs(_mp_arg(3)),cimg::abs(_mp_arg(4)));
+ }
+ double res = 0;
+ for (unsigned int i = 3; i<i_end; ++i) {
+ const double val = cimg::abs(_mp_arg(i));
+ if (val>res) res = val;
+ }
+ return res;
+ }
+
+ static double mp_normp(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ if (i_end==4) return cimg::abs(_mp_arg(3));
+ const double p = (double)mp.opcode[3];
+ double res = 0;
+ for (unsigned int i = 4; i<i_end; ++i)
+ res+=std::pow(cimg::abs(_mp_arg(i)),p);
+ res = std::pow(res,1/p);
+ return res>0?res:0.0;
+ }
+
+ static double mp_permutations(_cimg_math_parser& mp) {
+ return cimg::permutations(_mp_arg(2),_mp_arg(3),(bool)_mp_arg(4));
+ }
+
+ static double mp_pow(_cimg_math_parser& mp) {
+ const double v = _mp_arg(2), p = _mp_arg(3);
+ return std::pow(v,p);
+ }
+
+ static double mp_pow0_25(_cimg_math_parser& mp) {
+ const double val = _mp_arg(2);
+ return std::sqrt(std::sqrt(val));
+ }
+
+ static double mp_pow3(_cimg_math_parser& mp) {
+ const double val = _mp_arg(2);
+ return val*val*val;
+ }
+
+ static double mp_pow4(_cimg_math_parser& mp) {
+ const double val = _mp_arg(2);
+ return val*val*val*val;
+ }
+
+ static double mp_print(_cimg_math_parser& mp) {
+ const double val = _mp_arg(1);
+ cimg_pragma_openmp(critical)
+ {
+ CImg<charT> expr(mp.opcode[2] - 3);
+ const ulongT *ptrs = mp.opcode._data + 3;
+ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++);
+ cimg::strellipsize(expr);
+ cimg::mutex(6);
+ std::fprintf(cimg::output(),"\n[_cimg_math_parser] %s = %g",expr._data,val);
+ std::fflush(cimg::output());
+ cimg::mutex(6,0);
+ }
+ return val;
+ }
+
+ static double mp_prod(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ double val = _mp_arg(3);
+ for (unsigned int i = 4; i<i_end; ++i) val*=_mp_arg(i);
+ return val;
+ }
+
+ static double mp_copy(_cimg_math_parser& mp) {
+ return _mp_arg(2);
+ }
+
+ static double mp_rol(_cimg_math_parser& mp) {
+ return cimg::rol(_mp_arg(2),(unsigned int)_mp_arg(3));
+ }
+
+ static double mp_ror(_cimg_math_parser& mp) {
+ return cimg::ror(_mp_arg(2),(unsigned int)_mp_arg(3));
+ }
+
+ static double mp_rot2d(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const float
+ theta = (float)_mp_arg(2)*cimg::PI/180,
+ ca = std::cos(theta),
+ sa = std::sin(theta);
+ *(ptrd++) = ca;
+ *(ptrd++) = -sa;
+ *(ptrd++) = sa;
+ *ptrd = ca;
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_rot3d(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const float x = (float)_mp_arg(2), y = (float)_mp_arg(3), z = (float)_mp_arg(4), theta = (float)_mp_arg(5);
+ CImg<double>(ptrd,3,3,1,1,true) = CImg<double>::rotation_matrix(x,y,z,theta);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_round(_cimg_math_parser& mp) {
+ return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4));
+ }
+
+ static double mp_self_add(_cimg_math_parser& mp) {
+ return _mp_arg(1)+=_mp_arg(2);
+ }
+
+ static double mp_self_bitwise_and(_cimg_math_parser& mp) {
+ double &val = _mp_arg(1);
+ return val = (double)((longT)val & (longT)_mp_arg(2));
+ }
+
+ static double mp_self_bitwise_left_shift(_cimg_math_parser& mp) {
+ double &val = _mp_arg(1);
+ return val = (double)((longT)val<<(unsigned int)_mp_arg(2));
+ }
+
+ static double mp_self_bitwise_or(_cimg_math_parser& mp) {
+ double &val = _mp_arg(1);
+ return val = (double)((longT)val | (longT)_mp_arg(2));
+ }
+
+ static double mp_self_bitwise_right_shift(_cimg_math_parser& mp) {
+ double &val = _mp_arg(1);
+ return val = (double)((longT)val>>(unsigned int)_mp_arg(2));
+ }
+
+ static double mp_self_decrement(_cimg_math_parser& mp) {
+ return --_mp_arg(1);
+ }
+
+ static double mp_self_increment(_cimg_math_parser& mp) {
+ return ++_mp_arg(1);
+ }
+
+ static double mp_self_map_vector_s(_cimg_math_parser& mp) { // Vector += scalar
+ unsigned int
+ ptrd = (unsigned int)mp.opcode[1] + 1,
+ siz = (unsigned int)mp.opcode[2];
+ mp_func op = (mp_func)mp.opcode[3];
+ CImg<ulongT> l_opcode(1,3);
+ l_opcode[2] = mp.opcode[4]; // Scalar argument.
+ l_opcode.swap(mp.opcode);
+ ulongT &target = mp.opcode[1];
+ while (siz-->0) { target = ptrd++; (*op)(mp); }
+ l_opcode.swap(mp.opcode);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_self_map_vector_v(_cimg_math_parser& mp) { // Vector += vector
+ unsigned int
+ ptrd = (unsigned int)mp.opcode[1] + 1,
+ siz = (unsigned int)mp.opcode[2],
+ ptrs = (unsigned int)mp.opcode[4] + 1;
+ mp_func op = (mp_func)mp.opcode[3];
+ CImg<ulongT> l_opcode(1,4);
+ l_opcode.swap(mp.opcode);
+ ulongT &target = mp.opcode[1], &argument = mp.opcode[2];
+ while (siz-->0) { target = ptrd++; argument = ptrs++; (*op)(mp); }
+ l_opcode.swap(mp.opcode);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_self_mul(_cimg_math_parser& mp) {
+ return _mp_arg(1)*=_mp_arg(2);
+ }
+
+ static double mp_self_div(_cimg_math_parser& mp) {
+ return _mp_arg(1)/=_mp_arg(2);
+ }
+
+ static double mp_self_modulo(_cimg_math_parser& mp) {
+ double &val = _mp_arg(1);
+ return val = cimg::mod(val,_mp_arg(2));
+ }
+
+ static double mp_self_pow(_cimg_math_parser& mp) {
+ double &val = _mp_arg(1);
+ return val = std::pow(val,_mp_arg(2));
+ }
+
+ static double mp_self_sub(_cimg_math_parser& mp) {
+ return _mp_arg(1)-=_mp_arg(2);
+ }
+
+ static double mp_set_ioff(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const longT
+ off = (longT)_mp_arg(2),
+ whds = (longT)img.size();
+ const double val = _mp_arg(1);
+ if (off>=0 && off<whds) img[off] = (T)val;
+ return val;
+ }
+
+ static double mp_set_ixyzc(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const int
+ x = (int)_mp_arg(2), y = (int)_mp_arg(3),
+ z = (int)_mp_arg(4), c = (int)_mp_arg(5);
+ const double val = _mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() &&
+ z>=0 && z<img.depth() && c>=0 && c<img.spectrum())
+ img(x,y,z,c) = (T)val;
+ return val;
+ }
+
+ static double mp_set_joff(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2),
+ whds = (longT)img.size();
+ const double val = _mp_arg(1);
+ if (off>=0 && off<whds) img[off] = (T)val;
+ return val;
+ }
+
+ static double mp_set_jxyzc(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const double
+ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y],
+ oz = mp.mem[_cimg_mp_slot_z], oc = mp.mem[_cimg_mp_slot_c];
+ const int
+ x = (int)(ox + _mp_arg(2)), y = (int)(oy + _mp_arg(3)),
+ z = (int)(oz + _mp_arg(4)), c = (int)(oc + _mp_arg(5));
+ const double val = _mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() &&
+ z>=0 && z<img.depth() && c>=0 && c<img.spectrum())
+ img(x,y,z,c) = (T)val;
+ return val;
+ }
+
+ static double mp_set_Ioff_s(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const longT
+ off = (longT)_mp_arg(2),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T val = (T)_mp_arg(1);
+ if (off>=0 && off<whd) {
+ T *ptrd = &img[off];
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_set_Ioff_v(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const longT
+ off = (longT)_mp_arg(2),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (off>=0 && off<whd) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[3];
+ T *ptrd = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_set_Ixyz_s(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const int
+ x = (int)_mp_arg(2),
+ y = (int)_mp_arg(3),
+ z = (int)_mp_arg(4);
+ const T val = (T)_mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_set_Ixyz_v(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const int
+ x = (int)_mp_arg(2),
+ y = (int)_mp_arg(3),
+ z = (int)_mp_arg(4);
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[5];
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_set_Joff_s(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T val = (T)_mp_arg(1);
+ if (off>=0 && off<whd) {
+ T *ptrd = &img[off];
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_set_Joff_v(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
+ const longT
+ off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (off>=0 && off<whd) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[3];
+ T *ptrd = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_set_Jxyz_s(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z];
+ const int
+ x = (int)(ox + _mp_arg(2)),
+ y = (int)(oy + _mp_arg(3)),
+ z = (int)(oz + _mp_arg(4));
+ const T val = (T)_mp_arg(1);
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_forC(img,c) { *ptrd = val; ptrd+=whd; }
+ }
+ return _mp_arg(1);
+ }
+
+ static double mp_set_Jxyz_v(_cimg_math_parser& mp) {
+ CImg<T> &img = mp.imgout;
+ const double ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z];
+ const int
+ x = (int)(ox + _mp_arg(2)),
+ y = (int)(oy + _mp_arg(3)),
+ z = (int)(oz + _mp_arg(4));
+ const double *ptrs = &_mp_arg(1) + 1;
+ if (x>=0 && x<img.width() && y>=0 && y<img.height() && z>=0 && z<img.depth()) {
+ const unsigned int vsiz = (unsigned int)mp.opcode[5];
+ T *ptrd = &img(x,y,z);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_sign(_cimg_math_parser& mp) {
+ return cimg::sign(_mp_arg(2));
+ }
+
+ static double mp_sin(_cimg_math_parser& mp) {
+ return std::sin(_mp_arg(2));
+ }
+
+ static double mp_sinc(_cimg_math_parser& mp) {
+ return cimg::sinc(_mp_arg(2));
+ }
+
+ static double mp_single(_cimg_math_parser& mp) {
+ const double res = _mp_arg(1);
+ cimg_pragma_openmp(critical)
+ {
+ for (const CImg<ulongT> *const p_end = ++mp.p_code + mp.opcode[2];
+ mp.p_code<p_end; ++mp.p_code) { // Evaluate body
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ }
+ --mp.p_code;
+ return res;
+ }
+
+ static double mp_sinh(_cimg_math_parser& mp) {
+ return std::sinh(_mp_arg(2));
+ }
+
+ static double mp_solve(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double
+ *ptr1 = &_mp_arg(2) + 1,
+ *ptr2 = &_mp_arg(3) + 1;
+ const unsigned int
+ k = (unsigned int)mp.opcode[4],
+ l = (unsigned int)mp.opcode[5],
+ m = (unsigned int)mp.opcode[6];
+ CImg<double>(ptrd,m,k,1,1,true) = CImg<double>(ptr2,m,l,1,1,true).get_solve(CImg<double>(ptr1,k,l,1,1,true));
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_sort(_cimg_math_parser& mp) {
+ double *const ptrd = &_mp_arg(1) + 1;
+ const double *const ptrs = &_mp_arg(2) + 1;
+ const unsigned int
+ siz = (unsigned int)mp.opcode[3],
+ chunk_siz = (unsigned int)mp.opcode[5];
+ const bool is_increasing = (bool)_mp_arg(4);
+ CImg<doubleT>(ptrd,chunk_siz,siz/chunk_siz,1,1,true) = CImg<doubleT>(ptrs,chunk_siz,siz/chunk_siz,1,1,true).
+ get_sort(is_increasing,chunk_siz>1?'y':0);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_sqr(_cimg_math_parser& mp) {
+ return cimg::sqr(_mp_arg(2));
+ }
+
+ static double mp_sqrt(_cimg_math_parser& mp) {
+ return std::sqrt(_mp_arg(2));
+ }
+
+ static double mp_std(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ CImg<doubleT> vals(i_end - 3);
+ double *p = vals.data();
+ for (unsigned int i = 3; i<i_end; ++i) *(p++) = _mp_arg(i);
+ return std::sqrt(vals.variance());
+ }
+
+ static double mp_string_init(_cimg_math_parser& mp) {
+ const char *ptrs = (char*)&mp.opcode[3];
+ unsigned int
+ ptrd = (unsigned int)mp.opcode[1] + 1,
+ siz = (unsigned int)mp.opcode[2];
+ while (siz-->0) mp.mem[ptrd++] = (double)*(ptrs++);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_stod(_cimg_math_parser& mp) {
+ const double *ptrs = &_mp_arg(2);
+ const unsigned int siz = (unsigned int)_mp_arg(3);
+ const bool is_strict = (bool)_mp_arg(4);
+ if (!siz) return *ptrs>='0' && *ptrs<='9'?*ptrs - '0':cimg::type<double>::nan();
+ CImg<charT> ss(siz + 1);
+ double val = cimg::type<double>::nan();
+ char sep;
+ for (unsigned i = 0; i<siz; ++i) ss[i] = (char)*(++ptrs);
+ ss[siz] = 0;
+
+ int err = std::sscanf(ss,"%lf%c",&val,&sep);
+#if cimg_OS==2
+ // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able
+ // to read those particular values.
+ if (!err && (*ss=='+' || *ss=='-' || *ss=='i' || *ss=='I' || *ss=='n' || *ss=='N')) {
+ bool is_positive = true;
+ const char *s = ss;
+ if (*s=='+') ++s; else if (*s=='-') { ++s; is_positive = false; }
+ if (!cimg::strcasecmp(s,"inf")) { val = cimg::type<double>::inf(); err = 1; }
+ else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type<double>::nan(); err = 1; }
+ if (err==1 && !is_positive) val = -val;
+ }
+#endif
+ if (is_strict && err!=1) return cimg::type<double>::nan();
+ return val;
+ }
+
+ static double mp_sub(_cimg_math_parser& mp) {
+ return _mp_arg(2) - _mp_arg(3);
+ }
+
+ static double mp_sum(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ double val = _mp_arg(3);
+ for (unsigned int i = 4; i<i_end; ++i) val+=_mp_arg(i);
+ return val;
+ }
+
+ static double mp_tan(_cimg_math_parser& mp) {
+ return std::tan(_mp_arg(2));
+ }
+
+ static double mp_tanh(_cimg_math_parser& mp) {
+ return std::tanh(_mp_arg(2));
+ }
+
+ static double mp_trace(_cimg_math_parser& mp) {
+ const double *ptrs = &_mp_arg(2) + 1;
+ const unsigned int k = (unsigned int)mp.opcode[3];
+ return CImg<double>(ptrs,k,k,1,1,true).trace();
+ }
+
+ static double mp_transp(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const double *ptrs = &_mp_arg(2) + 1;
+ const unsigned int
+ k = (unsigned int)mp.opcode[3],
+ l = (unsigned int)mp.opcode[4];
+ CImg<double>(ptrd,l,k,1,1,true) = CImg<double>(ptrs,k,l,1,1,true).get_transpose();
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_u(_cimg_math_parser& mp) {
+ return cimg::rand(_mp_arg(2),_mp_arg(3));
+ }
+
+ static double mp_uppercase(_cimg_math_parser& mp) {
+ return cimg::uppercase(_mp_arg(2));
+ }
+
+ static double mp_variance(_cimg_math_parser& mp) {
+ const unsigned int i_end = (unsigned int)mp.opcode[2];
+ CImg<doubleT> vals(i_end - 3);
+ double *p = vals.data();
+ for (unsigned int i = 3; i<i_end; ++i) *(p++) = _mp_arg(i);
+ return vals.variance();
+ }
+
+ static double mp_vector_copy(_cimg_math_parser& mp) {
+ std::memcpy(&_mp_arg(1) + 1,&_mp_arg(2) + 1,sizeof(double)*mp.opcode[3]);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_crop(_cimg_math_parser& mp) {
+ double *const ptrd = &_mp_arg(1) + 1;
+ const double *const ptrs = &_mp_arg(2) + 1;
+ const longT
+ length = (longT)mp.opcode[3],
+ start = (longT)_mp_arg(4),
+ sublength = (longT)mp.opcode[5];
+ if (start<0 || start + sublength>length)
+ throw CImgArgumentException("[_cimg_math_parser] CImg<%s>: Value accessor '[]': "
+ "Out-of-bounds sub-vector request "
+ "(length: %ld, start: %ld, sub-length: %ld).",
+ mp.imgin.pixel_type(),length,start,sublength);
+ std::memcpy(ptrd,ptrs + start,sublength*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_init(_cimg_math_parser& mp) {
+ unsigned int
+ ptrs = 4U,
+ ptrd = (unsigned int)mp.opcode[1] + 1,
+ siz = (unsigned int)mp.opcode[3];
+ switch (mp.opcode[2] - 4) {
+ case 0 : std::memset(mp.mem._data + ptrd,0,siz*sizeof(double)); break; // 0 values given
+ case 1 : { const double val = _mp_arg(ptrs); while (siz-->0) mp.mem[ptrd++] = val; } break;
+ default : while (siz-->0) { mp.mem[ptrd++] = _mp_arg(ptrs++); if (ptrs>=mp.opcode[2]) ptrs = 4U; }
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_eq(_cimg_math_parser& mp) {
+ const double
+ *ptr1 = &_mp_arg(2) + 1,
+ *ptr2 = &_mp_arg(4) + 1;
+ unsigned int p1 = (unsigned int)mp.opcode[3], p2 = (unsigned int)mp.opcode[5], n;
+ const int N = (int)_mp_arg(6);
+ const bool case_sensitive = (bool)_mp_arg(7);
+ bool still_equal = true;
+ double value;
+ if (!N) return true;
+
+ // Compare all values.
+ if (N<0) {
+ if (p1>0 && p2>0) { // Vector == vector
+ if (p1!=p2) return false;
+ if (case_sensitive)
+ while (still_equal && p1--) still_equal = *(ptr1++)==*(ptr2++);
+ else
+ while (still_equal && p1--)
+ still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++));
+ return still_equal;
+ } else if (p1>0 && !p2) { // Vector == scalar
+ value = _mp_arg(4);
+ if (!case_sensitive) value = cimg::lowercase(value);
+ while (still_equal && p1--) still_equal = *(ptr1++)==value;
+ return still_equal;
+ } else if (!p1 && p2>0) { // Scalar == vector
+ value = _mp_arg(2);
+ if (!case_sensitive) value = cimg::lowercase(value);
+ while (still_equal && p2--) still_equal = *(ptr2++)==value;
+ return still_equal;
+ } else { // Scalar == scalar
+ if (case_sensitive) return _mp_arg(2)==_mp_arg(4);
+ else return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4));
+ }
+ }
+
+ // Compare only first N values.
+ if (p1>0 && p2>0) { // Vector == vector
+ n = cimg::min((unsigned int)N,p1,p2);
+ if (case_sensitive)
+ while (still_equal && n--) still_equal = *(ptr1++)==(*ptr2++);
+ else
+ while (still_equal && n--) still_equal = cimg::lowercase(*(ptr1++))==cimg::lowercase(*(ptr2++));
+ return still_equal;
+ } else if (p1>0 && !p2) { // Vector == scalar
+ n = std::min((unsigned int)N,p1);
+ value = _mp_arg(4);
+ if (!case_sensitive) value = cimg::lowercase(value);
+ while (still_equal && n--) still_equal = *(ptr1++)==value;
+ return still_equal;
+ } else if (!p1 && p2>0) { // Scalar == vector
+ n = std::min((unsigned int)N,p2);
+ value = _mp_arg(2);
+ if (!case_sensitive) value = cimg::lowercase(value);
+ while (still_equal && n--) still_equal = *(ptr2++)==value;
+ return still_equal;
+ } // Scalar == scalar
+ if (case_sensitive) return _mp_arg(2)==_mp_arg(4);
+ return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4));
+ }
+
+ static double mp_vector_off(_cimg_math_parser& mp) {
+ const unsigned int
+ ptr = (unsigned int)mp.opcode[2] + 1,
+ siz = (unsigned int)mp.opcode[3];
+ const int off = (int)_mp_arg(4);
+ return off>=0 && off<(int)siz?mp.mem[ptr + off]:cimg::type<double>::nan();
+ }
+
+ static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector)
+ unsigned int
+ siz = (unsigned int)mp.opcode[2],
+ ptrs = (unsigned int)mp.opcode[5] + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ mp_func op = (mp_func)mp.opcode[3];
+ CImg<ulongT> l_opcode(4);
+ l_opcode[2] = mp.opcode[4]; // Scalar argument1
+ l_opcode.swap(mp.opcode);
+ ulongT &argument2 = mp.opcode[3];
+ while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); }
+ l_opcode.swap(mp.opcode);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_map_v(_cimg_math_parser& mp) { // Operator(vector)
+ unsigned int
+ siz = (unsigned int)mp.opcode[2],
+ ptrs = (unsigned int)mp.opcode[4] + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ mp_func op = (mp_func)mp.opcode[3];
+ CImg<ulongT> l_opcode(1,3);
+ l_opcode.swap(mp.opcode);
+ ulongT &argument = mp.opcode[2];
+ while (siz-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); }
+ l_opcode.swap(mp.opcode);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_map_vs(_cimg_math_parser& mp) { // Operator(vector,scalar)
+ unsigned int
+ siz = (unsigned int)mp.opcode[2],
+ ptrs = (unsigned int)mp.opcode[4] + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ mp_func op = (mp_func)mp.opcode[3];
+ CImg<ulongT> l_opcode(1,4);
+ l_opcode[3] = mp.opcode[5]; // Scalar argument2
+ l_opcode.swap(mp.opcode);
+ ulongT &argument1 = mp.opcode[2];
+ while (siz-->0) { argument1 = ptrs++; *(ptrd++) = (*op)(mp); }
+ l_opcode.swap(mp.opcode);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_map_vss(_cimg_math_parser& mp) { // Operator(vector,scalar,scalar)
+ unsigned int
+ siz = (unsigned int)mp.opcode[2],
+ ptrs = (unsigned int)mp.opcode[4] + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ mp_func op = (mp_func)mp.opcode[3];
+ CImg<ulongT> l_opcode(1,5);
+ l_opcode[3] = mp.opcode[5]; // Scalar argument2
+ l_opcode[4] = mp.opcode[6]; // Scalar argument3
+ l_opcode.swap(mp.opcode);
+ ulongT &argument1 = mp.opcode[2];
+ while (siz-->0) { argument1 = ptrs++; *(ptrd++) = (*op)(mp); }
+ l_opcode.swap(mp.opcode);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_map_vv(_cimg_math_parser& mp) { // Operator(vector,vector)
+ unsigned int
+ siz = (unsigned int)mp.opcode[2],
+ ptrs1 = (unsigned int)mp.opcode[4] + 1,
+ ptrs2 = (unsigned int)mp.opcode[5] + 1;
+ double *ptrd = &_mp_arg(1) + 1;
+ mp_func op = (mp_func)mp.opcode[3];
+ CImg<ulongT> l_opcode(1,4);
+ l_opcode.swap(mp.opcode);
+ ulongT &argument1 = mp.opcode[2], &argument2 = mp.opcode[3];
+ while (siz-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); }
+ l_opcode.swap(mp.opcode);
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_neq(_cimg_math_parser& mp) {
+ return !mp_vector_eq(mp);
+ }
+
+ static double mp_vector_print(_cimg_math_parser& mp) {
+ cimg_pragma_openmp(critical)
+ {
+ CImg<charT> expr(mp.opcode[2] - 4);
+ const ulongT *ptrs = mp.opcode._data + 4;
+ cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++);
+ cimg::strellipsize(expr);
+ unsigned int
+ ptr = (unsigned int)mp.opcode[1] + 1,
+ siz0 = (unsigned int)mp.opcode[3],
+ siz = siz0;
+ cimg::mutex(6);
+ std::fprintf(cimg::output(),"\n[_cimg_math_parser] %s = [ ",expr._data);
+ unsigned int count = 0;
+ while (siz-->0) {
+ if (count>=64 && siz>=64) {
+ std::fprintf(cimg::output(),"...,");
+ ptr = (unsigned int)mp.opcode[1] + 1 + siz0 - 64;
+ siz = 64;
+ } else std::fprintf(cimg::output(),"%g%s",mp.mem[ptr++],siz?",":"");
+ ++count;
+ }
+ std::fprintf(cimg::output()," ] (size: %u)",siz0);
+ std::fflush(cimg::output());
+ cimg::mutex(6,0);
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_resize(_cimg_math_parser& mp) {
+ double *const ptrd = &_mp_arg(1) + 1;
+ const unsigned int p1 = (unsigned int)mp.opcode[2], p2 = (unsigned int)mp.opcode[4];
+ const int
+ interpolation = (int)_mp_arg(5),
+ boundary_conditions = (int)_mp_arg(6);
+ if (p2) { // Resize vector
+ const double *const ptrs = &_mp_arg(3) + 1;
+ CImg<doubleT>(ptrd,p1,1,1,1,true) = CImg<doubleT>(ptrs,p2,1,1,1,true).
+ get_resize(p1,1,1,1,interpolation,boundary_conditions);
+ } else { // Resize scalar
+ const double value = _mp_arg(3);
+ CImg<doubleT>(ptrd,p1,1,1,1,true) = CImg<doubleT>(1,1,1,1,value).resize(p1,1,1,1,interpolation,
+ boundary_conditions);
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_reverse(_cimg_math_parser& mp) {
+ double *const ptrd = &_mp_arg(1) + 1;
+ const double *const ptrs = &_mp_arg(2) + 1;
+ const unsigned int p1 = (unsigned int)mp.opcode[3];
+ CImg<doubleT>(ptrd,p1,1,1,1,true) = CImg<doubleT>(ptrs,p1,1,1,1,true).get_mirror('x');
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_vector_set_off(_cimg_math_parser& mp) {
+ const unsigned int
+ ptr = (unsigned int)mp.opcode[2] + 1,
+ siz = (unsigned int)mp.opcode[3];
+ const int off = (int)_mp_arg(4);
+ if (off>=0 && off<(int)siz) mp.mem[ptr + off] = _mp_arg(5);
+ return _mp_arg(5);
+ }
+
+ static double mp_whiledo(_cimg_math_parser& mp) {
+ const ulongT
+ mem_body = mp.opcode[1],
+ mem_cond = mp.opcode[2];
+ const CImg<ulongT>
+ *const p_cond = ++mp.p_code,
+ *const p_body = p_cond + mp.opcode[3],
+ *const p_end = p_body + mp.opcode[4];
+ const unsigned int vsiz = (unsigned int)mp.opcode[5];
+ bool is_cond = false;
+ if (mp.opcode[6]) { // Set default value for result and condition if necessary
+ if (vsiz) CImg<doubleT>(&mp.mem[mem_body] + 1,vsiz,1,1,1,true).fill(cimg::type<double>::nan());
+ else mp.mem[mem_body] = cimg::type<double>::nan();
+ }
+ if (mp.opcode[7]) mp.mem[mem_cond] = 0;
+ const unsigned int _break_type = mp.break_type;
+ mp.break_type = 0;
+ do {
+ for (mp.p_code = p_cond; mp.p_code<p_body; ++mp.p_code) { // Evaluate condition
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.break_type==1) break;
+ is_cond = (bool)mp.mem[mem_cond];
+ if (is_cond && !mp.break_type) // Evaluate body
+ for (mp.p_code = p_body; mp.p_code<p_end; ++mp.p_code) {
+ mp.opcode._data = mp.p_code->_data;
+ const ulongT target = mp.opcode[1];
+ mp.mem[target] = _cimg_mp_defunc(mp);
+ }
+ if (mp.break_type==1) break; else if (mp.break_type==2) mp.break_type = 0;
+ } while (is_cond);
+
+ mp.break_type = _break_type;
+ mp.p_code = p_end - 1;
+ return mp.mem[mem_body];
+ }
+
+ static double mp_Ioff(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ boundary_conditions = (unsigned int)_mp_arg(3),
+ vsiz = (unsigned int)mp.opcode[4];
+ const CImg<T> &img = mp.imgin;
+ const longT
+ off = (longT)_mp_arg(2),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T *ptrs;
+ if (off>=0 && off<whd) {
+ ptrs = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2);
+ ptrs = &img[moff<whd?moff:whd2 - moff - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ case 2 : // Periodic
+ ptrs = &img[cimg::mod(off,whd)];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ case 1 : // Neumann
+ ptrs = off<0?&img[0]:&img[whd - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ default : // Dirichlet
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_Ixyz(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ interpolation = (unsigned int)_mp_arg(5),
+ boundary_conditions = (unsigned int)_mp_arg(6),
+ vsiz = (unsigned int)mp.opcode[7];
+ const CImg<T> &img = mp.imgin;
+ const double x = _mp_arg(2), y = _mp_arg(3), z = _mp_arg(4);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ const T *ptrs;
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 2 : { // Periodic
+ const int
+ cx = cimg::mod((int)x,img.width()),
+ cy = cimg::mod((int)y,img.height()),
+ cz = cimg::mod((int)z,img.depth());
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 1 : { // Neumann
+ ptrs = &img._atXYZ((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ default : // Dirichlet
+ if (img.containsXYZC(x,y,z)) {
+ ptrs = &img((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } else std::memset(ptrd,0,vsiz*sizeof(double));
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 2 : { // Periodic
+ const float
+ cx = cimg::mod((float)x,(float)img.width()),
+ cy = cimg::mod((float)y,(float)img.height()),
+ cz = cimg::mod((float)z,(float)img.depth());
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 1 : // Neumann
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ((float)x,(float)y,(float)z,c);
+ break;
+ default : // Dirichlet
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img.linear_atXYZ((float)x,(float)y,(float)z,c,(T)0);
+ }
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_Joff(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ boundary_conditions = (unsigned int)_mp_arg(3),
+ vsiz = (unsigned int)mp.opcode[4];
+ const CImg<T> &img = mp.imgin;
+ const int
+ ox = (int)mp.mem[_cimg_mp_slot_x],
+ oy = (int)mp.mem[_cimg_mp_slot_y],
+ oz = (int)mp.mem[_cimg_mp_slot_z];
+ const longT
+ off = img.offset(ox,oy,oz) + (longT)_mp_arg(2),
+ whd = (longT)img.width()*img.height()*img.depth();
+ const T *ptrs;
+ if (off>=0 && off<whd) {
+ ptrs = &img[off];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ if (img._data) switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const longT whd2 = 2*whd, moff = cimg::mod(off,whd2);
+ ptrs = &img[moff<whd?moff:whd2 - moff - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ }
+ case 2 : // Periodic
+ ptrs = &img[cimg::mod(off,whd)];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ case 1 : // Neumann
+ ptrs = off<0?&img[0]:&img[whd - 1];
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return cimg::type<double>::nan();
+ default : // Dirichlet
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+ std::memset(ptrd,0,vsiz*sizeof(double));
+ return cimg::type<double>::nan();
+ }
+
+ static double mp_Jxyz(_cimg_math_parser& mp) {
+ double *ptrd = &_mp_arg(1) + 1;
+ const unsigned int
+ interpolation = (unsigned int)_mp_arg(5),
+ boundary_conditions = (unsigned int)_mp_arg(6),
+ vsiz = (unsigned int)mp.opcode[7];
+ const CImg<T> &img = mp.imgin;
+ const double
+ ox = mp.mem[_cimg_mp_slot_x], oy = mp.mem[_cimg_mp_slot_y], oz = mp.mem[_cimg_mp_slot_z],
+ x = ox + _mp_arg(2), y = oy + _mp_arg(3), z = oz + _mp_arg(4);
+ const ulongT whd = (ulongT)img._width*img._height*img._depth;
+ const T *ptrs;
+ if (interpolation==0) switch (boundary_conditions) { // Nearest neighbor interpolation
+ case 3 : { // Mirror
+ const int
+ w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(),
+ mx = cimg::mod((int)x,w2), my = cimg::mod((int)y,h2), mz = cimg::mod((int)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 2 : { // Periodic
+ const int
+ cx = cimg::mod((int)x,img.width()),
+ cy = cimg::mod((int)y,img.height()),
+ cz = cimg::mod((int)z,img.depth());
+ ptrs = &img(cx,cy,cz);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ case 1 : { // Neumann
+ ptrs = &img._atXYZ((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } break;
+ default : // Dirichlet
+ if (img.containsXYZC(x,y,z)) {
+ ptrs = &img((int)x,(int)y,(int)z);
+ cimg_for_inC(img,0,vsiz - 1,c) { *(ptrd++) = (double)*ptrs; ptrs+=whd; }
+ } else std::memset(ptrd,0,vsiz*sizeof(double));
+ } else switch (boundary_conditions) { // Linear interpolation
+ case 3 : { // Mirror
+ const float
+ w2 = 2.0f*img.width(), h2 = 2.0f*img.height(), d2 = 2.0f*img.depth(),
+ mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2),
+ cx = mx<img.width()?mx:w2 - mx - 1,
+ cy = my<img.height()?my:h2 - my - 1,
+ cz = mz<img.depth()?mz:d2 - mz - 1;
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 2 : { // Periodic
+ const float
+ cx = cimg::mod((float)x,(float)img.width()),
+ cy = cimg::mod((float)y,(float)img.height()),
+ cz = cimg::mod((float)z,(float)img.depth());
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ(cx,cy,cz,c);
+ } break;
+ case 1 : // Neumann
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img._linear_atXYZ((float)x,(float)y,(float)z,c);
+ break;
+ default : // Dirichlet
+ cimg_for_inC(img,0,vsiz - 1,c) *(ptrd++) = (double)img.linear_atXYZ((float)x,(float)y,(float)z,c,(T)0);
+ }
+ return cimg::type<double>::nan();
+ }
+
+#undef _mp_arg
+
+ }; // struct _cimg_math_parser {}
+
+ //! Compute the square value of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square value \f$I_{(x,y,z,c)}^2\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg");
+ (img,img.get_sqr().normalize(0,255)).display();
+ \endcode
+ \image html ref_sqr.jpg
+ **/
+ CImg<T>& sqr() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(val*val); };
+ return *this;
+ }
+
+ //! Compute the square value of each pixel value \newinstance.
+ CImg<Tfloat> get_sqr() const {
+ return CImg<Tfloat>(*this,false).sqr();
+ }
+
+ //! Compute the square root of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its square root \f$\sqrt{I_{(x,y,z,c)}}\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg");
+ (img,img.get_sqrt().normalize(0,255)).display();
+ \endcode
+ \image html ref_sqrt.jpg
+ **/
+ CImg<T>& sqrt() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::sqrt((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the square root of each pixel value \newinstance.
+ CImg<Tfloat> get_sqrt() const {
+ return CImg<Tfloat>(*this,false).sqrt();
+ }
+
+ //! Compute the exponential of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its exponential \f$e^{I_{(x,y,z,c)}}\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& exp() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=4096))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::exp((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the exponential of each pixel value \newinstance.
+ CImg<Tfloat> get_exp() const {
+ return CImg<Tfloat>(*this,false).exp();
+ }
+
+ //! Compute the logarithm of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its logarithm
+ \f$\mathrm{log}_{e}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& log() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=262144))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::log((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the logarithm of each pixel value \newinstance.
+ CImg<Tfloat> get_log() const {
+ return CImg<Tfloat>(*this,false).log();
+ }
+
+ //! Compute the base-2 logarithm of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-2 logarithm
+ \f$\mathrm{log}_{2}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& log2() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=4096))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::log2((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the base-10 logarithm of each pixel value \newinstance.
+ CImg<Tfloat> get_log2() const {
+ return CImg<Tfloat>(*this,false).log2();
+ }
+
+ //! Compute the base-10 logarithm of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its base-10 logarithm
+ \f$\mathrm{log}_{10}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& log10() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=4096))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::log10((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the base-10 logarithm of each pixel value \newinstance.
+ CImg<Tfloat> get_log10() const {
+ return CImg<Tfloat>(*this,false).log10();
+ }
+
+ //! Compute the absolute value of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its absolute value \f$|I_{(x,y,z,c)}|\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& abs() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288))
+ cimg_rof(*this,ptrd,T) *ptrd = cimg::abs(*ptrd);
+ return *this;
+ }
+
+ //! Compute the absolute value of each pixel value \newinstance.
+ CImg<Tfloat> get_abs() const {
+ return CImg<Tfloat>(*this,false).abs();
+ }
+
+ //! Compute the sign of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sign
+ \f$\mathrm{sign}(I_{(x,y,z,c)})\f$.
+ \note
+ - The sign is set to:
+ - \c 1 if pixel value is strictly positive.
+ - \c -1 if pixel value is strictly negative.
+ - \c 0 if pixel value is equal to \c 0.
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& sign() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = cimg::sign(*ptrd);
+ return *this;
+ }
+
+ //! Compute the sign of each pixel value \newinstance.
+ CImg<Tfloat> get_sign() const {
+ return CImg<Tfloat>(*this,false).sign();
+ }
+
+ //! Compute the cosine of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its cosine \f$\cos(I_{(x,y,z,c)})\f$.
+ \note
+ - Pixel values are regarded as being in \e radian.
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& cos() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::cos((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the cosine of each pixel value \newinstance.
+ CImg<Tfloat> get_cos() const {
+ return CImg<Tfloat>(*this,false).cos();
+ }
+
+ //! Compute the sine of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sine \f$\sin(I_{(x,y,z,c)})\f$.
+ \note
+ - Pixel values are regarded as being in \e radian.
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& sin() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::sin((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the sine of each pixel value \newinstance.
+ CImg<Tfloat> get_sin() const {
+ return CImg<Tfloat>(*this,false).sin();
+ }
+
+ //! Compute the sinc of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its sinc
+ \f$\mathrm{sinc}(I_{(x,y,z,c)})\f$.
+ \note
+ - Pixel values are regarded as being exin \e radian.
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& sinc() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::sinc((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the sinc of each pixel value \newinstance.
+ CImg<Tfloat> get_sinc() const {
+ return CImg<Tfloat>(*this,false).sinc();
+ }
+
+ //! Compute the tangent of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its tangent \f$\tan(I_{(x,y,z,c)})\f$.
+ \note
+ - Pixel values are regarded as being exin \e radian.
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& tan() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::tan((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the tangent of each pixel value \newinstance.
+ CImg<Tfloat> get_tan() const {
+ return CImg<Tfloat>(*this,false).tan();
+ }
+
+ //! Compute the hyperbolic cosine of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic cosine
+ \f$\mathrm{cosh}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& cosh() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::cosh((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the hyperbolic cosine of each pixel value \newinstance.
+ CImg<Tfloat> get_cosh() const {
+ return CImg<Tfloat>(*this,false).cosh();
+ }
+
+ //! Compute the hyperbolic sine of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic sine
+ \f$\mathrm{sinh}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& sinh() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::sinh((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the hyperbolic sine of each pixel value \newinstance.
+ CImg<Tfloat> get_sinh() const {
+ return CImg<Tfloat>(*this,false).sinh();
+ }
+
+ //! Compute the hyperbolic tangent of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its hyperbolic tangent
+ \f$\mathrm{tanh}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& tanh() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=2048))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::tanh((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the hyperbolic tangent of each pixel value \newinstance.
+ CImg<Tfloat> get_tanh() const {
+ return CImg<Tfloat>(*this,false).tanh();
+ }
+
+ //! Compute the arccosine of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arccosine
+ \f$\mathrm{acos}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& acos() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::acos((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the arccosine of each pixel value \newinstance.
+ CImg<Tfloat> get_acos() const {
+ return CImg<Tfloat>(*this,false).acos();
+ }
+
+ //! Compute the arcsine of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arcsine
+ \f$\mathrm{asin}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& asin() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::asin((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the arcsine of each pixel value \newinstance.
+ CImg<Tfloat> get_asin() const {
+ return CImg<Tfloat>(*this,false).asin();
+ }
+
+ //! Compute the arctangent of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent
+ \f$\mathrm{atan}(I_{(x,y,z,c)})\f$.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ **/
+ CImg<T>& atan() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::atan((double)*ptrd);
+ return *this;
+ }
+
+ //! Compute the arctangent of each pixel value \newinstance.
+ CImg<Tfloat> get_atan() const {
+ return CImg<Tfloat>(*this,false).atan();
+ }
+
+ //! Compute the arctangent2 of each pixel value.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its arctangent2
+ \f$\mathrm{atan2}(I_{(x,y,z,c)})\f$.
+ \param img Image whose pixel values specify the second argument of the \c atan2() function.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ \par Example
+ \code
+ const CImg<float>
+ img_x(100,100,1,1,"x-w/2",false), // Define an horizontal centered gradient, from '-width/2' to 'width/2'.
+ img_y(100,100,1,1,"y-h/2",false), // Define a vertical centered gradient, from '-height/2' to 'height/2'.
+ img_atan2 = img_y.get_atan2(img_x); // Compute atan2(y,x) for each pixel value.
+ (img_x,img_y,img_atan2).display();
+ \endcode
+ **/
+ template<typename t>
+ CImg<T>& atan2(const CImg<t>& img) {
+ const ulongT siz = size(), isiz = img.size();
+ if (siz && isiz) {
+ if (is_overlapped(img)) return atan2(+img);
+ T *ptrd = _data, *const ptre = _data + siz;
+ if (siz>isiz) for (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)std::atan2((double)*ptrd,(double)*(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)std::atan2((double)*ptrd,(double)*(ptrs++));
+ }
+ return *this;
+ }
+
+ //! Compute the arctangent2 of each pixel value \newinstance.
+ template<typename t>
+ CImg<Tfloat> get_atan2(const CImg<t>& img) const {
+ return CImg<Tfloat>(*this,false).atan2(img);
+ }
+
+ //! In-place pointwise multiplication.
+ /**
+ Compute the pointwise multiplication between the image instance and the specified input image \c img.
+ \param img Input image, as the second operand of the multiplication.
+ \note
+ - Similar to operator+=(const CImg<t>&), except that it performs a pointwise multiplication
+ instead of an addition.
+ - It does \e not perform a \e matrix multiplication. For this purpose, use operator*=(const CImg<t>&) instead.
+ \par Example
+ \code
+ CImg<float>
+ img("reference.jpg"),
+ shade(img.width,img.height(),1,1,"-(x-w/2)^2-(y-h/2)^2",false);
+ shade.normalize(0,1);
+ (img,shade,img.get_mul(shade)).display();
+ \endcode
+ **/
+ template<typename t>
+ CImg<T>& mul(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)(*ptrd * *(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd * *(ptrs++));
+ }
+ return *this;
+ }
+
+ //! In-place pointwise multiplication \newinstance.
+ template<typename t>
+ CImg<_cimg_Tt> get_mul(const CImg<t>& img) const {
+ return CImg<_cimg_Tt>(*this,false).mul(img);
+ }
+
+ //! In-place pointwise division.
+ /**
+ Similar to mul(const CImg<t>&), except that it performs a pointwise division instead of a multiplication.
+ **/
+ template<typename t>
+ CImg<T>& div(const CImg<t>& img) {
+ const ulongT 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 (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)(*ptrd / *(ptrs++));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd / *(ptrs++));
+ }
+ return *this;
+ }
+
+ //! In-place pointwise division \newinstance.
+ template<typename t>
+ CImg<_cimg_Tt> get_div(const CImg<t>& img) const {
+ return CImg<_cimg_Tt>(*this,false).div(img);
+ }
+
+ //! Raise each pixel value to a specified power.
+ /**
+ Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its power \f$I_{(x,y,z,c)}^p\f$.
+ \param p Exponent value.
+ \note
+ - The \inplace of this method statically casts the computed values to the pixel type \c T.
+ - The \newinstance returns a \c CImg<float> image, if the pixel type \c T is \e not float-valued.
+ \par Example
+ \code
+ const CImg<float>
+ img0("reference.jpg"), // Load reference color image.
+ img1 = (img0/255).pow(1.8)*=255, // Compute gamma correction, with gamma = 1.8.
+ img2 = (img0/255).pow(0.5)*=255; // Compute gamma correction, with gamma = 0.5.
+ (img0,img1,img2).display();
+ \endcode
+ **/
+ CImg<T>& pow(const double p) {
+ if (is_empty()) return *this;
+ if (p==-4) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/(val*val*val*val)); }
+ return *this;
+ }
+ if (p==-3) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/(val*val*val)); }
+ return *this;
+ }
+ if (p==-2) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/(val*val)); }
+ return *this;
+ }
+ if (p==-1) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1.0/val); }
+ return *this;
+ }
+ if (p==-0.5) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = (T)(1/std::sqrt((double)val)); }
+ return *this;
+ }
+ if (p==0) return fill((T)1);
+ if (p==0.25) return sqrt().sqrt();
+ if (p==0.5) return sqrt();
+ if (p==1) return *this;
+ if (p==2) return sqr();
+ if (p==3) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=262144))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val; }
+ return *this;
+ }
+ if (p==4) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=131072))
+ cimg_rof(*this,ptrd,T) { const T val = *ptrd; *ptrd = val*val*val*val; }
+ return *this;
+ }
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=1024))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)std::pow((double)*ptrd,p);
+ return *this;
+ }
+
+ //! Raise each pixel value to a specified power \newinstance.
+ CImg<Tfloat> get_pow(const double p) const {
+ return CImg<Tfloat>(*this,false).pow(p);
+ }
+
+ //! Raise each pixel value to a power, specified from an expression.
+ /**
+ Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition.
+ **/
+ CImg<T>& pow(const char *const expression) {
+ return pow((+*this)._fill(expression,true,true,0,0,"pow",this));
+ }
+
+ //! Raise each pixel value to a power, specified from an expression \newinstance.
+ CImg<Tfloat> get_pow(const char *const expression) const {
+ return CImg<Tfloat>(*this,false).pow(expression);
+ }
+
+ //! Raise each pixel value to a power, pointwisely specified from another image.
+ /**
+ Similar to operator+=(const CImg<t>& img), except that it performs an exponentiation instead of an addition.
+ **/
+ template<typename t>
+ CImg<T>& pow(const CImg<t>& img) {
+ const ulongT siz = size(), isiz = img.size();
+ if (siz && isiz) {
+ if (is_overlapped(img)) return pow(+img);
+ T *ptrd = _data, *const ptre = _data + siz;
+ if (siz>isiz) for (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)std::pow((double)*ptrd,(double)(*(ptrs++)));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)std::pow((double)*ptrd,(double)(*(ptrs++)));
+ }
+ return *this;
+ }
+
+ //! Raise each pixel value to a power, pointwisely specified from another image \newinstance.
+ template<typename t>
+ CImg<Tfloat> get_pow(const CImg<t>& img) const {
+ return CImg<Tfloat>(*this,false).pow(img);
+ }
+
+ //! Compute the bitwise left rotation of each pixel value.
+ /**
+ Similar to operator<<=(unsigned int), except that it performs a left rotation instead of a left shift.
+ **/
+ CImg<T>& rol(const unsigned int n=1) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::rol(*ptrd,n);
+ return *this;
+ }
+
+ //! Compute the bitwise left rotation of each pixel value \newinstance.
+ CImg<T> get_rol(const unsigned int n=1) const {
+ return (+*this).rol(n);
+ }
+
+ //! Compute the bitwise left rotation of each pixel value.
+ /**
+ Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift.
+ **/
+ CImg<T>& rol(const char *const expression) {
+ return rol((+*this)._fill(expression,true,true,0,0,"rol",this));
+ }
+
+ //! Compute the bitwise left rotation of each pixel value \newinstance.
+ CImg<T> get_rol(const char *const expression) const {
+ return (+*this).rol(expression);
+ }
+
+ //! Compute the bitwise left rotation of each pixel value.
+ /**
+ Similar to operator<<=(const CImg<t>&), except that it performs a left rotation instead of a left shift.
+ **/
+ template<typename t>
+ CImg<T>& rol(const CImg<t>& img) {
+ const ulongT siz = size(), isiz = img.size();
+ if (siz && isiz) {
+ if (is_overlapped(img)) return rol(+img);
+ T *ptrd = _data, *const ptre = _data + siz;
+ if (siz>isiz) for (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)cimg::rol(*ptrd,(unsigned int)(*(ptrs++)));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)cimg::rol(*ptrd,(unsigned int)(*(ptrs++)));
+ }
+ return *this;
+ }
+
+ //! Compute the bitwise left rotation of each pixel value \newinstance.
+ template<typename t>
+ CImg<T> get_rol(const CImg<t>& img) const {
+ return (+*this).rol(img);
+ }
+
+ //! Compute the bitwise right rotation of each pixel value.
+ /**
+ Similar to operator>>=(unsigned int), except that it performs a right rotation instead of a right shift.
+ **/
+ CImg<T>& ror(const unsigned int n=1) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::ror(*ptrd,n);
+ return *this;
+ }
+
+ //! Compute the bitwise right rotation of each pixel value \newinstance.
+ CImg<T> get_ror(const unsigned int n=1) const {
+ return (+*this).ror(n);
+ }
+
+ //! Compute the bitwise right rotation of each pixel value.
+ /**
+ Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift.
+ **/
+ CImg<T>& ror(const char *const expression) {
+ return ror((+*this)._fill(expression,true,true,0,0,"ror",this));
+ }
+
+ //! Compute the bitwise right rotation of each pixel value \newinstance.
+ CImg<T> get_ror(const char *const expression) const {
+ return (+*this).ror(expression);
+ }
+
+ //! Compute the bitwise right rotation of each pixel value.
+ /**
+ Similar to operator>>=(const CImg<t>&), except that it performs a right rotation instead of a right shift.
+ **/
+ template<typename t>
+ CImg<T>& ror(const CImg<t>& img) {
+ const ulongT siz = size(), isiz = img.size();
+ if (siz && isiz) {
+ if (is_overlapped(img)) return ror(+img);
+ T *ptrd = _data, *const ptre = _data + siz;
+ if (siz>isiz) for (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = (T)cimg::ror(*ptrd,(unsigned int)(*(ptrs++)));
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)cimg::ror(*ptrd,(unsigned int)(*(ptrs++)));
+ }
+ return *this;
+ }
+
+ //! Compute the bitwise right rotation of each pixel value \newinstance.
+ template<typename t>
+ CImg<T> get_ror(const CImg<t>& img) const {
+ return (+*this).ror(img);
+ }
+
+ //! Pointwise min operator between instance image and a value.
+ /**
+ \param val Value used as the reference argument of the min operator.
+ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by
+ \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{val})\f$.
+ **/
+ CImg<T>& min(const T& val) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536))
+ cimg_rof(*this,ptrd,T) *ptrd = std::min(*ptrd,val);
+ return *this;
+ }
+
+ //! Pointwise min operator between instance image and a value \newinstance.
+ CImg<T> get_min(const T& val) const {
+ return (+*this).min(val);
+ }
+
+ //! Pointwise min operator between two images.
+ /**
+ \param img Image used as the reference argument of the min operator.
+ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by
+ \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$.
+ **/
+ template<typename t>
+ CImg<T>& min(const CImg<t>& img) {
+ const ulongT siz = size(), isiz = img.size();
+ if (siz && isiz) {
+ if (is_overlapped(img)) return min(+img);
+ T *ptrd = _data, *const ptre = _data + siz;
+ if (siz>isiz) for (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = std::min((T)*(ptrs++),*ptrd);
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = std::min((T)*(ptrs++),*ptrd);
+ }
+ return *this;
+ }
+
+ //! Pointwise min operator between two images \newinstance.
+ 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 an expression.
+ /**
+ \param expression Math formula as a C-string.
+ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by
+ \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$.
+ **/
+ CImg<T>& min(const char *const expression) {
+ return min((+*this)._fill(expression,true,true,0,0,"min",this));
+ }
+
+ //! Pointwise min operator between an image and an expression \newinstance.
+ CImg<Tfloat> get_min(const char *const expression) const {
+ return CImg<Tfloat>(*this,false).min(expression);
+ }
+
+ //! Pointwise max operator between instance image and a value.
+ /**
+ \param val Value used as the reference argument of the max operator.
+ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by
+ \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{val})\f$.
+ **/
+ CImg<T>& max(const T& val) {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536))
+ cimg_rof(*this,ptrd,T) *ptrd = std::max(*ptrd,val);
+ return *this;
+ }
+
+ //! Pointwise max operator between instance image and a value \newinstance.
+ CImg<T> get_max(const T& val) const {
+ return (+*this).max(val);
+ }
+
+ //! Pointwise max operator between two images.
+ /**
+ \param img Image used as the reference argument of the max operator.
+ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by
+ \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$.
+ **/
+ template<typename t>
+ CImg<T>& max(const CImg<t>& img) {
+ const ulongT siz = size(), isiz = img.size();
+ if (siz && isiz) {
+ if (is_overlapped(img)) return max(+img);
+ T *ptrd = _data, *const ptre = _data + siz;
+ if (siz>isiz) for (ulongT n = siz/isiz; n; --n)
+ for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
+ *ptrd = std::max((T)*(ptrs++),*ptrd);
+ for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = std::max((T)*(ptrs++),*ptrd);
+ }
+ return *this;
+ }
+
+ //! Pointwise max operator between two images \newinstance.
+ 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 an expression.
+ /**
+ \param expression Math formula as a C-string.
+ \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by
+ \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$.
+ **/
+ CImg<T>& max(const char *const expression) {
+ return max((+*this)._fill(expression,true,true,0,0,"max",this));
+ }
+
+ //! Pointwise max operator between an image and an expression \newinstance.
+ CImg<Tfloat> get_max(const char *const expression) const {
+ return CImg<Tfloat>(*this,false).max(expression);
+ }
+
+ //! Return a reference to the minimum pixel value.
+ /**
+ **/
+ 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;
+ }
+
+ //! Return a reference to the minimum pixel value \const.
+ 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.
+ /**
+ **/
+ 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;
+ }
+
+ //! Return a reference to the maximum pixel value \const.
+ 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 as well as the maximum pixel value.
+ /**
+ \param[out] max_val 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;
+ }
+
+ //! Return a reference to the minimum pixel value as well as the maximum pixel value \const.
+ 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 as well as the minimum pixel value.
+ /**
+ \param[out] min_val 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;
+ }
+
+ //! Return a reference to the maximum pixel value as well as the minimum pixel value \const.
+ 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 pixel value.
+ /**
+ \param k Rank of the search smallest element.
+ **/
+ T kth_smallest(const ulongT k) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "kth_smallest(): Empty instance.",
+ cimg_instance);
+ CImg<T> arr(*this);
+ ulongT 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 ulongT 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]);
+ ulongT 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 the median pixel value.
+ /**
+ **/
+ T median() const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "median(): Empty instance.",
+ cimg_instance);
+ const ulongT s = size();
+ switch (s) {
+ case 1 : return _data[0];
+ case 2 : return cimg::median(_data[0],_data[1]);
+ case 3 : return cimg::median(_data[0],_data[1],_data[2]);
+ case 5 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4]);
+ case 7 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6]);
+ case 9 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8]);
+ case 13 : return cimg::median(_data[0],_data[1],_data[2],_data[3],_data[4],_data[5],_data[6],_data[7],_data[8],
+ _data[9],_data[10],_data[11],_data[12]);
+ }
+ const T res = kth_smallest(s>>1);
+ return (s%2)?res:(T)((res + kth_smallest((s>>1) - 1))/2);
+ }
+
+ //! Return the product of all the pixel values.
+ /**
+ **/
+ double product() const {
+ if (is_empty()) return 0;
+ double res = 1;
+ cimg_for(*this,ptrs,T) res*=(double)*ptrs;
+ return res;
+ }
+
+ //! Return the sum of all the pixel values.
+ /**
+ **/
+ double sum() const {
+ double res = 0;
+ cimg_for(*this,ptrs,T) res+=(double)*ptrs;
+ return res;
+ }
+
+ //! Return the average pixel value.
+ /**
+ **/
+ double mean() const {
+ double res = 0;
+ cimg_for(*this,ptrs,T) res+=(double)*ptrs;
+ return res/size();
+ }
+
+ //! Return the variance of the pixel values.
+ /**
+ \param variance_method Method used to estimate the variance. Can be:
+ - \c 0: Second moment, computed as
+ \f$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$.
+ - \c 1: Best unbiased estimator, computed as \f$\frac{1}{N - 1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 \f$.
+ - \c 2: Least median of squares.
+ - \c 3: Least trimmed of squares.
+ **/
+ double variance(const unsigned int variance_method=1) const {
+ double foo;
+ return variance_mean(variance_method,foo);
+ }
+
+ //! Return the variance as well as the average of the pixel values.
+ /**
+ \param variance_method Method used to estimate the variance (see variance(const unsigned int) const).
+ \param[out] mean Average pixel value.
+ **/
+ template<typename t>
+ double variance_mean(const unsigned int variance_method, t& mean) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "variance_mean(): Empty instance.",
+ cimg_instance);
+
+ double variance = 0, average = 0;
+ const ulongT siz = size();
+ switch (variance_method) {
+ case 0 : { // Least mean square (standard definition)
+ double S = 0, S2 = 0;
+ cimg_for(*this,ptrs,T) { const double val = (double)*ptrs; S+=val; S2+=val*val; }
+ variance = (S2 - S*S/siz)/siz;
+ average = S;
+ } break;
+ case 1 : { // Least mean square (robust definition)
+ double S = 0, S2 = 0;
+ cimg_for(*this,ptrs,T) { const double val = (double)*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,false);
+ buf.sort();
+ const ulongT siz2 = siz>>1;
+ const double med_i = (double)buf[siz2];
+ cimg_for(buf,ptrs,Tfloat) {
+ const double val = (double)*ptrs; *ptrs = (Tfloat)cimg::abs(val - med_i); average+=val;
+ }
+ buf.sort();
+ const double sig = (double)(1.4828*buf[siz2]);
+ variance = sig*sig;
+ } break;
+ default : { // Least trimmed of Squares
+ CImg<Tfloat> buf(*this,false);
+ const ulongT siz2 = siz>>1;
+ cimg_for(buf,ptrs,Tfloat) {
+ const double val = (double)*ptrs; (*ptrs)=(Tfloat)((*ptrs)*val); average+=val;
+ }
+ buf.sort();
+ double a = 0;
+ const Tfloat *ptrs = buf._data;
+ for (ulongT j = 0; j<siz2; ++j) a+=(double)*(ptrs++);
+ const double sig = (double)(2.6477*std::sqrt(a/siz2));
+ variance = sig*sig;
+ }
+ }
+ mean = (t)(average/siz);
+ return variance>0?variance:0;
+ }
+
+ //! Return estimated variance of the noise.
+ /**
+ \param variance_method Method used to compute the variance (see variance(const unsigned int) const).
+ \note Because of structures such as edges in images it is
+ recommanded to use a robust variance estimation. The variance of the
+ noise is estimated by computing the variance of the Laplacian \f$(\Delta
+ I)^2 \f$ scaled by a factor \f$c\f$ insuring \f$ c E[(\Delta I)^2]=
+ \sigma^2\f$ where \f$\sigma\f$ is the noise variance.
+ **/
+ double variance_noise(const unsigned int variance_method=2) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "variance_noise(): Empty instance.",
+ cimg_instance);
+
+ const ulongT siz = size();
+ if (!siz || !_data) return 0;
+ if (variance_method>1) { // Compute a scaled version of the Laplacian.
+ CImg<Tdouble> tmp(*this);
+ if (_depth==1) {
+ const double cste = 1.0/std::sqrt(20.0); // Depends on how the Laplacian is computed.
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=262144 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ CImg_3x3(I,T);
+ cimg_for3x3(*this,x,y,0,c,I,T) {
+ tmp(x,y,c) = cste*((double)Inc + (double)Ipc + (double)Icn +
+ (double)Icp - 4*(double)Icc);
+ }
+ }
+ } else {
+ const double cste = 1.0/std::sqrt(42.0); // Depends on how the Laplacian is computed.
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=262144 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ CImg_3x3x3(I,T);
+ cimg_for3x3x3(*this,x,y,z,c,I,T) {
+ tmp(x,y,z,c) = cste*(
+ (double)Incc + (double)Ipcc + (double)Icnc + (double)Icpc +
+ (double)Iccn + (double)Iccp - 6*(double)Iccc);
+ }
+ }
+ }
+ return tmp.variance(variance_method);
+ }
+
+ // Version that doesn't need intermediate images.
+ double variance = 0, S = 0, S2 = 0;
+ if (_depth==1) {
+ const double cste = 1.0/std::sqrt(20.0);
+ CImg_3x3(I,T);
+ cimg_forC(*this,c) cimg_for3x3(*this,x,y,0,c,I,T) {
+ const double val = cste*((double)Inc + (double)Ipc +
+ (double)Icn + (double)Icp - 4*(double)Icc);
+ S+=val; S2+=val*val;
+ }
+ } else {
+ const double cste = 1.0/std::sqrt(42.0);
+ CImg_3x3x3(I,T);
+ cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,T) {
+ const double val = cste *
+ ((double)Incc + (double)Ipcc + (double)Icnc +
+ (double)Icpc +
+ (double)Iccn + (double)Iccp - 6*(double)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.
+ /**
+ \param img Image used as the second argument of the MSE operator.
+ **/
+ template<typename t>
+ double 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);
+ double vMSE = 0;
+ const t* ptr2 = img._data;
+ cimg_for(*this,ptr1,T) {
+ const double diff = (double)*ptr1 - (double)*(ptr2++);
+ vMSE+=diff*diff;
+ }
+ const ulongT siz = img.size();
+ if (siz) vMSE/=siz;
+ return vMSE;
+ }
+
+ //! Compute the PSNR (Peak Signal-to-Noise Ratio) between two images.
+ /**
+ \param img Image used as the second argument of the PSNR operator.
+ \param max_value Maximum theoretical value of the signal.
+ **/
+ template<typename t>
+ double PSNR(const CImg<t>& img, const double max_value=255) const {
+ const double vMSE = (double)std::sqrt(MSE(img));
+ return (vMSE!=0)?(double)(20*std::log10(max_value/vMSE)):(double)(cimg::type<double>::max());
+ }
+
+ //! Evaluate math formula.
+ /**
+ \param expression Math formula, as a C-string.
+ \param x Value of the pre-defined variable \c x.
+ \param y Value of the pre-defined variable \c y.
+ \param z Value of the pre-defined variable \c z.
+ \param c Value of the pre-defined variable \c c.
+ \param list_inputs A list of input images attached to the specified math formula.
+ \param list_outputs A pointer to a list of output images attached to the specified math formula.
+ **/
+ double eval(const char *const expression,
+ const double x=0, const double y=0, const double z=0, const double c=0,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) {
+ return _eval(this,expression,x,y,z,c,list_inputs,list_outputs);
+ }
+
+ //! Evaluate math formula \const.
+ double eval(const char *const expression,
+ const double x=0, const double y=0, const double z=0, const double c=0,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) const {
+ return _eval(0,expression,x,y,z,c,list_inputs,list_outputs);
+ }
+
+ double _eval(CImg<T> *const img_output, const char *const expression,
+ const double x, const double y, const double z, const double c,
+ const CImgList<T> *const list_inputs, CImgList<T> *const list_outputs) const {
+ if (!expression) return 0;
+ if (!expression[1]) switch (*expression) { // Single-char optimization.
+ case 'w' : return (double)_width;
+ case 'h' : return (double)_height;
+ case 'd' : return (double)_depth;
+ case 's' : return (double)_spectrum;
+ case 'r' : return (double)_is_shared;
+ }
+ _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' ||
+ *expression=='*' || *expression==':'),"eval",
+ *this,img_output,list_inputs,list_outputs,false);
+ const double val = mp(x,y,z,c);
+ mp.end();
+ return val;
+ }
+
+ //! Evaluate math formula.
+ /**
+ \param[out] output Contains values of output vector returned by the evaluated expression
+ (or is empty if the returned type is scalar).
+ \param expression Math formula, as a C-string.
+ \param x Value of the pre-defined variable \c x.
+ \param y Value of the pre-defined variable \c y.
+ \param z Value of the pre-defined variable \c z.
+ \param c Value of the pre-defined variable \c c.
+ \param list_inputs A list of input images attached to the specified math formula.
+ \param list_outputs A pointer to a list of output images attached to the specified math formula.
+ **/
+ template<typename t>
+ void eval(CImg<t> &output, const char *const expression,
+ const double x=0, const double y=0, const double z=0, const double c=0,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) {
+ _eval(output,this,expression,x,y,z,c,list_inputs,list_outputs);
+ }
+
+ //! Evaluate math formula \const.
+ template<typename t>
+ void eval(CImg<t>& output, const char *const expression,
+ const double x=0, const double y=0, const double z=0, const double c=0,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) const {
+ _eval(output,0,expression,x,y,z,c,list_inputs,list_outputs);
+ }
+
+ template<typename t>
+ void _eval(CImg<t>& output, CImg<T> *const img_output, const char *const expression,
+ const double x, const double y, const double z, const double c,
+ const CImgList<T> *const list_inputs, CImgList<T> *const list_outputs) const {
+ if (!expression) { output.assign(1); *output = 0; }
+ if (!expression[1]) switch (*expression) { // Single-char optimization.
+ case 'w' : output.assign(1); *output = (t)_width;
+ case 'h' : output.assign(1); *output = (t)_height;
+ case 'd' : output.assign(1); *output = (t)_depth;
+ case 's' : output.assign(1); *output = (t)_spectrum;
+ case 'r' : output.assign(1); *output = (t)_is_shared;
+ }
+ _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' ||
+ *expression=='*' || *expression==':'),"eval",
+ *this,img_output,list_inputs,list_outputs,false);
+ output.assign(1,std::max(1U,mp.result_dim));
+ mp(x,y,z,c,output._data);
+ mp.end();
+ }
+
+ //! Evaluate math formula on a set of variables.
+ /**
+ \param expression Math formula, as a C-string.
+ \param xyzc Set of values (x,y,z,c) used for the evaluation.
+ **/
+ template<typename t>
+ CImg<doubleT> eval(const char *const expression, const CImg<t>& xyzc,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) {
+ return _eval(this,expression,xyzc,list_inputs,list_outputs);
+ }
+
+ //! Evaluate math formula on a set of variables \const.
+ template<typename t>
+ CImg<doubleT> eval(const char *const expression, const CImg<t>& xyzc,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) const {
+ return _eval(0,expression,xyzc,list_inputs,list_outputs);
+ }
+
+ template<typename t>
+ CImg<doubleT> _eval(CImg<T> *const output, const char *const expression, const CImg<t>& xyzc,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) const {
+ CImg<doubleT> res(1,xyzc.size()/4);
+ if (!expression) return res.fill(0);
+ _cimg_math_parser mp(expression,"eval",*this,output,list_inputs,list_outputs,false);
+#ifdef cimg_use_openmp
+ cimg_pragma_openmp(parallel if (res._height>=512))
+ {
+ _cimg_math_parser
+ _mp = omp_get_thread_num()?mp:_cimg_math_parser(),
+ &lmp = omp_get_thread_num()?_mp:mp;
+ cimg_pragma_openmp(for)
+ for (unsigned int i = 0; i<res._height; ++i) {
+ const unsigned int i4 = 4*i;
+ const double
+ x = (double)xyzc[i4], y = (double)xyzc[i4 + 1],
+ z = (double)xyzc[i4 + 2], c = (double)xyzc[i4 + 3];
+ res[i] = lmp(x,y,z,c);
+ }
+ }
+#else
+ const t *ps = xyzc._data;
+ cimg_for(res,pd,double) {
+ const double x = (double)*(ps++), y = (double)*(ps++), z = (double)*(ps++), c = (double)*(ps++);
+ *pd = mp(x,y,z,c);
+ }
+#endif
+ mp.end();
+ return res;
+ }
+
+ //! Compute statistics vector from the pixel values.
+ /*
+ \param variance_method Method used to compute the variance (see variance(const unsigned int) const).
+ \return Statistics vector as
+ <tt>[min; max; mean; variance; xmin; ymin; zmin; cmin; xmax; ymax; zmax; cmax; sum; product]</tt>.
+ **/
+ CImg<Tdouble> get_stats(const unsigned int variance_method=1) const {
+ if (is_empty()) return CImg<doubleT>();
+ const ulongT siz = size();
+ const T *const odata = _data;
+ const T *pm = odata, *pM = odata;
+ double S = 0, S2 = 0, P = _data?1:0;
+ T m = *pm, M = m;
+ cimg_for(*this,ptrs,T) {
+ const T val = *ptrs;
+ const double _val = (double)val;
+ if (val<m) { m = val; pm = ptrs; }
+ if (val>M) { M = val; pM = ptrs; }
+ S+=_val;
+ S2+=_val*_val;
+ P*=_val;
+ }
+ const double
+ 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,14).fill((double)m,(double)M,mean_value,variance_value,
+ (double)xm,(double)ym,(double)zm,(double)cm,
+ (double)xM,(double)yM,(double)zM,(double)cM,
+ S,P);
+ }
+
+ //! Compute statistics vector from the pixel values \inplace.
+ CImg<T>& stats(const unsigned int variance_method=1) {
+ return get_stats(variance_method).move_to(*this);
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Vector / Matrix Operations
+ //@{
+ //-------------------------------------
+
+ //! Compute norm of the image, viewed as a matrix.
+ /**
+ \param magnitude_type Norm type. Can be:
+ - \c -1: Linf-norm
+ - \c 0: L2-norm
+ - \c 1: L1-norm
+ **/
+ double magnitude(const int magnitude_type=2) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "magnitude(): Empty instance.",
+ cimg_instance);
+ double res = 0;
+ switch (magnitude_type) {
+ case -1 : {
+ cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; }
+ } break;
+ case 1 : {
+ cimg_for(*this,ptrs,T) res+=(double)cimg::abs(*ptrs);
+ } break;
+ default : {
+ cimg_for(*this,ptrs,T) res+=(double)cimg::sqr(*ptrs);
+ res = (double)std::sqrt(res);
+ }
+ }
+ return res;
+ }
+
+ //! Compute the trace of the image, viewed as a matrix.
+ /**
+ **/
+ double trace() const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "trace(): Empty instance.",
+ cimg_instance);
+ double res = 0;
+ cimg_forX(*this,k) res+=(double)(*this)(k,k);
+ return res;
+ }
+
+ //! Compute the determinant of the image, viewed as a matrix.
+ /**
+ **/
+ double det() const {
+ if (is_empty() || _width!=_height || _depth!=1 || _spectrum!=1)
+ throw CImgInstanceException(_cimg_instance
+ "det(): Instance is not a square matrix.",
+ cimg_instance);
+
+ switch (_width) {
+ case 1 : return (double)((*this)(0,0));
+ case 2 : return (double)((*this)(0,0))*(double)((*this)(1,1)) - (double)((*this)(0,1))*(double)((*this)(1,0));
+ case 3 : {
+ const double
+ a = (double)_data[0], d = (double)_data[1], g = (double)_data[2],
+ b = (double)_data[3], e = (double)_data[4], h = (double)_data[5],
+ c = (double)_data[6], f = (double)_data[7], i = (double)_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);
+ double res = d?(double)1:(double)-1;
+ cimg_forX(lu,i) res*=lu(i,i);
+ return res;
+ }
+ }
+ }
+
+ //! Compute the dot product between instance and argument, viewed as matrices.
+ /**
+ \param img Image used as a second argument of the dot product.
+ **/
+ template<typename t>
+ double 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 ulongT nb = std::min(size(),img.size());
+ double res = 0;
+ for (ulongT off = 0; off<nb; ++off) res+=(double)_data[off]*(double)img[off];
+ return res;
+ }
+
+ //! Get vector-valued pixel located at specified position.
+ /**
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ **/
+ CImg<T> get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
+ CImg<T> res;
+ if (res._height!=_spectrum) res.assign(1,_spectrum);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ const T *ptrs = data(x,y,z);
+ T *ptrd = res._data;
+ cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return res;
+ }
+
+ //! Get (square) matrix-valued pixel located at specified position.
+ /**
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \note - The spectrum() of the image must be a square.
+ **/
+ 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 ulongT whd = (ulongT)_width*_height*_depth;
+ CImg<T> res(n,n);
+ T *ptrd = res._data;
+ cimg_forC(*this,c) { *(ptrd++) = *ptrs; ptrs+=whd; }
+ return res;
+ }
+
+ //! Get tensor-valued pixel located at specified position.
+ /**
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ **/
+ 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 ulongT whd = (ulongT)_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 vector-valued pixel at specified position.
+ /**
+ \param vec Vector to put on the instance image.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ **/
+ 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 ulongT whd = (ulongT)_width*_height*_depth;
+ T *ptrd = data(x,y,z);
+ for (unsigned int k = std::min((unsigned int)vec.size(),_spectrum); k; --k) {
+ *ptrd = (T)*(ptrs++); ptrd+=whd;
+ }
+ }
+ return *this;
+ }
+
+ //! Set (square) matrix-valued pixel at specified position.
+ /**
+ \param mat Matrix to put on the instance image.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ **/
+ 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 tensor-valued pixel at specified position.
+ /**
+ \param ten Tensor to put on the instance image.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ **/
+ 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 ulongT siz = (ulongT)_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 pixel values along axis \c y.
+ /**
+ \note Equivalent to \code unroll('y'); \endcode.
+ **/
+ CImg<T>& vector() {
+ return unroll('y');
+ }
+
+ //! Unroll pixel values along axis \c y \newinstance.
+ CImg<T> get_vector() const {
+ return get_unroll('y');
+ }
+
+ //! Resize image to become a scalar square matrix.
+ /**
+ **/
+ CImg<T>& matrix() {
+ const ulongT 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 : {
+ ulongT 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;
+ }
+
+ //! Resize image to become a scalar square matrix \newinstance.
+ CImg<T> get_matrix() const {
+ return (+*this).matrix();
+ }
+
+ //! Resize image to become a symmetric tensor.
+ /**
+ **/
+ CImg<T>& tensor() {
+ return get_tensor().move_to(*this);
+ }
+
+ //! Resize image to become a symmetric tensor \newinstance.
+ CImg<T> get_tensor() const {
+ CImg<T> res;
+ const ulongT 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;
+ }
+
+ //! Resize image to become a diagonal matrix.
+ /**
+ \note Transform the image as a diagonal matrix so that each of its initial value becomes a diagonal coefficient.
+ **/
+ CImg<T>& diagonal() {
+ return get_diagonal().move_to(*this);
+ }
+
+ //! Resize image to become a diagonal matrix \newinstance.
+ CImg<T> get_diagonal() const {
+ if (is_empty()) return *this;
+ const unsigned int siz = (unsigned int)size();
+ CImg<T> res(siz,siz,1,1,0);
+ cimg_foroff(*this,off) res((unsigned int)off,(unsigned int)off) = (*this)[off];
+ return res;
+ }
+
+ //! Replace the image by an identity matrix.
+ /**
+ \note If the instance image is not square, it is resized to a square matrix using its maximum
+ dimension as a reference.
+ **/
+ CImg<T>& identity_matrix() {
+ return identity_matrix(std::max(_width,_height)).move_to(*this);
+ }
+
+ //! Replace the image by an identity matrix \newinstance.
+ CImg<T> get_identity_matrix() const {
+ return identity_matrix(std::max(_width,_height));
+ }
+
+ //! Fill image with a linear sequence of values.
+ /**
+ \param a0 Starting value of the sequence.
+ \param a1 Ending value of the sequence.
+ **/
+ CImg<T>& sequence(const T& a0, const T& a1) {
+ if (is_empty()) return *this;
+ const ulongT siz = size() - 1;
+ T* ptr = _data;
+ if (siz) {
+ const double delta = (double)a1 - (double)a0;
+ cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz);
+ } else *ptr = a0;
+ return *this;
+ }
+
+ //! Fill image with a linear sequence of values \newinstance.
+ CImg<T> get_sequence(const T& a0, const T& a1) const {
+ return (+*this).sequence(a0,a1);
+ }
+
+ //! Transpose the image, viewed as a matrix.
+ /**
+ \note Equivalent to \code permute_axes("yxzc"); \endcode
+ **/
+ 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);
+ }
+
+ //! Transpose the image, viewed as a matrix \newinstance.
+ CImg<T> get_transpose() const {
+ return get_permute_axes("yxzc");
+ }
+
+ //! Compute the cross product between two \c 1x3 images, viewed as 3d vectors.
+ /**
+ \param img Image used as the second argument of the cross product.
+ \note The first argument of the cross product is \c *this.
+ **/
+ 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;
+ }
+
+ //! Compute the cross product between two \c 1x3 images, viewed as 3d vectors \newinstance.
+ template<typename t>
+ CImg<_cimg_Tt> get_cross(const CImg<t>& img) const {
+ return CImg<_cimg_Tt>(*this).cross(img);
+ }
+
+ //! Invert the instance image, viewed as a matrix.
+ /**
+ \param use_LU Choose the inverting algorithm. Can be:
+ - \c true: LU-based matrix inversion.
+ - \c false: SVD-based matrix inversion.
+ **/
+ 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;
+ }
+
+ //! Invert the instance image, viewed as a matrix \newinstance.
+ CImg<Tfloat> get_invert(const bool use_LU=true) const {
+ return CImg<Tfloat>(*this,false).invert(use_LU);
+ }
+
+ //! Compute the Moore-Penrose pseudo-inverse of the instance image, viewed as a matrix.
+ /**
+ **/
+ CImg<T>& pseudoinvert() {
+ return get_pseudoinvert().move_to(*this);
+ }
+
+ //! Compute the Moore-Penrose pseudo-inverse of the instance image, viewed as a matrix \newinstance.
+ CImg<Tfloat> get_pseudoinvert() const {
+ CImg<Tfloat> U, S, V;
+ SVD(U,S,V);
+ const Tfloat tolerance = (sizeof(Tfloat)<=4?5.96e-8f:1.11e-16f)*std::max(_width,_height)*S.max();
+ cimg_forX(V,x) {
+ const Tfloat s = S(x), invs = s>tolerance?1/s:0;
+ cimg_forY(V,y) V(x,y)*=invs;
+ }
+ return V*U.transpose();
+ }
+
+ //! Solve a system of linear equations.
+ /**
+ \param A Matrix of the linear system.
+ \note Solve \c AX=B where \c B=*this.
+ **/
+ template<typename t>
+ CImg<T>& solve(const CImg<t>& A) {
+ if (_depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1)
+ throw CImgArgumentException(_cimg_instance
+ "solve(): Instance and specified matrix (%u,%u,%u,%u,%p) have "
+ "incompatible dimensions.",
+ cimg_instance,
+ A._width,A._height,A._depth,A._spectrum,A._data);
+ typedef _cimg_Ttfloat Ttfloat;
+ if (A._width==A._height) { // Classical linear system
+ if (_width!=1) {
+ CImg<T> res(_width,A._width);
+ cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A));
+ return res.move_to(*this);
+ }
+#ifdef cimg_use_lapack
+ char TRANS = 'N';
+ int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N];
+ Ttfloat
+ *const lapA = new Ttfloat[N*N],
+ *const lapB = new Ttfloat[N],
+ *const WORK = new Ttfloat[LWORK];
+ cimg_forXY(A,k,l) lapA[k*N + l] = (Ttfloat)(A(k,l));
+ cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i));
+ cimg::getrf(N,lapA,IPIV,INFO);
+ if (INFO)
+ cimg::warn(_cimg_instance
+ "solve(): LAPACK library function dgetrf_() returned error code %d.",
+ cimg_instance,
+ INFO);
+
+ if (!INFO) {
+ cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
+ if (INFO)
+ cimg::warn(_cimg_instance
+ "solve(): LAPACK library function dgetrs_() returned error code %d.",
+ cimg_instance,
+ INFO);
+ }
+ if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0);
+ delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
+#else
+ CImg<Ttfloat> lu(A,false);
+ CImg<Ttfloat> indx;
+ bool d;
+ lu._LU(indx,d);
+ _solve(lu,indx);
+#endif
+ } else { // Least-square solution for non-square systems.
+#ifdef cimg_use_lapack
+ if (_width!=1) {
+ CImg<T> res(_width,A._width);
+ cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A));
+ return res.move_to(*this);
+ }
+ char TRANS = 'N';
+ int INFO, N = A._width, M = A._height, LWORK = -1, LDA = M, LDB = M, NRHS = _width;
+ Ttfloat WORK_QUERY;
+ Ttfloat
+ * const lapA = new Ttfloat[M*N],
+ * const lapB = new Ttfloat[M*NRHS];
+ cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, &WORK_QUERY, LWORK, INFO);
+ LWORK = (int) WORK_QUERY;
+ Ttfloat *const WORK = new Ttfloat[LWORK];
+ cimg_forXY(A,k,l) lapA[k*M + l] = (Ttfloat)(A(k,l));
+ cimg_forXY(*this,k,l) lapB[k*M + l] = (Ttfloat)((*this)(k,l));
+ cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, WORK, LWORK, INFO);
+ if (INFO != 0)
+ cimg::warn(_cimg_instance
+ "solve(): LAPACK library function sgels() returned error code %d.",
+ cimg_instance,
+ INFO);
+ assign(NRHS, N);
+ if (!INFO)
+ cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l];
+ else
+ assign(A.get_pseudoinvert()*(*this));
+ delete[] lapA; delete[] lapB; delete[] WORK;
+#else
+ assign(A.get_pseudoinvert()*(*this));
+#endif
+ }
+ return *this;
+ }
+
+ //! Solve a system of linear equations \newinstance.
+ 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 = (int)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 tridiagonal system of linear equations.
+ /**
+ \param A Coefficients of the tridiagonal system.
+ A is a tridiagonal matrix A = [ b0,c0,0,...; a1,b1,c1,0,... ; ... ; ...,0,aN,bN ],
+ stored as a 3 columns matrix
+ \note Solve AX=B where \c B=*this, using the Thomas algorithm.
+ **/
+ template<typename t>
+ CImg<T>& solve_tridiagonal(const CImg<t>& A) {
+ const unsigned int siz = (unsigned int)size();
+ if (A._width!=3 || A._height!=siz)
+ throw CImgArgumentException(_cimg_instance
+ "solve_tridiagonal(): Instance and tridiagonal matrix "
+ "(%u,%u,%u,%u,%p) have incompatible dimensions.",
+ cimg_instance,
+ A._width,A._height,A._depth,A._spectrum,A._data);
+ typedef _cimg_Ttfloat Ttfloat;
+ const Ttfloat epsilon = 1e-4f;
+ CImg<Ttfloat> B = A.get_column(1), V(*this,false);
+ for (int i = 1; i<(int)siz; ++i) {
+ const Ttfloat m = A(0,i)/(B[i - 1]?B[i - 1]:epsilon);
+ B[i] -= m*A(2,i - 1);
+ V[i] -= m*V[i - 1];
+ }
+ (*this)[siz - 1] = (T)(V[siz - 1]/(B[siz - 1]?B[siz - 1]:epsilon));
+ for (int i = (int)siz - 2; i>=0; --i) (*this)[i] = (T)((V[i] - A(2,i)*(*this)[i + 1])/(B[i]?B[i]:epsilon));
+ return *this;
+ }
+
+ //! Solve a tridiagonal system of linear equations \newinstance.
+ template<typename t>
+ CImg<_cimg_Ttfloat> get_solve_tridiagonal(const CImg<t>& A) const {
+ return CImg<_cimg_Ttfloat>(*this,false).solve_tridiagonal(A);
+ }
+
+ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix.
+ /**
+ \param[out] val Vector of the estimated eigenvalues, in decreasing order.
+ \param[out] vec Matrix of the estimated eigenvectors, sorted by columns.
+ **/
+ 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()<(ulongT)_width) val.assign(1,_width);
+ if (vec.size()<(ulongT)_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
+ "eigen(): Complex eigenvalues found.",
+ cimg_instance);
+
+ f = std::sqrt(f);
+ const double
+ l1 = 0.5*(e - f),
+ l2 = 0.5*(e + f),
+ b2 = b*b,
+ norm1 = std::sqrt(cimg::sqr(l2 - a) + b2),
+ norm2 = std::sqrt(cimg::sqr(l1 - a) + b2);
+ val[0] = (t)l2;
+ val[1] = (t)l1;
+ if (norm1>0) { vec(0,0) = (t)(b/norm1); vec(0,1) = (t)((l2 - a)/norm1); } else { vec(0,0) = 1; vec(0,1) = 0; }
+ if (norm2>0) { vec(1,0) = (t)(b/norm2); vec(1,1) = (t)((l1 - a)/norm2); } else { vec(1,0) = 1; vec(1,1) = 0; }
+ } break;
+ default :
+ throw CImgInstanceException(_cimg_instance
+ "eigen(): Eigenvalues computation of general matrices is limited "
+ "to 2x2 matrices.",
+ cimg_instance);
+ }
+ }
+ return *this;
+ }
+
+ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a matrix.
+ /**
+ \return A list of two images <tt>[val; vec]</tt>, whose meaning is similar as in eigen(CImg<t>&,CImg<t>&) const.
+ **/
+ CImgList<Tfloat> get_eigen() const {
+ CImgList<Tfloat> res(2);
+ eigen(res[0],res[1]);
+ return res;
+ }
+
+ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix.
+ /**
+ \param[out] val Vector of the estimated eigenvalues, in decreasing order.
+ \param[out] vec Matrix of the estimated eigenvectors, sorted by columns.
+ **/
+ template<typename t>
+ const CImg<T>& symmetric_eigen(CImg<t>& val, CImg<t>& vec) const {
+ if (is_empty()) { val.assign(); vec.assign(); }
+ else {
+#ifdef cimg_use_lapack
+ char JOB = 'V', UPLO = 'U';
+ int N = _width, LWORK = 4*N, INFO;
+ Tfloat
+ *const lapA = new Tfloat[N*N],
+ *const lapW = new Tfloat[N],
+ *const WORK = new Tfloat[LWORK];
+ cimg_forXY(*this,k,l) lapA[k*N + l] = (Tfloat)((*this)(k,l));
+ cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO);
+ if (INFO)
+ cimg::warn(_cimg_instance
+ "symmetric_eigen(): LAPACK library function dsyev_() returned error code %d.",
+ cimg_instance,
+ INFO);
+
+ val.assign(1,N);
+ vec.assign(N,N);
+ if (!INFO) {
+ cimg_forY(val,i) val(i) = (T)lapW[N - 1 -i];
+ cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N - 1 - k)*N + l]);
+ } else { val.fill(0); vec.fill(0); }
+ delete[] lapA; delete[] lapW; delete[] WORK;
+#else
+ if (_width!=_height || _depth>1 || _spectrum>1)
+ throw CImgInstanceException(_cimg_instance
+ "eigen(): Instance is not a square matrix.",
+ cimg_instance);
+
+ val.assign(1,_width);
+ if (vec._data) vec.assign(_width,_width);
+ if (_width<3) {
+ eigen(val,vec);
+ if (_width==2) { vec[1] = -vec[2]; vec[3] = vec[0]; } // Force orthogonality for 2x2 matrices.
+ return *this;
+ }
+ CImg<t> V(_width,_width);
+ Tfloat M = 0, m = (Tfloat)min_max(M), maxabs = cimg::max((Tfloat)1.0f,cimg::abs(m),cimg::abs(M));
+ (CImg<Tfloat>(*this,false)/=maxabs).SVD(vec,val,V,false);
+ if (maxabs!=1) val*=maxabs;
+
+ 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;
+ }
+
+ //! Compute eigenvalues and eigenvectors of the instance image, viewed as a symmetric matrix.
+ /**
+ \return A list of two images <tt>[val; vec]</tt>, whose meaning are similar as in
+ symmetric_eigen(CImg<t>&,CImg<t>&) const.
+ **/
+ CImgList<Tfloat> get_symmetric_eigen() const {
+ CImgList<Tfloat> res(2);
+ symmetric_eigen(res[0],res[1]);
+ return res;
+ }
+
+ //! Sort pixel values and get sorting permutations.
+ /**
+ \param[out] permutations Permutation map used for the sorting.
+ \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way.
+ **/
+ template<typename t>
+ CImg<T>& sort(CImg<t>& permutations, const bool is_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,is_increasing,true);
+ }
+
+ //! Sort pixel values and get sorting permutations \newinstance.
+ template<typename t>
+ CImg<T> get_sort(CImg<t>& permutations, const bool is_increasing=true) const {
+ return (+*this).sort(permutations,is_increasing);
+ }
+
+ //! Sort pixel values.
+ /**
+ \param is_increasing Tells if pixel values are sorted in an increasing (\c true) or decreasing (\c false) way.
+ \param axis Tells if the value sorting must be done along a specific axis. Can be:
+ - \c 0: All pixel values are sorted, independently on their initial position.
+ - \c 'x': Image columns are sorted, according to the first value in each column.
+ - \c 'y': Image rows are sorted, according to the first value in each row.
+ - \c 'z': Image slices are sorted, according to the first value in each slice.
+ - \c 'c': Image channels are sorted, according to the first value in each channel.
+ **/
+ CImg<T>& sort(const bool is_increasing=true, const char axis=0) {
+ if (is_empty()) return *this;
+ CImg<uintT> perm;
+ switch (cimg::lowercase(axis)) {
+ case 0 :
+ _quicksort(0,size() - 1,perm,is_increasing,false);
+ break;
+ case 'x' : {
+ perm.assign(_width);
+ get_crop(0,0,0,0,_width - 1,0,0,0).sort(perm,is_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,is_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,is_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,is_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;
+ }
+
+ //! Sort pixel values \newinstance.
+ CImg<T> get_sort(const bool is_increasing=true, const char axis=0) const {
+ return (+*this).sort(is_increasing,axis);
+ }
+
+ template<typename t>
+ CImg<T>& _quicksort(const long indm, const long indM, CImg<t>& permutations,
+ const bool is_increasing, const bool is_permutations) {
+ if (indm<indM) {
+ const long mid = (indm + indM)/2;
+ if (is_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];
+ long i = indm, j = indM;
+ if (is_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,is_increasing,is_permutations);
+ if (i<indM) _quicksort(i,indM,permutations,is_increasing,is_permutations);
+ }
+ }
+ return *this;
+ }
+
+ //! Compute the SVD of the instance image, viewed as a general matrix.
+ /**
+ Compute the SVD decomposition \c *this=U*S*V' where \c U and \c V are orthogonal matrices
+ and \c S is a diagonal matrix. \c V' denotes the matrix transpose of \c V.
+ \param[out] U First matrix of the SVD product.
+ \param[out] S Coefficients of the second (diagonal) matrix of the SVD product.
+ These coefficients are stored as a vector.
+ \param[out] V Third matrix of the SVD product.
+ \param sorting Tells if the diagonal coefficients are sorted (in decreasing order).
+ \param max_iteration Maximum number of iterations considered for the algorithm convergence.
+ \param lambda Epsilon used for the algorithm convergence.
+ \note The instance matrix can be computed from \c U,\c S and \c V by
+ \code
+ const CImg<> A; // Input matrix (assumed to contain some values).
+ CImg<> U,S,V;
+ A.SVD(U,S,V)
+ \endcode
+ **/
+ 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 = std::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)std::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 = std::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 = cimg::_hypot(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))/std::max((t)1e-25,2*h*y);
+ g = cimg::_hypot(f,(t)1);
+ f = ((x - z)*(x + z)+h*((y/(f + (f>=0?g:-g))) - h))/std::max((t)1e-25,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 = cimg::_hypot(f,h);
+ rv1[j] = z; c = f/std::max((t)1e-25,z); s = h/std::max((t)1e-25,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 = cimg::_hypot(f,h); S[j] = z;
+ if (z) { z = 1/std::max((t)1e-25,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;
+ }
+
+ //! Compute the SVD of the instance image, viewed as a general matrix.
+ /**
+ \return A list of three images <tt>[U; S; V]</tt>, whose meaning is similar as in
+ SVD(CImg<t>&,CImg<t>&,CImg<t>&,bool,unsigned int,float) const.
+ **/
+ 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;
+ }
+
+ // [internal] Compute the LU decomposition of a permuted matrix.
+ 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_node 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_node) {
+ 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_node.assign(1,nb_nodes,1,1,(t)-1);
+ previous_node(starting_node) = (t)starting_node;
+ CImg<uintT> Q(nb_nodes);
+ cimg_forX(Q,u) Q(u) = (unsigned int)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_node(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.
+ /**
+ \param starting_node Indice of the starting node.
+ \param ending_node Indice of the ending node.
+ \param previous_node 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.
+ \note image instance corresponds to the adjacency matrix of the graph.
+ **/
+ template<typename t>
+ CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node,
+ CImg<t>& previous_node) {
+ return get_dijkstra(starting_node,ending_node,previous_node).move_to(*this);
+ }
+
+ //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance.
+ template<typename t>
+ CImg<T> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node,
+ CImg<t>& previous_node) 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_node);
+ }
+
+ //! 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);
+ }
+
+ //! Return minimal path in a graph, using the Dijkstra algorithm \newinstance.
+ 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 an image containing the ascii codes of the specified string.
+ /**
+ \param str input C-string to encode as an image.
+ \param is_last_zero Tells if the ending \c '0' character appear in the resulting image.
+ **/
+ static CImg<T> string(const char *const str, const bool is_last_zero=true, const bool is_shared=false) {
+ if (!str) return CImg<T>();
+ return CImg<T>(str,(unsigned int)std::strlen(str) + (is_last_zero?1:0),1,1,1,is_shared);
+ }
+
+ //! Return a \c 1x1 image containing specified value.
+ /**
+ \param a0 First vector value.
+ **/
+ static CImg<T> vector(const T& a0) {
+ CImg<T> r(1,1);
+ r[0] = a0;
+ return r;
+ }
+
+ //! Return a \c 1x2 image containing specified values.
+ /**
+ \param a0 First vector value.
+ \param a1 Second vector value.
+ **/
+ static CImg<T> vector(const T& a0, const T& a1) {
+ CImg<T> r(1,2); T *ptr = r._data;
+ *(ptr++) = a0; *(ptr++) = a1;
+ return r;
+ }
+
+ //! Return a \c 1x3 image containing specified values.
+ /**
+ \param a0 First vector value.
+ \param a1 Second vector value.
+ \param a2 Third vector value.
+ **/
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2) {
+ CImg<T> r(1,3); T *ptr = r._data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
+ return r;
+ }
+
+ //! Return a \c 1x4 image containing specified values.
+ /**
+ \param a0 First vector value.
+ \param a1 Second vector value.
+ \param a2 Third vector value.
+ \param a3 Fourth vector value.
+ **/
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3) {
+ CImg<T> r(1,4); T *ptr = r._data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
+ return r;
+ }
+
+ //! Return a \c 1x5 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
+ CImg<T> r(1,5); T *ptr = r._data;
+ *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
+ return r;
+ }
+
+ //! Return a \c 1x6 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) {
+ 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 \c 1x7 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6) {
+ CImg<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 \c 1x8 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7) {
+ CImg<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 \c 1x9 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8) {
+ CImg<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 \c 1x10 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9) {
+ CImg<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 \c 1x11 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10) {
+ CImg<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 \c 1x12 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11) {
+ CImg<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 \c 1x13 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12) {
+ CImg<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 \c 1x14 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13) {
+ CImg<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 \c 1x15 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13, const T& a14) {
+ CImg<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 \c 1x16 image containing specified values.
+ static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13, const T& a14, const T& a15) {
+ CImg<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 matrix containing specified coefficients.
+ /**
+ \param a0 First matrix value.
+ \note Equivalent to vector(const T&).
+ **/
+ static CImg<T> matrix(const T& a0) {
+ return vector(a0);
+ }
+
+ //! Return a 2x2 matrix containing specified coefficients.
+ /**
+ \param a0 First matrix value.
+ \param a1 Second matrix value.
+ \param a2 Third matrix value.
+ \param a3 Fourth matrix value.
+ **/
+ static CImg<T> matrix(const T& a0, const T& a1,
+ const T& a2, const T& a3) {
+ CImg<T> r(2,2); T *ptr = r._data;
+ *(ptr++) = a0; *(ptr++) = a1;
+ *(ptr++) = a2; *(ptr++) = a3;
+ return r;
+ }
+
+ //! Return a 3x3 matrix containing specified coefficients.
+ /**
+ \param a0 First matrix value.
+ \param a1 Second matrix value.
+ \param a2 Third matrix value.
+ \param a3 Fourth matrix value.
+ \param a4 Fifth matrix value.
+ \param a5 Sixth matrix value.
+ \param a6 Seventh matrix value.
+ \param a7 Eighth matrix value.
+ \param a8 Nineth matrix value.
+ **/
+ static CImg<T> matrix(const T& a0, const T& a1, const T& a2,
+ const T& a3, const T& a4, const T& a5,
+ const T& a6, const T& a7, const T& a8) {
+ CImg<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 matrix containing specified coefficients.
+ static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3,
+ const T& a4, const T& a5, const T& a6, const T& a7,
+ const T& a8, const T& a9, const T& a10, const T& a11,
+ const T& a12, const T& a13, const T& a14, const T& a15) {
+ CImg<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 matrix containing specified coefficients.
+ static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4,
+ const T& a5, const T& a6, const T& a7, const T& a8, const T& a9,
+ const T& a10, const T& a11, const T& a12, const T& a13, const T& a14,
+ const T& a15, const T& a16, const T& a17, const T& a18, const T& a19,
+ const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) {
+ CImg<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 containing specified coefficients.
+ /**
+ \param a0 First matrix value.
+ \note Equivalent to vector(const T&).
+ **/
+ static CImg<T> tensor(const T& a0) {
+ return matrix(a0);
+ }
+
+ //! Return a 2x2 symmetric matrix tensor containing specified coefficients.
+ static CImg<T> tensor(const T& a0, const T& a1, const T& a2) {
+ return matrix(a0,a1,a1,a2);
+ }
+
+ //! Return a 3x3 symmetric matrix containing specified coefficients.
+ static CImg<T> tensor(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) {
+ return matrix(a0,a1,a2,a1,a3,a4,a2,a4,a5);
+ }
+
+ //! Return a 1x1 diagonal matrix containing specified coefficients.
+ static CImg<T> diagonal(const T& a0) {
+ return matrix(a0);
+ }
+
+ //! Return a 2x2 diagonal matrix containing specified coefficients.
+ static CImg<T> diagonal(const T& a0, const T& a1) {
+ return matrix(a0,0,0,a1);
+ }
+
+ //! Return a 3x3 diagonal matrix containing 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 containing 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 containing 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.
+ /**
+ \param N Dimension of the 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.
+ /**
+ \param N Size of the resulting vector.
+ \param a0 Starting value of the sequence.
+ \param a1 Ending value of the sequence.
+ **/
+ 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 from an { axis + angle } or a quaternion.
+ /**
+ \param x X-coordinate of the rotation axis, or first quaternion coordinate.
+ \param y Y-coordinate of the rotation axis, or second quaternion coordinate.
+ \param z Z-coordinate of the rotation axis, or third quaternion coordinate.
+ \param w Angle of the rotation axis (in degree), or fourth quaternion coordinate.
+ \param is_quaternion Tell is the four arguments denotes a set { axis + angle } or a quaternion (x,y,z,w).
+ **/
+ static CImg<T> rotation_matrix(const float x, const float y, const float z, const float w,
+ const bool is_quaternion=false) {
+ double X, Y, Z, W, N;
+ if (is_quaternion) {
+ N = std::sqrt((double)x*x + (double)y*y + (double)z*z + (double)w*w);
+ if (N>0) { X = x/N; Y = y/N; Z = z/N; W = w/N; }
+ else { X = Y = Z = 0; W = 1; }
+ return CImg<T>::matrix((T)(X*X + Y*Y - Z*Z - W*W),(T)(2*Y*Z - 2*X*W),(T)(2*X*Z + 2*Y*W),
+ (T)(2*X*W + 2*Y*Z),(T)(X*X - Y*Y + Z*Z - W*W),(T)(2*Z*W - 2*X*Y),
+ (T)(2*Y*W - 2*X*Z),(T)(2*X*Y + 2*Z*W),(T)(X*X - Y*Y - Z*Z + W*W));
+ }
+ N = cimg::hypot((double)x,(double)y,(double)z);
+ if (N>0) { X = x/N; Y = y/N; Z = z/N; }
+ else { X = Y = 0; Z = 1; }
+ const double ang = w*cimg::PI/180, c = std::cos(ang), omc = 1 - c, s = std::sin(ang);
+ return CImg<T>::matrix((T)(X*X*omc + c),(T)(X*Y*omc - Z*s),(T)(X*Z*omc + Y*s),
+ (T)(X*Y*omc + Z*s),(T)(Y*Y*omc + c),(T)(Y*Z*omc - X*s),
+ (T)(X*Z*omc - Y*s),(T)(Y*Z*omc + X*s),(T)(Z*Z*omc + c));
+ }
+
+ //@}
+ //-----------------------------------
+ //
+ //! \name Value Manipulation
+ //@{
+ //-----------------------------------
+
+ //! Fill all pixel values with specified value.
+ /**
+ \param val Fill value.
+ **/
+ 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,sizeof(T)*size());
+ return *this;
+ }
+
+ //! Fill all pixel values with specified value \newinstance.
+ CImg<T> get_fill(const T& val) const {
+ return CImg<T>(_width,_height,_depth,_spectrum).fill(val);
+ }
+
+ //! Fill sequentially all pixel values with specified values.
+ /**
+ \param val0 First fill value.
+ \param val1 Second fill value.
+ **/
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 all pixel values with specified values \overloading.
+ 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;
+ }
+
+ //! Fill sequentially all pixel values with specified values \newinstance.
+ 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 sequentially pixel values according to a given expression.
+ /**
+ \param expression C-string describing a math formula, or a sequence of values.
+ \param repeat_values In case a list of values is provided, tells if this list must be repeated for the filling.
+ \param list_inputs In case of a mathematical expression, attach a list of images to the specified expression.
+ \param list_outputs In case of a mathematical expression, attach a list of images to the specified expression.
+ **/
+ CImg<T>& fill(const char *const expression, const bool repeat_values, const bool allow_formula=true,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) {
+ return _fill(expression,repeat_values,allow_formula,list_inputs,list_outputs,"fill",0);
+ }
+
+ CImg<T>& _fill(const char *const expression, const bool repeat_values, bool allow_formula,
+ const CImgList<T> *const list_inputs, CImgList<T> *const list_outputs,
+ const char *const calling_function, const CImg<T> *provides_copy) {
+ if (is_empty() || !expression || !*expression) return *this;
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ CImg<charT> is_error;
+ bool is_value_sequence = false;
+
+ if (allow_formula) {
+
+ // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser.
+ double value;
+ char sep;
+ const int err = cimg_sscanf(expression,"%lf %c",&value,&sep);
+ if (err==1 || (err==2 && sep==',')) {
+ if (err==1) return fill((T)value);
+ else is_value_sequence = true;
+ }
+
+ // Try to fill values according to a formula.
+ cimg_abort_init;
+ if (!is_value_sequence) try {
+ CImg<T> base = provides_copy?provides_copy->get_shared():get_shared();
+ _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' ||
+ *expression=='*' || *expression==':'),
+ calling_function,base,this,list_inputs,list_outputs,true);
+ if (!provides_copy && expression && *expression!='>' && *expression!='<' && *expression!=':' &&
+ mp.need_input_copy)
+ base.assign().assign(*this); // Needs input copy
+
+ bool do_in_parallel = false;
+#ifdef cimg_use_openmp
+ cimg_openmp_if(*expression=='*' || *expression==':' ||
+ (mp.is_parallelizable && _width>=320 && _height*_depth*_spectrum>=2))
+ do_in_parallel = true;
+#endif
+ if (mp.result_dim) { // Vector-valued expression
+ const unsigned int N = std::min(mp.result_dim,_spectrum);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ T *ptrd = *expression=='<'?_data + _width*_height*_depth - 1:_data;
+ if (*expression=='<') {
+ CImg<doubleT> res(1,mp.result_dim);
+ cimg_rofYZ(*this,y,z) {
+ cimg_abort_test();
+ cimg_rofX(*this,x) {
+ mp(x,y,z,0,res._data);
+ const double *ptrs = res._data;
+ T *_ptrd = ptrd--; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; }
+ }
+ }
+ } else if (*expression=='>' || !do_in_parallel) {
+ CImg<doubleT> res(1,mp.result_dim);
+ cimg_forYZ(*this,y,z) {
+ cimg_abort_test();
+ cimg_forX(*this,x) {
+ mp(x,y,z,0,res._data);
+ const double *ptrs = res._data;
+ T *_ptrd = ptrd++; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; }
+ }
+ }
+ } else {
+#ifdef cimg_use_openmp
+ cimg_pragma_openmp(parallel)
+ {
+ _cimg_math_parser
+ _mp = omp_get_thread_num()?mp:_cimg_math_parser(),
+ &lmp = omp_get_thread_num()?_mp:mp;
+ lmp.is_fill = true;
+ cimg_pragma_openmp(for collapse(2))
+ cimg_forYZ(*this,y,z) cimg_abort_try {
+ cimg_abort_test();
+ CImg<doubleT> res(1,lmp.result_dim);
+ T *ptrd = data(0,y,z,0);
+ cimg_forX(*this,x) {
+ lmp(x,y,z,0,res._data);
+ const double *ptrs = res._data;
+ T *_ptrd = ptrd++; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; }
+ }
+ } cimg_abort_catch() cimg_abort_catch_fill()
+ }
+#endif
+ }
+
+ } else { // Scalar-valued expression
+ T *ptrd = *expression=='<'?end() - 1:_data;
+ if (*expression=='<')
+ cimg_rofYZC(*this,y,z,c) { cimg_abort_test(); cimg_rofX(*this,x) *(ptrd--) = (T)mp(x,y,z,c); }
+ else if (*expression=='>' || !do_in_parallel)
+ cimg_forYZC(*this,y,z,c) { cimg_abort_test(); cimg_forX(*this,x) *(ptrd++) = (T)mp(x,y,z,c); }
+ else {
+#ifdef cimg_use_openmp
+ cimg_pragma_openmp(parallel)
+ {
+ _cimg_math_parser
+ _mp = omp_get_thread_num()?mp:_cimg_math_parser(),
+ &lmp = omp_get_thread_num()?_mp:mp;
+ lmp.is_fill = true;
+ cimg_pragma_openmp(for collapse(3))
+ cimg_forYZC(*this,y,z,c) cimg_abort_try {
+ cimg_abort_test();
+ T *ptrd = data(0,y,z,c);
+ cimg_forX(*this,x) *ptrd++ = (T)lmp(x,y,z,c);
+ } cimg_abort_catch() cimg_abort_catch_fill()
+ }
+#endif
+ }
+ }
+ mp.end();
+ } catch (CImgException& e) { CImg<charT>::string(e._message).move_to(is_error); }
+ }
+
+ // Try to fill values according to a value sequence.
+ if (!allow_formula || is_value_sequence || is_error) {
+ CImg<charT> item(256);
+ char sep = 0;
+ const char *nexpression = expression;
+ ulongT nb = 0;
+ const ulongT siz = size();
+ T *ptrd = _data;
+ for (double val = 0; *nexpression && nb<siz; ++nb) {
+ sep = 0;
+ const int err = cimg_sscanf(nexpression,"%255[ \n\t0-9.eEinfa+-]%c",item._data,&sep);
+ if (err>0 && cimg_sscanf(item,"%lf",&val)==1 && (sep==',' || sep==';' || err==1)) {
+ nexpression+=std::strlen(item) + (err>1);
+ *(ptrd++) = (T)val;
+ } else break;
+ }
+ cimg::exception_mode(omode);
+ if (nb<siz && (sep || *nexpression)) {
+ if (is_error) throw CImgArgumentException("%s",is_error._data);
+ else throw CImgArgumentException(_cimg_instance
+ "%s(): Invalid sequence of filling values '%s'.",
+ cimg_instance,calling_function,expression);
+ }
+ if (repeat_values && nb && nb<siz)
+ for (T *ptrs = _data, *const ptre = _data + siz; ptrd<ptre; ++ptrs) *(ptrd++) = *ptrs;
+ }
+
+ cimg::exception_mode(omode);
+ cimg_abort_test();
+ return *this;
+ }
+
+ //! Fill sequentially pixel values according to a given expression \newinstance.
+ CImg<T> get_fill(const char *const expression, const bool repeat_values, const bool allow_formula=true,
+ const CImgList<T> *const list_inputs=0, CImgList<T> *const list_outputs=0) const {
+ return (+*this).fill(expression,repeat_values,allow_formula,list_inputs,list_outputs);
+ }
+
+ //! Fill sequentially pixel values according to the values found in another image.
+ /**
+ \param values Image containing the values used for the filling.
+ \param repeat_values In case there are less values than necessary in \c values, tells if these values must be
+ repeated for the filling.
+ **/
+ 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;
+ }
+
+ //! Fill sequentially pixel values according to the values found in another image \newinstance.
+ 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 pixel values along the X-axis at a specified pixel position.
+ /**
+ \param y Y-coordinate of the filled column.
+ \param z Z-coordinate of the filled column.
+ \param c C-coordinate of the filled column.
+ \param a0 First fill value.
+ **/
+ 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;
+ }
+
+ //! Fill pixel values along the X-axis at a specified pixel position \overloading.
+ 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 pixel values along the Y-axis at a specified pixel position.
+ /**
+ \param x X-coordinate of the filled row.
+ \param z Z-coordinate of the filled row.
+ \param c C-coordinate of the filled row.
+ \param a0 First fill value.
+ **/
+ 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;
+ }
+
+ //! Fill pixel values along the Y-axis at a specified pixel position \overloading.
+ 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 pixel values along the Z-axis at a specified pixel position.
+ /**
+ \param x X-coordinate of the filled slice.
+ \param y Y-coordinate of the filled slice.
+ \param c C-coordinate of the filled slice.
+ \param a0 First fill value.
+ **/
+ CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const int a0, ...) {
+ const ulongT wh = (ulongT)_width*_height;
+ if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,int);
+ return *this;
+ }
+
+ //! Fill pixel values along the Z-axis at a specified pixel position \overloading.
+ CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int c, const double a0, ...) {
+ const ulongT wh = (ulongT)_width*_height;
+ if (x<_width && y<_height && c<_spectrum) _cimg_fill1(x,y,0,c,wh,_depth,double);
+ return *this;
+ }
+
+ //! Fill pixel values along the C-axis at a specified pixel position.
+ /**
+ \param x X-coordinate of the filled channel.
+ \param y Y-coordinate of the filled channel.
+ \param z Z-coordinate of the filled channel.
+ \param a0 First filling value.
+ **/
+ CImg<T>& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int);
+ return *this;
+ }
+
+ //! Fill pixel values along the C-axis at a specified pixel position \overloading.
+ CImg<T>& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) {
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,double);
+ return *this;
+ }
+
+ //! Discard specified sequence of values in the image buffer, along a specific axis.
+ /**
+ \param values Sequence of values to discard.
+ \param axis Axis along which the values are discarded. If set to \c 0 (default value)
+ the method does it for all the buffer values and returns a one-column vector.
+ \note Discarded values will change the image geometry, so the resulting image
+ is returned as a one-column vector.
+ **/
+ template<typename t>
+ CImg<T>& discard(const CImg<t>& values, const char axis=0) {
+ if (is_empty() || !values) return *this;
+ return get_discard(values,axis).move_to(*this);
+ }
+
+ template<typename t>
+ CImg<T> get_discard(const CImg<t>& values, const char axis=0) const {
+ CImg<T> res;
+ if (!values) return +*this;
+ if (is_empty()) return res;
+ const ulongT vsiz = values.size();
+ const char _axis = cimg::lowercase(axis);
+ ulongT j = 0;
+ unsigned int k = 0;
+ int i0 = 0;
+ res.assign(width(),height(),depth(),spectrum());
+ switch (_axis) {
+ case 'x' : {
+ cimg_forX(*this,i) {
+ if ((*this)(i)!=(T)values[j]) {
+ if (j) --i;
+ res.draw_image(k,get_columns(i0,i));
+ k+=i - i0 + 1; i0 = i + 1; j = 0;
+ } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } }
+ }
+ if (i0<width()) { res.draw_image(k,get_columns(i0,width() - 1)); k+=width() - i0; }
+ res.resize(k,-100,-100,-100,0);
+ } break;
+ case 'y' : {
+ cimg_forY(*this,i) {
+ if ((*this)(0,i)!=(T)values[j]) {
+ if (j) --i;
+ res.draw_image(0,k,get_rows(i0,i));
+ k+=i - i0 + 1; i0 = i + 1; j = 0;
+ } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } }
+ }
+ if (i0<height()) { res.draw_image(0,k,get_rows(i0,height() - 1)); k+=height() - i0; }
+ res.resize(-100,k,-100,-100,0);
+ } break;
+ case 'z' : {
+ cimg_forZ(*this,i) {
+ if ((*this)(0,0,i)!=(T)values[j]) {
+ if (j) --i;
+ res.draw_image(0,0,k,get_slices(i0,i));
+ k+=i - i0 + 1; i0 = i + 1; j = 0;
+ } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } }
+ }
+ if (i0<depth()) { res.draw_image(0,0,k,get_slices(i0,height() - 1)); k+=depth() - i0; }
+ res.resize(-100,-100,k,-100,0);
+ } break;
+ case 'c' : {
+ cimg_forC(*this,i) {
+ if ((*this)(0,0,0,i)!=(T)values[j]) {
+ if (j) --i;
+ res.draw_image(0,0,0,k,get_channels(i0,i));
+ k+=i - i0 + 1; i0 = i + 1; j = 0;
+ } else { ++j; if (j>=vsiz) { j = 0; i0 = i + 1; } }
+ }
+ if (i0<spectrum()) { res.draw_image(0,0,k,get_channels(i0,height() - 1)); k+=spectrum() - i0; }
+ res.resize(-100,-100,-100,k,0);
+ } break;
+ default : {
+ res.unroll('y');
+ cimg_foroff(*this,i) {
+ if ((*this)[i]!=(T)values[j]) {
+ if (j) --i;
+ std::memcpy(res._data + k,_data + i0,(i - i0 + 1)*sizeof(T));
+ k+=i - i0 + 1; i0 = (int)i + 1; j = 0;
+ } else { ++j; if (j>=vsiz) { j = 0; i0 = (int)i + 1; }}
+ }
+ const ulongT siz = size();
+ if ((ulongT)i0<siz) { std::memcpy(res._data + k,_data + i0,(siz - i0)*sizeof(T)); k+=siz - i0; }
+ res.resize(1,k,1,1,0);
+ }
+ }
+ return res;
+ }
+
+ //! Discard neighboring duplicates in the image buffer, along the specified axis.
+ CImg<T>& discard(const char axis=0) {
+ return get_discard(axis).move_to(*this);
+ }
+
+ //! Discard neighboring duplicates in the image buffer, along the specified axis \newinstance.
+ CImg<T> get_discard(const char axis=0) const {
+ CImg<T> res;
+ if (is_empty()) return res;
+ const char _axis = cimg::lowercase(axis);
+ T current = *_data?(T)0:(T)1;
+ int j = 0;
+ res.assign(width(),height(),depth(),spectrum());
+ switch (_axis) {
+ case 'x' : {
+ cimg_forX(*this,i)
+ if ((*this)(i)!=current) { res.draw_image(j++,get_column(i)); current = (*this)(i); }
+ res.resize(j,-100,-100,-100,0);
+ } break;
+ case 'y' : {
+ cimg_forY(*this,i)
+ if ((*this)(0,i)!=current) { res.draw_image(0,j++,get_row(i)); current = (*this)(0,i); }
+ res.resize(-100,j,-100,-100,0);
+ } break;
+ case 'z' : {
+ cimg_forZ(*this,i)
+ if ((*this)(0,0,i)!=current) { res.draw_image(0,0,j++,get_slice(i)); current = (*this)(0,0,i); }
+ res.resize(-100,-100,j,-100,0);
+ } break;
+ case 'c' : {
+ cimg_forC(*this,i)
+ if ((*this)(0,0,0,i)!=current) { res.draw_image(0,0,0,j++,get_channel(i)); current = (*this)(0,0,0,i); }
+ res.resize(-100,-100,-100,j,0);
+ } break;
+ default : {
+ res.unroll('y');
+ cimg_foroff(*this,i)
+ if ((*this)[i]!=current) res[j++] = current = (*this)[i];
+ res.resize(-100,j,-100,-100,0);
+ }
+ }
+ return res;
+ }
+
+ //! Invert endianness of all pixel values.
+ /**
+ **/
+ CImg<T>& invert_endianness() {
+ cimg::invert_endianness(_data,size());
+ return *this;
+ }
+
+ //! Invert endianness of all pixel values \newinstance.
+ CImg<T> get_invert_endianness() const {
+ return (+*this).invert_endianness();
+ }
+
+ //! Fill image with random values in specified range.
+ /**
+ \param val_min Minimal authorized random value.
+ \param val_max Maximal authorized random value.
+ \note Random variables are uniformely distributed in [val_min,val_max].
+ **/
+ CImg<T>& rand(const T& val_min, const T& val_max) {
+ const float delta = (float)val_max - (float)val_min + (cimg::type<T>::is_float()?0:1);
+ if (cimg::type<T>::is_float()) cimg_for(*this,ptrd,T) *ptrd = (T)(val_min + cimg::rand()*delta);
+ else cimg_for(*this,ptrd,T) *ptrd = std::min(val_max,(T)(val_min + cimg::rand()*delta));
+ return *this;
+ }
+
+ //! Fill image with random values in specified range \newinstance.
+ CImg<T> get_rand(const T& val_min, const T& val_max) const {
+ return (+*this).rand(val_min,val_max);
+ }
+
+ //! Round pixel values.
+ /**
+ \param y Rounding precision.
+ \param rounding_type Rounding type. Can be:
+ - \c -1: Backward.
+ - \c 0: Nearest.
+ - \c 1: Forward.
+ **/
+ CImg<T>& round(const double y=1, const int rounding_type=0) {
+ if (y>0)
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=8192))
+ cimg_rof(*this,ptrd,T) *ptrd = cimg::round(*ptrd,y,rounding_type);
+ return *this;
+ }
+
+ //! Round pixel values \newinstance.
+ CImg<T> get_round(const double y=1, const unsigned int rounding_type=0) const {
+ return (+*this).round(y,rounding_type);
+ }
+
+ //! Add random noise to pixel values.
+ /**
+ \param sigma Amplitude of the random additive noise. If \p sigma<0, it stands for a percentage of the
+ global value range.
+ \param noise_type Type of additive noise (can be \p 0=gaussian, \p 1=uniform, \p 2=Salt and Pepper,
+ \p 3=Poisson or \p 4=Rician).
+ \return A reference to the modified image instance.
+ \note
+ - For Poisson noise (\p noise_type=3), parameter \p sigma is ignored, as Poisson noise only depends on
+ the image value itself.
+ - Function \p CImg<T>::get_noise() is also defined. It returns a non-shared modified copy of the image instance.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_noise(40);
+ (img,res.normalize(0,255)).display();
+ \endcode
+ \image html ref_noise.jpg
+ **/
+ CImg<T>& noise(const double sigma, const unsigned int noise_type=0) {
+ if (is_empty()) return *this;
+ const Tfloat vmin = (Tfloat)cimg::type<T>::min(), vmax = (Tfloat)cimg::type<T>::max();
+ Tfloat nsigma = (Tfloat)sigma, m = 0, M = 0;
+ if (nsigma==0 && noise_type!=3) return *this;
+ if (nsigma<0 || noise_type==2) m = (Tfloat)min_max(M);
+ if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.0);
+ switch (noise_type) {
+ case 0 : { // Gaussian noise
+ cimg_rof(*this,ptrd,T) {
+ Tfloat val = (Tfloat)(*ptrd + nsigma*cimg::grand());
+ if (val>vmax) val = vmax;
+ if (val<vmin) val = vmin;
+ *ptrd = (T)val;
+ }
+ } break;
+ case 1 : { // Uniform noise
+ cimg_rof(*this,ptrd,T) {
+ Tfloat val = (Tfloat)(*ptrd + nsigma*cimg::rand(-1,1));
+ if (val>vmax) val = vmax;
+ if (val<vmin) val = vmin;
+ *ptrd = (T)val;
+ }
+ } break;
+ case 2 : { // Salt & Pepper noise
+ if (nsigma<0) nsigma = -nsigma;
+ if (M==m) { m = 0; M = cimg::type<T>::is_float()?(Tfloat)1:(Tfloat)cimg::type<T>::max(); }
+ cimg_rof(*this,ptrd,T) if (cimg::rand(100)<nsigma) *ptrd = (T)(cimg::rand()<0.5?M:m);
+ } break;
+ case 3 : { // Poisson Noise
+ cimg_rof(*this,ptrd,T) *ptrd = (T)cimg::prand(*ptrd);
+ } break;
+ case 4 : { // Rice noise
+ const Tfloat sqrt2 = (Tfloat)std::sqrt(2.0);
+ cimg_rof(*this,ptrd,T) {
+ const Tfloat
+ val0 = (Tfloat)*ptrd/sqrt2,
+ re = (Tfloat)(val0 + nsigma*cimg::grand()),
+ im = (Tfloat)(val0 + nsigma*cimg::grand());
+ Tfloat val = cimg::hypot(re,im);
+ if (val>vmax) val = vmax;
+ if (val<vmin) val = vmin;
+ *ptrd = (T)val;
+ }
+ } break;
+ default :
+ throw CImgArgumentException(_cimg_instance
+ "noise(): Invalid specified noise type %d "
+ "(should be { 0=gaussian | 1=uniform | 2=salt&Pepper | 3=poisson }).",
+ cimg_instance,
+ noise_type);
+ }
+ return *this;
+ }
+
+ //! Add random noise to pixel values \newinstance.
+ CImg<T> get_noise(const double sigma, const unsigned int noise_type=0) const {
+ return (+*this).noise(sigma,noise_type);
+ }
+
+ //! Linearly normalize pixel values.
+ /**
+ \param min_value Minimum desired value of the resulting image.
+ \param max_value Maximum desired value of the resulting image.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_normalize(160,220);
+ (img,res).display();
+ \endcode
+ \image html ref_normalize2.jpg
+ **/
+ CImg<T>& normalize(const T& min_value, const T& max_value) {
+ if (is_empty()) return *this;
+ const T a = min_value<max_value?min_value:max_value, b = min_value<max_value?max_value:min_value;
+ T m, M = max_min(m);
+ const Tfloat fm = (Tfloat)m, fM = (Tfloat)M;
+ if (m==M) return fill(min_value);
+ if (m!=a || M!=b) cimg_rof(*this,ptrd,T) *ptrd = (T)((*ptrd - fm)/(fM - fm)*(b - a) + a);
+ return *this;
+ }
+
+ //! Linearly normalize pixel values \newinstance.
+ CImg<Tfloat> get_normalize(const T& min_value, const T& max_value) const {
+ return CImg<Tfloat>(*this,false).normalize((Tfloat)min_value,(Tfloat)max_value);
+ }
+
+ //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm.
+ /**
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_normalize();
+ (img,res.normalize(0,255)).display();
+ \endcode
+ \image html ref_normalize.jpg
+ **/
+ CImg<T>& normalize() {
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16))
+ cimg_forYZ(*this,y,z) {
+ T *ptrd = data(0,y,z,0);
+ cimg_forX(*this,x) {
+ 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;
+ }
+
+ //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm \newinstance.
+ CImg<Tfloat> get_normalize() const {
+ return CImg<Tfloat>(*this,false).normalize();
+ }
+
+ //! Compute Lp-norm of each multi-valued pixel of the image instance.
+ /**
+ \param norm_type Type of computed vector norm (can be \p -1=Linf, or \p>=0).
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_norm();
+ (img,res.normalize(0,255)).display();
+ \endcode
+ \image html ref_norm.jpg
+ **/
+ CImg<T>& norm(const int norm_type=2) {
+ if (_spectrum==1 && norm_type) return abs();
+ return get_norm(norm_type).move_to(*this);
+ }
+
+ //! Compute L2-norm of each multi-valued pixel of the image instance \newinstance.
+ CImg<Tfloat> get_norm(const int norm_type=2) const {
+ if (is_empty()) return *this;
+ if (_spectrum==1 && norm_type) return get_abs();
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ CImg<Tfloat> res(_width,_height,_depth);
+ switch (norm_type) {
+ case -1 : { // Linf-norm.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16))
+ cimg_forYZ(*this,y,z) {
+ const ulongT off = (ulongT)offset(0,y,z);
+ const T *ptrs = _data + off;
+ Tfloat *ptrd = res._data + off;
+ cimg_forX(*this,x) {
+ 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 0 : { // L0-norm.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16))
+ cimg_forYZ(*this,y,z) {
+ const ulongT off = (ulongT)offset(0,y,z);
+ const T *ptrs = _data + off;
+ Tfloat *ptrd = res._data + off;
+ cimg_forX(*this,x) {
+ unsigned int n = 0;
+ const T *_ptrs = ptrs++;
+ cimg_forC(*this,c) { n+=*_ptrs==0?0:1; _ptrs+=whd; }
+ *(ptrd++) = (Tfloat)n;
+ }
+ }
+ } break;
+ case 1 : { // L1-norm.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16))
+ cimg_forYZ(*this,y,z) {
+ const ulongT off = (ulongT)offset(0,y,z);
+ const T *ptrs = _data + off;
+ Tfloat *ptrd = res._data + off;
+ cimg_forX(*this,x) {
+ Tfloat n = 0;
+ const T *_ptrs = ptrs++;
+ cimg_forC(*this,c) { n+=cimg::abs(*_ptrs); _ptrs+=whd; }
+ *(ptrd++) = n;
+ }
+ }
+ } break;
+ case 2 : { // L2-norm.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16))
+ cimg_forYZ(*this,y,z) {
+ const ulongT off = (ulongT)offset(0,y,z);
+ const T *ptrs = _data + off;
+ Tfloat *ptrd = res._data + off;
+ cimg_forX(*this,x) {
+ Tfloat n = 0;
+ const T *_ptrs = ptrs++;
+ cimg_forC(*this,c) { n+=cimg::sqr((Tfloat)*_ptrs); _ptrs+=whd; }
+ *(ptrd++) = (Tfloat)std::sqrt((Tfloat)n);
+ }
+ }
+ } break;
+ default : { // Linf-norm.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16))
+ cimg_forYZ(*this,y,z) {
+ const ulongT off = (ulongT)offset(0,y,z);
+ const T *ptrs = _data + off;
+ Tfloat *ptrd = res._data + off;
+ cimg_forX(*this,x) {
+ Tfloat n = 0;
+ const T *_ptrs = ptrs++;
+ cimg_forC(*this,c) { n+=std::pow(cimg::abs((Tfloat)*_ptrs),(Tfloat)norm_type); _ptrs+=whd; }
+ *(ptrd++) = (Tfloat)std::pow((Tfloat)n,1/(Tfloat)norm_type);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Cut pixel values in specified range.
+ /**
+ \param min_value Minimum desired value of the resulting image.
+ \param max_value Maximum desired value of the resulting image.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_cut(160,220);
+ (img,res).display();
+ \endcode
+ \image html ref_cut.jpg
+ **/
+ CImg<T>& cut(const T& min_value, const T& max_value) {
+ if (is_empty()) return *this;
+ const T a = min_value<max_value?min_value:max_value, b = min_value<max_value?max_value:min_value;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) *ptrd = (*ptrd<a)?a:((*ptrd>b)?b:*ptrd);
+ return *this;
+ }
+
+ //! Cut pixel values in specified range \newinstance.
+ CImg<T> get_cut(const T& min_value, const T& max_value) const {
+ return (+*this).cut(min_value,max_value);
+ }
+
+ //! Uniformly quantize pixel values.
+ /**
+ \param nb_levels Number of quantization levels.
+ \param keep_range Tells if resulting values keep the same range as the original ones.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_quantize(4);
+ (img,res).display();
+ \endcode
+ \image html ref_quantize.jpg
+ **/
+ CImg<T>& quantize(const unsigned int nb_levels, const bool keep_range=true) {
+ if (!nb_levels)
+ throw CImgArgumentException(_cimg_instance
+ "quantize(): Invalid quantization request with 0 values.",
+ cimg_instance);
+
+ if (is_empty()) return *this;
+ Tfloat m, M = (Tfloat)max_min(m), range = M - m;
+ if (range>0) {
+ if (keep_range)
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) {
+ const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range);
+ *ptrd = (T)(m + std::min(val,nb_levels - 1)*range/nb_levels);
+ } else
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) {
+ const unsigned int val = (unsigned int)((*ptrd-m)*nb_levels/range);
+ *ptrd = (T)std::min(val,nb_levels - 1);
+ }
+ }
+ return *this;
+ }
+
+ //! Uniformly quantize pixel values \newinstance.
+ CImg<T> get_quantize(const unsigned int n, const bool keep_range=true) const {
+ return (+*this).quantize(n,keep_range);
+ }
+
+ //! Threshold pixel values.
+ /**
+ \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.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_threshold(128);
+ (img,res.normalize(0,255)).display();
+ \endcode
+ \image html ref_threshold.jpg
+ **/
+ CImg<T>& threshold(const T& value, const bool soft_threshold=false, const bool strict_threshold=false) {
+ if (is_empty()) return *this;
+ if (strict_threshold) {
+ if (soft_threshold)
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) {
+ const T v = *ptrd;
+ *ptrd = v>value?(T)(v-value):v<-(float)value?(T)(v + value):(T)0;
+ }
+ else
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536))
+ cimg_rof(*this,ptrd,T) *ptrd = *ptrd>value?(T)1:(T)0;
+ } else {
+ if (soft_threshold)
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32768))
+ cimg_rof(*this,ptrd,T) {
+ const T v = *ptrd;
+ *ptrd = v>=value?(T)(v-value):v<=-(float)value?(T)(v + value):(T)0;
+ }
+ else
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=65536))
+ cimg_rof(*this,ptrd,T) *ptrd = *ptrd>=value?(T)1:(T)0;
+ }
+ return *this;
+ }
+
+ //! Threshold pixel values \newinstance.
+ 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 pixel values.
+ /**
+ \param nb_levels Number of desired histogram levels.
+ \param min_value Minimum pixel value considered for the histogram computation.
+ All pixel values lower than \p min_value will not be counted.
+ \param max_value Maximum pixel value considered for the histogram computation.
+ All pixel values higher than \p max_value will not be counted.
+ \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.
+ - The resulting histogram is always defined in 1d. Histograms of multi-valued images are not multi-dimensional.
+ \par Example
+ \code
+ const CImg<float> img = CImg<float>("reference.jpg").histogram(256);
+ img.display_graph(0,3);
+ \endcode
+ \image html ref_histogram.jpg
+ **/
+ CImg<T>& histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) {
+ return get_histogram(nb_levels,min_value,max_value).move_to(*this);
+ }
+
+ //! Compute the histogram of pixel values \overloading.
+ CImg<T>& histogram(const unsigned int nb_levels) {
+ return get_histogram(nb_levels).move_to(*this);
+ }
+
+ //! Compute the histogram of pixel values \newinstance.
+ CImg<ulongT> get_histogram(const unsigned int nb_levels, const T& min_value, const T& max_value) const {
+ if (!nb_levels || is_empty()) return CImg<ulongT>();
+ const double
+ vmin = (double)(min_value<max_value?min_value:max_value),
+ vmax = (double)(min_value<max_value?max_value:min_value);
+ CImg<ulongT> res(nb_levels,1,1,1,0);
+ cimg_rof(*this,ptrs,T) {
+ const T val = *ptrs;
+ if (val>=vmin && val<=vmax) ++res[val==vmax?nb_levels - 1:(unsigned int)((val - vmin)*nb_levels/(vmax - vmin))];
+ }
+ return res;
+ }
+
+ //! Compute the histogram of pixel values \newinstance.
+ CImg<ulongT> get_histogram(const unsigned int nb_levels) const {
+ if (!nb_levels || is_empty()) return CImg<ulongT>();
+ T vmax = 0, vmin = min_max(vmax);
+ return get_histogram(nb_levels,vmin,vmax);
+ }
+
+ //! Equalize histogram of pixel values.
+ /**
+ \param nb_levels Number of histogram levels used for the equalization.
+ \param min_value Minimum pixel value considered for the histogram computation.
+ All pixel values lower than \p min_value will not be counted.
+ \param max_value Maximum pixel value considered for the histogram computation.
+ All pixel values higher than \p max_value will not be counted.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), res = img.get_equalize(256);
+ (img,res).display();
+ \endcode
+ \image html ref_equalize.jpg
+ **/
+ CImg<T>& equalize(const unsigned int nb_levels, const T& min_value, const T& max_value) {
+ if (!nb_levels || is_empty()) return *this;
+ const T
+ vmin = min_value<max_value?min_value:max_value,
+ vmax = min_value<max_value?max_value:min_value;
+ CImg<ulongT> hist = get_histogram(nb_levels,vmin,vmax);
+ ulongT cumul = 0;
+ cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos] = cumul; }
+ if (!cumul) cumul = 1;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=1048576))
+ cimg_rof(*this,ptrd,T) {
+ const int pos = (int)((*ptrd-vmin)*(nb_levels - 1.)/(vmax-vmin));
+ if (pos>=0 && pos<(int)nb_levels) *ptrd = (T)(vmin + (vmax-vmin)*hist[pos]/cumul);
+ }
+ return *this;
+ }
+
+ //! Equalize histogram of pixel values \overloading.
+ CImg<T>& equalize(const unsigned int nb_levels) {
+ if (!nb_levels || is_empty()) return *this;
+ T vmax = 0, vmin = min_max(vmax);
+ return equalize(nb_levels,vmin,vmax);
+ }
+
+ //! Equalize histogram of pixel values \newinstance.
+ CImg<T> get_equalize(const unsigned int nblevels, const T& val_min, const T& val_max) const {
+ return (+*this).equalize(nblevels,val_min,val_max);
+ }
+
+ //! Equalize histogram of pixel values \newinstance.
+ CImg<T> get_equalize(const unsigned int nblevels) const {
+ return (+*this).equalize(nblevels);
+ }
+
+ //! Index multi-valued pixels regarding to a specified colormap.
+ /**
+ \param colormap Multi-valued colormap used as the basis for multi-valued pixel indexing.
+ \param dithering Level of dithering (0=disable, 1=standard level).
+ \param map_indexes Tell if the values of the resulting image are the colormap indices or the colormap vectors.
+ \note
+ - \p img.index(colormap,dithering,1) is equivalent to <tt>img.index(colormap,dithering,0).map(colormap)</tt>.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"), colormap(3,1,1,3, 0,128,255, 0,128,255, 0,128,255);
+ const CImg<float> res = img.get_index(colormap,1,true);
+ (img,res).display();
+ \endcode
+ \image html ref_index.jpg
+ **/
+ template<typename t>
+ CImg<T>& index(const CImg<t>& colormap, const float dithering=1, const bool map_indexes=false) {
+ return get_index(colormap,dithering,map_indexes).move_to(*this);
+ }
+
+ //! Index multi-valued pixels regarding to a specified colormap \newinstance.
+ template<typename t>
+ CImg<typename CImg<t>::Tuint>
+ get_index(const CImg<t>& colormap, const float dithering=1, const bool map_indexes=true) const {
+ if (colormap._spectrum!=_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "index(): Instance and specified colormap (%u,%u,%u,%u,%p) "
+ "have incompatible dimensions.",
+ cimg_instance,
+ colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data);
+
+ typedef typename CImg<t>::Tuint tuint;
+ if (is_empty()) return CImg<tuint>();
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ pwhd = (ulongT)colormap._width*colormap._height*colormap._depth;
+ CImg<tuint> res(_width,_height,_depth,map_indexes?_spectrum:1);
+ tuint *ptrd = res._data;
+ if (dithering>0) { // Dithered versions.
+ const float ndithering = cimg::cut(dithering,0,1)/16;
+ Tfloat valm = 0, valM = (Tfloat)max_min(valm);
+ if (valm==valM && valm>=0 && valM<=255) { valm = 0; valM = 255; }
+ CImg<Tfloat> cache = get_crop(-1,0,0,0,_width,1,0,_spectrum - 1);
+ Tfloat *cache_current = cache.data(1,0,0,0), *cache_next = cache.data(1,1,0,0);
+ const ulongT cwhd = (ulongT)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 = colormap._data;
+ for (const t *ptrp0 = colormap._data, *ptrp_end = ptrp0 + pwhd; ptrp0<ptrp_end; ) {
+ const Tfloat pval0 = (Tfloat)*(ptrp0++) - val0, dist = pval0*pval0;
+ if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+ }
+ const Tfloat err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)*ndithering;
+ *ptrs0+=7*err0; *(ptrsn0 - 1)+=3*err0; *(ptrsn0++)+=5*err0; *ptrsn0+=err0;
+ if (map_indexes) *(ptrd++) = (tuint)*ptrmin0; else *(ptrd++) = (tuint)(ptrmin0 - colormap._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 = colormap._data;
+ for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+ const Tfloat
+ pval0 = (Tfloat)*(ptrp0++) - val0, pval1 = (Tfloat)*(ptrp1++) - val1,
+ dist = pval0*pval0 + pval1*pval1;
+ if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+ }
+ const t *const ptrmin1 = ptrmin0 + pwhd;
+ const Tfloat
+ err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)*ndithering,
+ err1 = ((*(ptrs1++)=val1) - (Tfloat)*ptrmin1)*ndithering;
+ *ptrs0+=7*err0; *ptrs1+=7*err1;
+ *(ptrsn0 - 1)+=3*err0; *(ptrsn1 - 1)+=3*err1;
+ *(ptrsn0++)+=5*err0; *(ptrsn1++)+=5*err1;
+ *ptrsn0+=err0; *ptrsn1+=err1;
+ if (map_indexes) { *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*ptrmin1; }
+ else *(ptrd++) = (tuint)(ptrmin0 - colormap._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 = colormap._data;
+ for (const t *ptrp0 = colormap._data, *ptrp1 = ptrp0 + pwhd, *ptrp2 = ptrp1 + pwhd,
+ *ptrp_end = ptrp1; ptrp0<ptrp_end; ) {
+ const Tfloat
+ pval0 = (Tfloat)*(ptrp0++) - val0,
+ pval1 = (Tfloat)*(ptrp1++) - val1,
+ pval2 = (Tfloat)*(ptrp2++) - val2,
+ dist = pval0*pval0 + pval1*pval1 + pval2*pval2;
+ if (dist<distmin) { ptrmin0 = ptrp0 - 1; distmin = dist; }
+ }
+ const t *const ptrmin1 = ptrmin0 + pwhd, *const ptrmin2 = ptrmin1 + pwhd;
+ const Tfloat
+ err0 = ((*(ptrs0++)=val0) - (Tfloat)*ptrmin0)*ndithering,
+ err1 = ((*(ptrs1++)=val1) - (Tfloat)*ptrmin1)*ndithering,
+ err2 = ((*(ptrs2++)=val2) - (Tfloat)*ptrmin2)*ndithering;
+
+ *ptrs0+=7*err0; *ptrs1+=7*err1; *ptrs2+=7*err2;
+ *(ptrsn0 - 1)+=3*err0; *(ptrsn1 - 1)+=3*err1; *(ptrsn2 - 1)+=3*err2;
+ *(ptrsn0++)+=5*err0; *(ptrsn1++)+=5*err1; *(ptrsn2++)+=5*err2;
+ *ptrsn0+=err0; *ptrsn1+=err1; *ptrsn2+=err2;
+
+ if (map_indexes) {
+ *(ptrd++) = (tuint)*ptrmin0; *(ptrd1++) = (tuint)*ptrmin1; *(ptrd2++) = (tuint)*ptrmin2;
+ } else *(ptrd++) = (tuint)(ptrmin0 - colormap._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 = colormap._data;
+ for (const t *ptrp = colormap._data, *ptrp_end = ptrp + pwhd; ptrp<ptrp_end; ++ptrp) {
+ Tfloat dist = 0; Tfloat *_ptrs = ptrs; const t *_ptrp = ptrp;
+ cimg_forC(*this,c) {
+ const Tfloat _val = *_ptrs, val = _val<valm?valm:_val>valM?valM:_val;
+ dist+=cimg::sqr((*_ptrs=val) - (Tfloat)*_ptrp); _ptrs+=cwhd; _ptrp+=pwhd;
+ }
+ if (dist<distmin) { ptrmin = ptrp; distmin = dist; }
+ }
+ const t *_ptrmin = ptrmin; Tfloat *_ptrs = ptrs++, *_ptrsn = (ptrsn++) - 1;
+ cimg_forC(*this,c) {
+ const Tfloat err = (*(_ptrs++) - (Tfloat)*_ptrmin)*ndithering;
+ *_ptrs+=7*err; *(_ptrsn++)+=3*err; *(_ptrsn++)+=5*err; *_ptrsn+=err;
+ _ptrmin+=pwhd; _ptrs+=cwhd - 1; _ptrsn+=cwhd - 2;
+ }
+ if (map_indexes) {
+ tuint *_ptrd = ptrd++;
+ cimg_forC(*this,c) { *_ptrd = (tuint)*ptrmin; _ptrd+=whd; ptrmin+=pwhd; }
+ }
+ else *(ptrd++) = (tuint)(ptrmin - colormap._data);
+ }
+ cimg::swap(cache_current,cache_next);
+ }
+ }
+ } else { // Non-dithered versions
+ switch (_spectrum) {
+ case 1 : { // Optimized for scalars.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=64 && _height*_depth>=16 && pwhd>=16))
+ cimg_forYZ(*this,y,z) {
+ tuint *ptrd = res.data(0,y,z);
+ for (const T *ptrs0 = data(0,y,z), *ptrs_end = ptrs0 + _width; ptrs0<ptrs_end; ) {
+ const Tfloat val0 = (Tfloat)*(ptrs0++);
+ Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = colormap._data;
+ for (const t *ptrp0 = colormap._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 - colormap._data);
+ }
+ }
+ } break;
+ case 2 : { // Optimized for 2d vectors.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=64 && _height*_depth>=16 && pwhd>=16))
+ cimg_forYZ(*this,y,z) {
+ tuint *ptrd = res.data(0,y,z), *ptrd1 = ptrd + whd;
+ for (const T *ptrs0 = data(0,y,z), *ptrs1 = ptrs0 + whd, *ptrs_end = ptrs0 + _width; ptrs0<ptrs_end; ) {
+ const Tfloat val0 = (Tfloat)*(ptrs0++), val1 = (Tfloat)*(ptrs1++);
+ Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = colormap._data;
+ for (const t *ptrp0 = colormap._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 - colormap._data);
+ }
+ }
+ } break;
+ case 3 : { // Optimized for 3d vectors (colors).
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=64 && _height*_depth>=16 && pwhd>=16))
+ cimg_forYZ(*this,y,z) {
+ tuint *ptrd = res.data(0,y,z), *ptrd1 = ptrd + whd, *ptrd2 = ptrd1 + whd;
+ for (const T *ptrs0 = data(0,y,z), *ptrs1 = ptrs0 + whd, *ptrs2 = ptrs1 + whd,
+ *ptrs_end = ptrs0 + _width; ptrs0<ptrs_end; ) {
+ const Tfloat val0 = (Tfloat)*(ptrs0++), val1 = (Tfloat)*(ptrs1++), val2 = (Tfloat)*(ptrs2++);
+ Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin0 = colormap._data;
+ for (const t *ptrp0 = colormap._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 - colormap._data);
+ }
+ }
+ } break;
+ default : // Generic version.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=64 && _height*_depth>=16 && pwhd>=16))
+ cimg_forYZ(*this,y,z) {
+ tuint *ptrd = res.data(0,y,z);
+ for (const T *ptrs = data(0,y,z), *ptrs_end = ptrs + _width; ptrs<ptrs_end; ++ptrs) {
+ Tfloat distmin = cimg::type<Tfloat>::max(); const t *ptrmin = colormap._data;
+ for (const t *ptrp = colormap._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 - colormap._data);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Map predefined colormap on the scalar (indexed) image instance.
+ /**
+ \param colormap Multi-valued colormap used for mapping the indexes.
+ \param boundary_conditions The border condition type { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }.
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg"),
+ colormap1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255),
+ colormap2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255),
+ res = img.get_index(colormap1,0).map(colormap2);
+ (img,res).display();
+ \endcode
+ \image html ref_map.jpg
+ **/
+ template<typename t>
+ CImg<T>& map(const CImg<t>& colormap, const unsigned int boundary_conditions=0) {
+ return get_map(colormap,boundary_conditions).move_to(*this);
+ }
+
+ //! Map predefined colormap on the scalar (indexed) image instance \newinstance.
+ template<typename t>
+ CImg<t> get_map(const CImg<t>& colormap, const unsigned int boundary_conditions=0) const {
+ if (_spectrum!=1 && colormap._spectrum!=1)
+ throw CImgArgumentException(_cimg_instance
+ "map(): Instance and specified colormap (%u,%u,%u,%u,%p) "
+ "have incompatible dimensions.",
+ cimg_instance,
+ colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data);
+
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ cwhd = (ulongT)colormap._width*colormap._height*colormap._depth,
+ cwhd2 = 2*cwhd;
+ CImg<t> res(_width,_height,_depth,colormap._spectrum==1?_spectrum:colormap._spectrum);
+ switch (colormap._spectrum) {
+
+ case 1 : { // Optimized for scalars
+ const T *ptrs = _data;
+ switch (boundary_conditions) {
+ case 3 : // Mirror
+ cimg_for(res,ptrd,t) {
+ const ulongT ind = ((ulongT)*(ptrs++))%cwhd2;
+ *ptrd = colormap[ind<cwhd?ind:cwhd2 - ind - 1];
+ }
+ break;
+ case 2 : // Periodic
+ cimg_for(res,ptrd,t) {
+ const ulongT ind = (ulongT)*(ptrs++);
+ *ptrd = colormap[ind%cwhd];
+ } break;
+ case 1 : // Neumann
+ cimg_for(res,ptrd,t) {
+ const longT ind = (longT)*(ptrs++);
+ *ptrd = colormap[cimg::cut(ind,(longT)0,(longT)cwhd - 1)];
+ } break;
+ default : // Dirichlet
+ cimg_for(res,ptrd,t) {
+ const ulongT ind = (ulongT)*(ptrs++);
+ *ptrd = ind<cwhd?colormap[ind]:(t)0;
+ }
+ }
+ } break;
+
+ case 2 : { // Optimized for 2d vectors.
+ const t *const ptrp0 = colormap._data, *ptrp1 = ptrp0 + cwhd;
+ t *ptrd0 = res._data, *ptrd1 = ptrd0 + whd;
+ switch (boundary_conditions) {
+ case 3 : // Mirror
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT
+ _ind = ((ulongT)*(ptrs++))%cwhd2,
+ ind = _ind<cwhd?_ind:cwhd2 - _ind - 1;
+ *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind];
+ }
+ break;
+ case 2 : // Periodic
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT ind = ((ulongT)*(ptrs++))%cwhd;
+ *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind];
+ }
+ break;
+ case 1 : // Neumann
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const longT ind = cimg::cut((longT)*(ptrs++),(longT)0,(longT)cwhd - 1);
+ *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind];
+ }
+ break;
+ default : // Dirichlet
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT ind = (ulongT)*(ptrs++);
+ const bool is_in = ind<cwhd;
+ *(ptrd0++) = is_in?ptrp0[ind]:(t)0; *(ptrd1++) = is_in?ptrp1[ind]:(t)0;
+ }
+ }
+ } break;
+
+ case 3 : { // Optimized for 3d vectors (colors).
+ const t *const ptrp0 = colormap._data, *ptrp1 = ptrp0 + cwhd, *ptrp2 = ptrp1 + cwhd;
+ t *ptrd0 = res._data, *ptrd1 = ptrd0 + whd, *ptrd2 = ptrd1 + whd;
+ switch (boundary_conditions) {
+ case 3 : // Mirror
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT
+ _ind = ((ulongT)*(ptrs++))%cwhd2,
+ ind = _ind<cwhd?_ind:cwhd2 - _ind - 1;
+ *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind]; *(ptrd2++) = ptrp2[ind];
+ } break;
+ case 2 : // Periodic
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT ind = ((ulongT)*(ptrs++))%cwhd;
+ *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind]; *(ptrd2++) = ptrp2[ind];
+ } break;
+ case 1 : // Neumann
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const longT ind = cimg::cut((longT)*(ptrs++),(longT)0,(longT)cwhd - 1);
+ *(ptrd0++) = ptrp0[ind]; *(ptrd1++) = ptrp1[ind]; *(ptrd2++) = ptrp2[ind];
+ } break;
+ default : // Dirichlet
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT ind = (ulongT)*(ptrs++);
+ const bool is_in = ind<cwhd;
+ *(ptrd0++) = is_in?ptrp0[ind]:(t)0; *(ptrd1++) = is_in?ptrp1[ind]:(t)0; *(ptrd2++) = is_in?ptrp2[ind]:(t)0;
+ }
+ }
+ } break;
+
+ default : { // Generic version.
+ t *ptrd = res._data;
+ switch (boundary_conditions) {
+ case 3 : // Mirror
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT
+ _ind = ((ulongT)*(ptrs++))%cwhd,
+ ind = _ind<cwhd?_ind:cwhd2 - _ind - 1;
+ const t *ptrp = colormap._data + ind;
+ t *_ptrd = ptrd++; cimg_forC(res,c) { *_ptrd = *ptrp; _ptrd+=whd; ptrp+=cwhd; }
+ } break;
+ case 2 : // Periodic
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT ind = ((ulongT)*(ptrs++))%cwhd;
+ const t *ptrp = colormap._data + ind;
+ t *_ptrd = ptrd++; cimg_forC(res,c) { *_ptrd = *ptrp; _ptrd+=whd; ptrp+=cwhd; }
+ } break;
+ case 1 : // Neumann
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const longT ind = cimg::cut((longT)*(ptrs++),(longT)0,(longT)cwhd - 1);
+ const t *ptrp = colormap._data + ind;
+ t *_ptrd = ptrd++; cimg_forC(res,c) { *_ptrd = *ptrp; _ptrd+=whd; ptrp+=cwhd; }
+ } break;
+ default : // Dirichlet
+ for (const T *ptrs = _data, *ptrs_end = ptrs + whd; ptrs<ptrs_end; ) {
+ const ulongT ind = (ulongT)*(ptrs++);
+ const bool is_in = ind<cwhd;
+ if (is_in) {
+ const t *ptrp = colormap._data + ind;
+ t *_ptrd = ptrd++; cimg_forC(res,c) { *_ptrd = *ptrp; _ptrd+=whd; ptrp+=cwhd; }
+ } else {
+ t *_ptrd = ptrd++; cimg_forC(res,c) { *_ptrd = (t)0; _ptrd+=whd; }
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Label connected components.
+ /**
+ \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity
+ in 2d case, and between 6(false)- or 26(true)-connectivity in 3d case.
+ \param tolerance Tolerance used to determine if two neighboring pixels belong to the same region.
+ \note The algorithm of connected components computation has been primarily done
+ by A. Meijster, according to the publication:
+ 'W.H. Hesselink, A. Meijster, C. Bron, "Concurrent Determination of Connected Components.",
+ In: Science of Computer Programming 41 (2001), pp. 173--194'.
+ The submitted code has then been modified to fit CImg coding style and constraints.
+ **/
+ CImg<T>& label(const bool is_high_connectivity=false, const Tfloat tolerance=0) {
+ return get_label(is_high_connectivity,tolerance).move_to(*this);
+ }
+
+ //! Label connected components \newinstance.
+ CImg<ulongT> get_label(const bool is_high_connectivity=false,
+ const Tfloat tolerance=0) const {
+ if (is_empty()) return CImg<ulongT>();
+
+ // Create neighborhood tables.
+ int dx[13], dy[13], dz[13], nb = 0;
+ dx[nb] = 1; dy[nb] = 0; dz[nb++] = 0;
+ dx[nb] = 0; dy[nb] = 1; dz[nb++] = 0;
+ if (is_high_connectivity) {
+ dx[nb] = 1; dy[nb] = 1; dz[nb++] = 0;
+ dx[nb] = 1; dy[nb] = -1; dz[nb++] = 0;
+ }
+ if (_depth>1) { // 3d version.
+ dx[nb] = 0; dy[nb] = 0; dz[nb++]=1;
+ if (is_high_connectivity) {
+ dx[nb] = 1; dy[nb] = 1; dz[nb++] = -1;
+ dx[nb] = 1; dy[nb] = 0; dz[nb++] = -1;
+ dx[nb] = 1; dy[nb] = -1; dz[nb++] = -1;
+ dx[nb] = 0; dy[nb] = 1; dz[nb++] = -1;
+
+ dx[nb] = 0; dy[nb] = 1; dz[nb++] = 1;
+ dx[nb] = 1; dy[nb] = -1; dz[nb++] = 1;
+ dx[nb] = 1; dy[nb] = 0; dz[nb++] = 1;
+ dx[nb] = 1; dy[nb] = 1; dz[nb++] = 1;
+ }
+ }
+ return _label(nb,dx,dy,dz,tolerance);
+ }
+
+ //! Label connected components \overloading.
+ /**
+ \param connectivity_mask Mask of the neighboring pixels.
+ \param tolerance Tolerance used to determine if two neighboring pixels belong to the same region.
+ **/
+ template<typename t>
+ CImg<T>& label(const CImg<t>& connectivity_mask, const Tfloat tolerance=0) {
+ return get_label(connectivity_mask,tolerance).move_to(*this);
+ }
+
+ //! Label connected components \newinstance.
+ template<typename t>
+ CImg<ulongT> get_label(const CImg<t>& connectivity_mask,
+ const Tfloat tolerance=0) const {
+ int nb = 0;
+ cimg_for(connectivity_mask,ptr,t) if (*ptr) ++nb;
+ CImg<intT> dx(nb,1,1,1,0), dy(nb,1,1,1,0), dz(nb,1,1,1,0);
+ nb = 0;
+ cimg_forXYZ(connectivity_mask,x,y,z) if ((x || y || z) &&
+ connectivity_mask(x,y,z)) {
+ dx[nb] = x; dy[nb] = y; dz[nb++] = z;
+ }
+ return _label(nb,dx,dy,dz,tolerance);
+ }
+
+ CImg<ulongT> _label(const unsigned int nb, const int
+ *const dx, const int *const dy, const int *const dz,
+ const Tfloat tolerance) const {
+ CImg<ulongT> res(_width,_height,_depth,_spectrum);
+ cimg_forC(*this,c) {
+ CImg<ulongT> _res = res.get_shared_channel(c);
+
+ // Init label numbers.
+ ulongT *ptr = _res.data();
+ cimg_foroff(_res,p) *(ptr++) = p;
+
+ // For each neighbour-direction, label.
+ for (unsigned int n = 0; n<nb; ++n) {
+ const int _dx = dx[n], _dy = dy[n], _dz = dz[n];
+ if (_dx || _dy || _dz) {
+ const int
+ x0 = _dx<0?-_dx:0,
+ x1 = _dx<0?width():width() - _dx,
+ y0 = _dy<0?-_dy:0,
+ y1 = _dy<0?height():height() - _dy,
+ z0 = _dz<0?-_dz:0,
+ z1 = _dz<0?depth():depth() - _dz;
+ const longT
+ wh = (longT)width()*height(),
+ whd = (longT)width()*height()*depth(),
+ offset = _dz*wh + _dy*width() + _dx;
+ for (longT z = z0, nz = z0 + _dz, pz = z0*wh; z<z1; ++z, ++nz, pz+=wh) {
+ for (longT y = y0, ny = y0 + _dy, py = y0*width() + pz; y<y1; ++y, ++ny, py+=width()) {
+ for (longT x = x0, nx = x0 + _dx, p = x0 + py; x<x1; ++x, ++nx, ++p) {
+ if ((Tfloat)cimg::abs((*this)(x,y,z,c,wh,whd) - (*this)(nx,ny,nz,c,wh,whd))<=tolerance) {
+ const longT q = p + offset;
+ ulongT x, y;
+ for (x = (ulongT)(p<q?q:p), y = (ulongT)(p<q?p:q); x!=y && _res[x]!=x; ) {
+ x = _res[x]; if (x<y) cimg::swap(x,y);
+ }
+ if (x!=y) _res[x] = (ulongT)y;
+ for (ulongT _p = (ulongT)p; _p!=y; ) {
+ const ulongT h = _res[_p];
+ _res[_p] = (ulongT)y;
+ _p = h;
+ }
+ for (ulongT _q = (ulongT)q; _q!=y; ) {
+ const ulongT h = _res[_q];
+ _res[_q] = (ulongT)y;
+ _q = h;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Resolve equivalences.
+ ulongT counter = 0;
+ ptr = _res.data();
+ cimg_foroff(_res,p) { *ptr = *ptr==p?counter++:_res[*ptr]; ++ptr; }
+ }
+ return res;
+ }
+
+ // [internal] Replace possibly malicious characters for commands to be called by system() by their escaped version.
+ CImg<T>& _system_strescape() {
+#define cimg_system_strescape(c,s) case c : if (p!=ptrs) CImg<T>(ptrs,(unsigned int)(p-ptrs),1,1,1,false).\
+ move_to(list); \
+ CImg<T>(s,(unsigned int)std::strlen(s),1,1,1,false).move_to(list); ptrs = p + 1; break
+ CImgList<T> list;
+ const T *ptrs = _data;
+ cimg_for(*this,p,T) switch ((int)*p) {
+ cimg_system_strescape('\\',"\\\\");
+ cimg_system_strescape('\"',"\\\"");
+ cimg_system_strescape('!',"\"\\!\"");
+ cimg_system_strescape('`',"\\`");
+ cimg_system_strescape('$',"\\$");
+ }
+ if (ptrs<end()) CImg<T>(ptrs,(unsigned int)(end()-ptrs),1,1,1,false).move_to(list);
+ return (list>'x').move_to(*this);
+ }
+
+ //@}
+ //---------------------------------
+ //
+ //! \name Color Base Management
+ //@{
+ //---------------------------------
+
+ //! Return colormap \e "default", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_default.jpg
+ **/
+ static const CImg<Tuchar>& default_LUT256() {
+ static CImg<Tuchar> colormap;
+ cimg::mutex(8);
+ if (!colormap) {
+ colormap.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) {
+ colormap(0,index,0) = (Tuchar)r;
+ colormap(0,index,1) = (Tuchar)g;
+ colormap(0,index++,2) = (Tuchar)b;
+ }
+ }
+ cimg::mutex(8,0);
+ return colormap;
+ }
+
+ //! Return colormap \e "HSV", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_hsv.jpg
+ **/
+ static const CImg<Tuchar>& HSV_LUT256() {
+ static CImg<Tuchar> colormap;
+ cimg::mutex(8);
+ if (!colormap) {
+ CImg<Tint> tmp(1,256,1,3,1);
+ tmp.get_shared_channel(0).sequence(0,359);
+ colormap = tmp.HSVtoRGB();
+ }
+ cimg::mutex(8,0);
+ return colormap;
+ }
+
+ //! Return colormap \e "lines", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_lines.jpg
+ **/
+ static const CImg<Tuchar>& lines_LUT256() {
+ static const unsigned char pal[] = {
+ 217,62,88,75,1,237,240,12,56,160,165,116,1,1,204,2,15,248,148,185,133,141,46,246,222,116,16,5,207,226,
+ 17,114,247,1,214,53,238,0,95,55,233,235,109,0,17,54,33,0,90,30,3,0,94,27,19,0,68,212,166,130,0,15,7,119,
+ 238,2,246,198,0,3,16,10,13,2,25,28,12,6,2,99,18,141,30,4,3,140,12,4,30,233,7,10,0,136,35,160,168,184,20,
+ 233,0,1,242,83,90,56,180,44,41,0,6,19,207,5,31,214,4,35,153,180,75,21,76,16,202,218,22,17,2,136,71,74,
+ 81,251,244,148,222,17,0,234,24,0,200,16,239,15,225,102,230,186,58,230,110,12,0,7,129,249,22,241,37,219,
+ 1,3,254,210,3,212,113,131,197,162,123,252,90,96,209,60,0,17,0,180,249,12,112,165,43,27,229,77,40,195,12,
+ 87,1,210,148,47,80,5,9,1,137,2,40,57,205,244,40,8,252,98,0,40,43,206,31,187,0,180,1,69,70,227,131,108,0,
+ 223,94,228,35,248,243,4,16,0,34,24,2,9,35,73,91,12,199,51,1,249,12,103,131,20,224,2,70,32,
+ 233,1,165,3,8,154,246,233,196,5,0,6,183,227,247,195,208,36,0,0,226,160,210,198,69,153,210,1,23,8,192,2,4,
+ 137,1,0,52,2,249,241,129,0,0,234,7,238,71,7,32,15,157,157,252,158,2,250,6,13,30,11,162,0,199,21,11,27,224,
+ 4,157,20,181,111,187,218,3,0,11,158,230,196,34,223,22,248,135,254,210,157,219,0,117,239,3,255,4,227,5,247,
+ 11,4,3,188,111,11,105,195,2,0,14,1,21,219,192,0,183,191,113,241,1,12,17,248,0,48,7,19,1,254,212,0,239,246,
+ 0,23,0,250,165,194,194,17,3,253,0,24,6,0,141,167,221,24,212,2,235,243,0,0,205,1,251,133,204,28,4,6,1,10,
+ 141,21,74,12,236,254,228,19,1,0,214,1,186,13,13,6,13,16,27,209,6,216,11,207,251,59,32,9,155,23,19,235,143,
+ 116,6,213,6,75,159,23,6,0,228,4,10,245,249,1,7,44,234,4,102,174,0,19,239,103,16,15,18,8,214,22,4,47,244,
+ 255,8,0,251,173,1,212,252,250,251,252,6,0,29,29,222,233,246,5,149,0,182,180,13,151,0,203,183,0,35,149,0,
+ 235,246,254,78,9,17,203,73,11,195,0,3,5,44,0,0,237,5,106,6,130,16,214,20,168,247,168,4,207,11,5,1,232,251,
+ 129,210,116,231,217,223,214,27,45,38,4,177,186,249,7,215,172,16,214,27,249,230,236,2,34,216,217,0,175,30,
+ 243,225,244,182,20,212,2,226,21,255,20,0,2,13,62,13,191,14,76,64,20,121,4,118,0,216,1,147,0,2,210,1,215,
+ 95,210,236,225,184,46,0,248,24,11,1,9,141,250,243,9,221,233,160,11,147,2,55,8,23,12,253,9,0,54,0,231,6,3,
+ 141,8,2,246,9,180,5,11,8,227,8,43,110,242,1,130,5,97,36,10,6,219,86,133,11,108,6,1,5,244,67,19,28,0,174,
+ 154,16,127,149,252,188,196,196,228,244,9,249,0,0,0,37,170,32,250,0,73,255,23,3,224,234,38,195,198,0,255,87,
+ 33,221,174,31,3,0,189,228,6,153,14,144,14,108,197,0,9,206,245,254,3,16,253,178,248,0,95,125,8,0,3,168,21,
+ 23,168,19,50,240,244,185,0,1,144,10,168,31,82,1,13 };
+ static const CImg<Tuchar> colormap(pal,1,256,1,3,false);
+ return colormap;
+ }
+
+ //! Return colormap \e "hot", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_hot.jpg
+ **/
+ static const CImg<Tuchar>& hot_LUT256() {
+ static CImg<Tuchar> colormap;
+ cimg::mutex(8);
+ if (!colormap) {
+ colormap.assign(1,4,1,3,(T)0);
+ colormap[1] = colormap[2] = colormap[3] = colormap[6] = colormap[7] = colormap[11] = 255;
+ colormap.resize(1,256,1,3,3);
+ }
+ cimg::mutex(8,0);
+ return colormap;
+ }
+
+ //! Return colormap \e "cool", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_cool.jpg
+ **/
+ static const CImg<Tuchar>& cool_LUT256() {
+ static CImg<Tuchar> colormap;
+ cimg::mutex(8);
+ if (!colormap) colormap.assign(1,2,1,3).fill((T)0,(T)255,(T)255,(T)0,(T)255,(T)255).resize(1,256,1,3,3);
+ cimg::mutex(8,0);
+ return colormap;
+ }
+
+ //! Return colormap \e "jet", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_jet.jpg
+ **/
+ static const CImg<Tuchar>& jet_LUT256() {
+ static CImg<Tuchar> colormap;
+ cimg::mutex(8);
+ if (!colormap) {
+ colormap.assign(1,4,1,3,(T)0);
+ colormap[2] = colormap[3] = colormap[5] = colormap[6] = colormap[8] = colormap[9] = 255;
+ colormap.resize(1,256,1,3,3);
+ }
+ cimg::mutex(8,0);
+ return colormap;
+ }
+
+ //! Return colormap \e "flag", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_flag.jpg
+ **/
+ static const CImg<Tuchar>& flag_LUT256() {
+ static CImg<Tuchar> colormap;
+ cimg::mutex(8);
+ if (!colormap) {
+ colormap.assign(1,4,1,3,(T)0);
+ colormap[0] = colormap[1] = colormap[5] = colormap[9] = colormap[10] = 255;
+ colormap.resize(1,256,1,3,0,2);
+ }
+ cimg::mutex(8,0);
+ return colormap;
+ }
+
+ //! Return colormap \e "cube", containing 256 colors entries in RGB.
+ /**
+ \return The following \c 256x1x1x3 colormap is returned:
+ \image html ref_colormap_cube.jpg
+ **/
+ static const CImg<Tuchar>& cube_LUT256() {
+ static CImg<Tuchar> colormap;
+ cimg::mutex(8);
+ if (!colormap) {
+ colormap.assign(1,8,1,3,(T)0);
+ colormap[1] = colormap[3] = colormap[5] = colormap[7] =
+ colormap[10] = colormap[11] = colormap[12] = colormap[13] =
+ colormap[20] = colormap[21] = colormap[22] = colormap[23] = 255;
+ colormap.resize(1,256,1,3,3);
+ }
+ cimg::mutex(8,0);
+ return colormap;
+ }
+
+ //! Convert pixel values from sRGB to RGB color spaces.
+ CImg<T>& sRGBtoRGB() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32))
+ cimg_rof(*this,ptr,T) {
+ const Tfloat
+ sval = (Tfloat)*ptr/255,
+ val = (Tfloat)(sval<=0.04045f?sval/12.92f:std::pow((sval + 0.055f)/(1.055f),2.4f));
+ *ptr = (T)cimg::cut(val*255,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from sRGB to RGB color spaces \newinstance.
+ CImg<Tfloat> get_sRGBtoRGB() const {
+ return CImg<Tfloat>(*this,false).sRGBtoRGB();
+ }
+
+ //! Convert pixel values from RGB to sRGB color spaces.
+ CImg<T>& RGBtosRGB() {
+ if (is_empty()) return *this;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32))
+ cimg_rof(*this,ptr,T) {
+ const Tfloat
+ val = (Tfloat)*ptr/255,
+ sval = (Tfloat)(val<=0.0031308f?val*12.92f:1.055f*std::pow(val,0.416667f) - 0.055f);
+ *ptr = (T)cimg::cut(sval*255,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to sRGB color spaces \newinstance.
+ CImg<Tfloat> get_RGBtosRGB() const {
+ return CImg<Tfloat>(*this,false).RGBtosRGB();
+ }
+
+ //! Convert pixel values from RGB to HSI color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ R = (Tfloat)p1[N],
+ G = (Tfloat)p2[N],
+ B = (Tfloat)p3[N],
+ theta = (Tfloat)(std::acos(0.5f*((R - G) + (R - B))/
+ std::sqrt(cimg::sqr(R - G) + (R - B)*(G - B)))*180/cimg::PI),
+ m = cimg::min(R,G,B),
+ sum = R + G + B;
+ Tfloat H = 0, S = 0, I = 0;
+ if (theta>0) H = B<=G?theta:360 - theta;
+ if (sum>0) S = 1 - 3*m/sum;
+ I = sum/(3*255);
+ p1[N] = (T)cimg::cut(H,0,360);
+ p2[N] = (T)cimg::cut(S,0,1);
+ p3[N] = (T)cimg::cut(I,0,1);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to HSI color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoHSI() const {
+ return CImg<Tfloat>(*this,false).RGBtoHSI();
+ }
+
+ //! Convert pixel values from HSI to RGB color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256))
+ for (ulongT N = 0; N<whd; ++N) {
+ Tfloat
+ H = cimg::mod((Tfloat)p1[N],(Tfloat)360),
+ S = (Tfloat)p2[N],
+ I = (Tfloat)p3[N],
+ 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);
+ }
+ p1[N] = (T)cimg::cut(R*255,0,255);
+ p2[N] = (T)cimg::cut(G*255,0,255);
+ p3[N] = (T)cimg::cut(B*255,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from HSI to RGB color spaces \newinstance.
+ CImg<Tfloat> get_HSItoRGB() const {
+ return CImg< Tuchar>(*this,false).HSItoRGB();
+ }
+
+ //! Convert pixel values from RGB to HSL color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ R = (Tfloat)p1[N],
+ G = (Tfloat)p2[N],
+ B = (Tfloat)p3[N],
+ m = cimg::min(R,G,B),
+ M = cimg::max(R,G,B),
+ L = (m + M)/(2*255);
+ Tfloat H = 0, S = 0;
+ if (M==m) H = S = 0;
+ else {
+ const Tfloat
+ f = R==m?G - B:G==m?B - R:R - G,
+ i = R==m?3:G==m?5:1;
+ H = i - f/(M - m);
+ if (H>=6) H-=6;
+ H*=60;
+ S = 2*L<=1?(M - m)/(M + m):(M - m)/(2*255 - M - m);
+ }
+ p1[N] = (T)cimg::cut(H,0,360);
+ p2[N] = (T)cimg::cut(S,0,1);
+ p3[N] = (T)cimg::cut(L,0,1);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to HSL color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoHSL() const {
+ return CImg<Tfloat>(*this,false).RGBtoHSL();
+ }
+
+ //! Convert pixel values from HSL to RGB color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ H = cimg::mod((Tfloat)p1[N],(Tfloat)360),
+ S = (Tfloat)p2[N],
+ L = (Tfloat)p3[N],
+ q = 2*L<1?L*(1 + S):L + S - L*S,
+ p = 2*L - q,
+ h = H/360,
+ tr = h + (Tfloat)1/3,
+ tg = h,
+ tb = h - (Tfloat)1/3,
+ ntr = tr<0?tr + 1:tr>1?tr - 1:(Tfloat)tr,
+ ntg = tg<0?tg + 1:tg>1?tg - 1:(Tfloat)tg,
+ ntb = tb<0?tb + 1:tb>1?tb - 1:(Tfloat)tb,
+ R = 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 = 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 = 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[N] = (T)cimg::cut(255*R,0,255);
+ p2[N] = (T)cimg::cut(255*G,0,255);
+ p3[N] = (T)cimg::cut(255*B,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from HSL to RGB color spaces \newinstance.
+ CImg<Tuchar> get_HSLtoRGB() const {
+ return CImg<Tuchar>(*this,false).HSLtoRGB();
+ }
+
+ //! Convert pixel values from RGB to HSV color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ R = (Tfloat)p1[N],
+ G = (Tfloat)p2[N],
+ B = (Tfloat)p3[N],
+ m = cimg::min(R,G,B),
+ M = cimg::max(R,G,B);
+ Tfloat H = 0, S = 0;
+ if (M!=m) {
+ const Tfloat
+ f = R==m?G - B:G==m?B - R:R - G,
+ i = R==m?3:G==m?5:1;
+ H = i - f/(M - m);
+ if (H>=6) H-=6;
+ H*=60;
+ S = (M - m)/M;
+ }
+ p1[N] = (T)cimg::cut(H,0,360);
+ p2[N] = (T)cimg::cut(S,0,1);
+ p3[N] = (T)cimg::cut(M/255,0,1);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to HSV color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoHSV() const {
+ return CImg<Tfloat>(*this,false).RGBtoHSV();
+ }
+
+ //! Convert pixel values from HSV to RGB color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=256))
+ for (ulongT N = 0; N<whd; ++N) {
+ Tfloat
+ H = cimg::mod((Tfloat)p1[N],(Tfloat)360),
+ S = (Tfloat)p2[N],
+ V = (Tfloat)p3[N],
+ 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;
+ }
+ }
+ p1[N] = (T)cimg::cut(R*255,0,255);
+ p2[N] = (T)cimg::cut(G*255,0,255);
+ p3[N] = (T)cimg::cut(B*255,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from HSV to RGB color spaces \newinstance.
+ CImg<Tuchar> get_HSVtoRGB() const {
+ return CImg<Tuchar>(*this,false).HSVtoRGB();
+ }
+
+ //! Convert pixel values from RGB to YCbCr color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=512))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ R = (Tfloat)p1[N],
+ G = (Tfloat)p2[N],
+ B = (Tfloat)p3[N],
+ 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[N] = (T)cimg::cut(Y,0,255),
+ p2[N] = (T)cimg::cut(Cb,0,255),
+ p3[N] = (T)cimg::cut(Cr,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to YCbCr color spaces \newinstance.
+ CImg<Tuchar> get_RGBtoYCbCr() const {
+ return CImg<Tuchar>(*this,false).RGBtoYCbCr();
+ }
+
+ //! Convert pixel values from RGB to YCbCr color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=512))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ Y = (Tfloat)p1[N] - 16,
+ Cb = (Tfloat)p2[N] - 128,
+ Cr = (Tfloat)p3[N] - 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[N] = (T)cimg::cut(R,0,255),
+ p2[N] = (T)cimg::cut(G,0,255),
+ p3[N] = (T)cimg::cut(B,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to YCbCr color spaces \newinstance.
+ CImg<Tuchar> get_YCbCrtoRGB() const {
+ return CImg<Tuchar>(*this,false).YCbCrtoRGB();
+ }
+
+ //! Convert pixel values from RGB to YUV color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=16384))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ R = (Tfloat)p1[N]/255,
+ G = (Tfloat)p2[N]/255,
+ B = (Tfloat)p3[N]/255,
+ Y = 0.299f*R + 0.587f*G + 0.114f*B;
+ p1[N] = (T)Y;
+ p2[N] = (T)(0.492f*(B - Y));
+ p3[N] = (T)(0.877*(R - Y));
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to YUV color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoYUV() const {
+ return CImg<Tfloat>(*this,false).RGBtoYUV();
+ }
+
+ //! Convert pixel values from YUV to RGB color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=16384))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ Y = (Tfloat)p1[N],
+ U = (Tfloat)p2[N],
+ V = (Tfloat)p3[N],
+ R = (Y + 1.140f*V)*255,
+ G = (Y - 0.395f*U - 0.581f*V)*255,
+ B = (Y + 2.032f*U)*255;
+ p1[N] = (T)cimg::cut(R,0,255),
+ p2[N] = (T)cimg::cut(G,0,255),
+ p3[N] = (T)cimg::cut(B,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from YUV to RGB color spaces \newinstance.
+ CImg<Tuchar> get_YUVtoRGB() const {
+ return CImg< Tuchar>(*this,false).YUVtoRGB();
+ }
+
+ //! Convert pixel values from RGB to CMY color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ R = (Tfloat)p1[N],
+ G = (Tfloat)p2[N],
+ B = (Tfloat)p3[N],
+ C = 255 - R,
+ M = 255 - G,
+ Y = 255 - B;
+ p1[N] = (T)cimg::cut(C,0,255),
+ p2[N] = (T)cimg::cut(M,0,255),
+ p3[N] = (T)cimg::cut(Y,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to CMY color spaces \newinstance.
+ CImg<Tuchar> get_RGBtoCMY() const {
+ return CImg<Tfloat>(*this,false).RGBtoCMY();
+ }
+
+ //! Convert pixel values from CMY to RGB color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ C = (Tfloat)p1[N],
+ M = (Tfloat)p2[N],
+ Y = (Tfloat)p3[N],
+ R = 255 - C,
+ G = 255 - M,
+ B = 255 - Y;
+ p1[N] = (T)cimg::cut(R,0,255),
+ p2[N] = (T)cimg::cut(G,0,255),
+ p3[N] = (T)cimg::cut(B,0,255);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from CMY to RGB color spaces \newinstance.
+ CImg<Tuchar> get_CMYtoRGB() const {
+ return CImg<Tuchar>(*this,false).CMYtoRGB();
+ }
+
+ //! Convert pixel values from CMY to CMYK color spaces.
+ CImg<T>& CMYtoCMYK() {
+ return get_CMYtoCMYK().move_to(*this);
+ }
+
+ //! Convert pixel values from CMY to CMYK color spaces \newinstance.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=1024))
+ for (ulongT N = 0; N<whd; ++N) {
+ Tfloat
+ C = (Tfloat)ps1[N],
+ M = (Tfloat)ps2[N],
+ Y = (Tfloat)ps3[N],
+ 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[N] = (Tfloat)cimg::cut(C,0,255),
+ pd2[N] = (Tfloat)cimg::cut(M,0,255),
+ pd3[N] = (Tfloat)cimg::cut(Y,0,255),
+ pd4[N] = (Tfloat)cimg::cut(K,0,255);
+ }
+ return res;
+ }
+
+ //! Convert pixel values from CMYK to CMY color spaces.
+ CImg<T>& CMYKtoCMY() {
+ return get_CMYKtoCMY().move_to(*this);
+ }
+
+ //! Convert pixel values from CMYK to CMY color spaces \newinstance.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=1024))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ C = (Tfloat)ps1[N],
+ M = (Tfloat)ps2[N],
+ Y = (Tfloat)ps3[N],
+ K = (Tfloat)ps4[N],
+ K1 = 1 - K/255,
+ nC = C*K1 + K,
+ nM = M*K1 + K,
+ nY = Y*K1 + K;
+ pd1[N] = (Tfloat)cimg::cut(nC,0,255),
+ pd2[N] = (Tfloat)cimg::cut(nM,0,255),
+ pd3[N] = (Tfloat)cimg::cut(nY,0,255);
+ }
+ return res;
+ }
+
+ //! Convert pixel values from RGB to XYZ color spaces.
+ /**
+ \param use_D65 Tell to use the D65 illuminant (D50 otherwise).
+ **/
+ CImg<T>& RGBtoXYZ(const bool use_D65=true) {
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ R = (Tfloat)p1[N]/255,
+ G = (Tfloat)p2[N]/255,
+ B = (Tfloat)p3[N]/255;
+ if (use_D65) { // D65
+ p1[N] = (T)(0.4124564*R + 0.3575761*G + 0.1804375*B);
+ p2[N] = (T)(0.2126729*R + 0.7151522*G + 0.0721750*B);
+ p3[N] = (T)(0.0193339*R + 0.1191920*G + 0.9503041*B);
+ } else { // D50
+ p1[N] = (T)(0.43603516*R + 0.38511658*G + 0.14305115*B);
+ p2[N] = (T)(0.22248840*R + 0.71690369*G + 0.06060791*B);
+ p3[N] = (T)(0.01391602*R + 0.09706116*G + 0.71392822*B);
+ }
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from RGB to XYZ color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoXYZ(const bool use_D65=true) const {
+ return CImg<Tfloat>(*this,false).RGBtoXYZ(use_D65);
+ }
+
+ //! Convert pixel values from XYZ to RGB color spaces.
+ /**
+ \param use_D65 Tell to use the D65 illuminant (D50 otherwise).
+ **/
+ CImg<T>& XYZtoRGB(const bool use_D65=true) {
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=2048))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ X = (Tfloat)p1[N]*255,
+ Y = (Tfloat)p2[N]*255,
+ Z = (Tfloat)p3[N]*255;
+ if (use_D65) {
+ p1[N] = (T)cimg::cut(3.2404542*X - 1.5371385*Y - 0.4985314*Z,0,255);
+ p2[N] = (T)cimg::cut(-0.9692660*X + 1.8760108*Y + 0.0415560*Z,0,255);
+ p3[N] = (T)cimg::cut(0.0556434*X - 0.2040259*Y + 1.0572252*Z,0,255);
+ } else {
+ p1[N] = (T)cimg::cut(3.134274799724*X - 1.617275708956*Y - 0.490724283042*Z,0,255);
+ p2[N] = (T)cimg::cut(-0.978795575994*X + 1.916161689117*Y + 0.033453331711*Z,0,255);
+ p3[N] = (T)cimg::cut(0.071976988401*X - 0.228984974402*Y + 1.405718224383*Z,0,255);
+ }
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from XYZ to RGB color spaces \newinstance.
+ CImg<Tuchar> get_XYZtoRGB(const bool use_D65=true) const {
+ return CImg<Tuchar>(*this,false).XYZtoRGB(use_D65);
+ }
+
+ //! Convert pixel values from XYZ to Lab color spaces.
+ CImg<T>& XYZtoLab(const bool use_D65=true) {
+#define _cimg_Labf(x) (24389*(x)>216?cimg::cbrt(x):(24389*(x)/27 + 16)/116)
+
+ if (_spectrum!=3)
+ throw CImgInstanceException(_cimg_instance
+ "XYZtoLab(): Instance is not a XYZ image.",
+ cimg_instance);
+ const CImg<Tfloat> white = CImg<Tfloat>(1,1,1,3,255).RGBtoXYZ(use_D65);
+ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=128))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ X = (Tfloat)(p1[N]/white[0]),
+ Y = (Tfloat)(p2[N]/white[1]),
+ Z = (Tfloat)(p3[N]/white[2]),
+ fX = (Tfloat)_cimg_Labf(X),
+ fY = (Tfloat)_cimg_Labf(Y),
+ fZ = (Tfloat)_cimg_Labf(Z);
+ p1[N] = (T)cimg::cut(116*fY - 16,0,100);
+ p2[N] = (T)(500*(fX - fY));
+ p3[N] = (T)(200*(fY - fZ));
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from XYZ to Lab color spaces \newinstance.
+ CImg<Tfloat> get_XYZtoLab(const bool use_D65=true) const {
+ return CImg<Tfloat>(*this,false).XYZtoLab(use_D65);
+ }
+
+ //! Convert pixel values from Lab to XYZ color spaces.
+ CImg<T>& LabtoXYZ(const bool use_D65=true) {
+ if (_spectrum!=3)
+ throw CImgInstanceException(_cimg_instance
+ "LabtoXYZ(): Instance is not a Lab image.",
+ cimg_instance);
+ const CImg<Tfloat> white = CImg<Tfloat>(1,1,1,3,255).RGBtoXYZ(use_D65);
+ T *p1 = data(0,0,0,0), *p2 = data(0,0,0,1), *p3 = data(0,0,0,2);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=128))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ L = (Tfloat)p1[N],
+ a = (Tfloat)p2[N],
+ b = (Tfloat)p3[N],
+ cY = (L + 16)/116,
+ cZ = cY - b/200,
+ cX = a/500 + cY,
+ X = (Tfloat)(24389*cX>216?cX*cX*cX:(116*cX - 16)*27/24389),
+ Y = (Tfloat)(27*L>216?cY*cY*cY:27*L/24389),
+ Z = (Tfloat)(24389*cZ>216?cZ*cZ*cZ:(116*cZ - 16)*27/24389);
+ p1[N] = (T)(X*white[0]);
+ p2[N] = (T)(Y*white[1]);
+ p3[N] = (T)(Z*white[2]);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from Lab to XYZ color spaces \newinstance.
+ CImg<Tfloat> get_LabtoXYZ(const bool use_D65=true) const {
+ return CImg<Tfloat>(*this,false).LabtoXYZ(use_D65);
+ }
+
+ //! Convert pixel values from XYZ to xyY color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=4096))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ X = (Tfloat)p1[N],
+ Y = (Tfloat)p2[N],
+ Z = (Tfloat)p3[N],
+ sum = X + Y + Z,
+ nsum = sum>0?sum:1;
+ p1[N] = (T)(X/nsum);
+ p2[N] = (T)(Y/nsum);
+ p3[N] = (T)Y;
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from XYZ to xyY color spaces \newinstance.
+ CImg<Tfloat> get_XYZtoxyY() const {
+ return CImg<Tfloat>(*this,false).XYZtoxyY();
+ }
+
+ //! Convert pixel values from xyY pixels to XYZ color spaces.
+ 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);
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(whd>=4096))
+ for (ulongT N = 0; N<whd; ++N) {
+ const Tfloat
+ px = (Tfloat)p1[N],
+ py = (Tfloat)p2[N],
+ Y = (Tfloat)p3[N],
+ ny = py>0?py:1;
+ p1[N] = (T)(px*Y/ny);
+ p2[N] = (T)Y;
+ p3[N] = (T)((1 - px - py)*Y/ny);
+ }
+ return *this;
+ }
+
+ //! Convert pixel values from xyY pixels to XYZ color spaces \newinstance.
+ CImg<Tfloat> get_xyYtoXYZ() const {
+ return CImg<Tfloat>(*this,false).xyYtoXYZ();
+ }
+
+ //! Convert pixel values from RGB to Lab color spaces.
+ CImg<T>& RGBtoLab(const bool use_D65=true) {
+ return RGBtoXYZ(use_D65).XYZtoLab(use_D65);
+ }
+
+ //! Convert pixel values from RGB to Lab color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoLab(const bool use_D65=true) const {
+ return CImg<Tfloat>(*this,false).RGBtoLab(use_D65);
+ }
+
+ //! Convert pixel values from Lab to RGB color spaces.
+ CImg<T>& LabtoRGB(const bool use_D65=true) {
+ return LabtoXYZ().XYZtoRGB(use_D65);
+ }
+
+ //! Convert pixel values from Lab to RGB color spaces \newinstance.
+ CImg<Tuchar> get_LabtoRGB(const bool use_D65=true) const {
+ return CImg<Tuchar>(*this,false).LabtoRGB(use_D65);
+ }
+
+ //! Convert pixel values from RGB to xyY color spaces.
+ CImg<T>& RGBtoxyY(const bool use_D65=true) {
+ return RGBtoXYZ(use_D65).XYZtoxyY();
+ }
+
+ //! Convert pixel values from RGB to xyY color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoxyY(const bool use_D65=true) const {
+ return CImg<Tfloat>(*this,false).RGBtoxyY(use_D65);
+ }
+
+ //! Convert pixel values from xyY to RGB color spaces.
+ CImg<T>& xyYtoRGB(const bool use_D65=true) {
+ return xyYtoXYZ().XYZtoRGB(use_D65);
+ }
+
+ //! Convert pixel values from xyY to RGB color spaces \newinstance.
+ CImg<Tuchar> get_xyYtoRGB(const bool use_D65=true) const {
+ return CImg<Tuchar>(*this,false).xyYtoRGB(use_D65);
+ }
+
+ //! Convert pixel values from RGB to CMYK color spaces.
+ CImg<T>& RGBtoCMYK() {
+ return RGBtoCMY().CMYtoCMYK();
+ }
+
+ //! Convert pixel values from RGB to CMYK color spaces \newinstance.
+ CImg<Tfloat> get_RGBtoCMYK() const {
+ return CImg<Tfloat>(*this,false).RGBtoCMYK();
+ }
+
+ //! Convert pixel values from CMYK to RGB color spaces.
+ CImg<T>& CMYKtoRGB() {
+ return CMYKtoCMY().CMYtoRGB();
+ }
+
+ //! Convert pixel values from CMYK to RGB color spaces \newinstance.
+ CImg<Tuchar> get_CMYKtoRGB() const {
+ return CImg<Tuchar>(*this,false).CMYKtoRGB();
+ }
+
+ //@}
+ //------------------------------------------
+ //
+ //! \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 image to new dimensions.
+ /**
+ \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 boundary_conditions.
+ - 1 = nearest-neighbor interpolation.
+ - 2 = moving average interpolation.
+ - 3 = linear interpolation.
+ - 4 = grid interpolation.
+ - 5 = cubic interpolation.
+ - 6 = lanczos interpolation.
+ \param boundary_conditions Type of boundary conditions used if necessary.
+ \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 boundary_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,(T)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,boundary_conditions,
+ centering_x,centering_y,centering_z,centering_c).move_to(*this);
+ }
+
+ //! Resize image to new dimensions \newinstance.
+ 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 boundary_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 = std::max(1U,(unsigned int)(size_x>=0?size_x:-size_x*width()/100)),
+ sy = std::max(1U,(unsigned int)(size_y>=0?size_y:-size_y*height()/100)),
+ sz = std::max(1U,(unsigned int)(size_z>=0?size_z:-size_z*depth()/100)),
+ sc = std::max(1U,(unsigned int)(size_c>=0?size_c:-size_c*spectrum()/100));
+ if (sx==_width && sy==_height && sz==_depth && sc==_spectrum) return +*this;
+ if (is_empty()) return CImg<T>(sx,sy,sz,sc,(T)0);
+ CImg<T> res;
+ switch (interpolation_type) {
+
+ // Raw resizing.
+ //
+ case -1 :
+ std::memcpy(res.assign(sx,sy,sz,sc,(T)0)._data,_data,sizeof(T)*std::min(size(),(ulongT)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 (boundary_conditions) {
+ case 3 : { // Mirror
+ res.assign(sx,sy,sz,sc);
+ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=65536))
+ cimg_forXYZC(res,x,y,z,c) {
+ const int
+ mx = cimg::mod(x - xc,w2), my = cimg::mod(y - yc,h2),
+ mz = cimg::mod(z - zc,d2), mc = cimg::mod(c - cc,s2);
+ res(x,y,z,c) = (*this)(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,
+ mc<spectrum()?mc:s2 - mc - 1);
+ }
+ } break;
+ case 2 : { // Periodic
+ 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(),
+ dx = width(), dy = height(), dz = depth(), dc = spectrum();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=65536))
+ for (int c = c0; c<(int)sc; c+=dc)
+ for (int z = z0; z<(int)sz; z+=dz)
+ for (int y = y0; y<(int)sy; y+=dy)
+ for (int x = x0; x<(int)sx; x+=dx)
+ res.draw_image(x,y,z,c,*this);
+ } break;
+ case 1 : { // Neumann
+ 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
+ res.assign(sx,sy,sz,sc,(T)0).draw_image(xc,yc,zc,cc,*this);
+ }
+ break;
+ } break;
+
+ // Nearest neighbor interpolation.
+ //
+ case 1 : {
+ res.assign(sx,sy,sz,sc);
+ CImg<ulongT> off_x(sx), off_y(sy + 1), off_z(sz + 1), off_c(sc + 1);
+ const ulongT
+ wh = (ulongT)_width*_height,
+ whd = (ulongT)_width*_height*_depth,
+ sxy = (ulongT)sx*sy,
+ sxyz = (ulongT)sx*sy*sz;
+ if (sx==_width) off_x.fill(1);
+ else {
+ ulongT *poff_x = off_x._data, curr = 0;
+ cimg_forX(res,x) {
+ const ulongT old = curr;
+ curr = (ulongT)((x + 1.0)*_width/sx);
+ *(poff_x++) = curr - old;
+ }
+ }
+ if (sy==_height) off_y.fill(_width);
+ else {
+ ulongT *poff_y = off_y._data, curr = 0;
+ cimg_forY(res,y) {
+ const ulongT old = curr;
+ curr = (ulongT)((y + 1.0)*_height/sy);
+ *(poff_y++) = _width*(curr - old);
+ }
+ *poff_y = 0;
+ }
+ if (sz==_depth) off_z.fill(wh);
+ else {
+ ulongT *poff_z = off_z._data, curr = 0;
+ cimg_forZ(res,z) {
+ const ulongT old = curr;
+ curr = (ulongT)((z + 1.0)*_depth/sz);
+ *(poff_z++) = wh*(curr - old);
+ }
+ *poff_z = 0;
+ }
+ if (sc==_spectrum) off_c.fill(whd);
+ else {
+ ulongT *poff_c = off_c._data, curr = 0;
+ cimg_forC(res,c) {
+ const ulongT old = curr;
+ curr = (ulongT)((c + 1.0)*_spectrum/sc);
+ *(poff_c++) = whd*(curr - old);
+ }
+ *poff_c = 0;
+ }
+
+ T *ptrd = res._data;
+ const T* ptrc = _data;
+ const ulongT *poff_c = off_c._data;
+ for (unsigned int c = 0; c<sc; ) {
+ const T *ptrz = ptrc;
+ const ulongT *poff_z = off_z._data;
+ for (unsigned int z = 0; z<sz; ) {
+ const T *ptry = ptrz;
+ const ulongT *poff_y = off_y._data;
+ for (unsigned int y = 0; y<sy; ) {
+ const T *ptrx = ptry;
+ const ulongT *poff_x = off_x._data;
+ cimg_forX(res,x) { *(ptrd++) = *ptrx; ptrx+=*(poff_x++); }
+ ++y;
+ ulongT dy = *(poff_y++);
+ for ( ; !dy && y<dy; std::memcpy(ptrd,ptrd - sx,sizeof(T)*sx), ++y, ptrd+=sx, dy = *(poff_y++)) {}
+ ptry+=dy;
+ }
+ ++z;
+ ulongT dz = *(poff_z++);
+ for ( ; !dz && z<dz; std::memcpy(ptrd,ptrd-sxy,sizeof(T)*sxy), ++z, ptrd+=sxy, dz = *(poff_z++)) {}
+ ptrz+=dz;
+ }
+ ++c;
+ ulongT dc = *(poff_c++);
+ for ( ; !dc && c<dc; std::memcpy(ptrd,ptrd-sxyz,sizeof(T)*sxyz), ++c, ptrd+=sxyz, dc = *(poff_c++)) {}
+ ptrc+=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 = std::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 = std::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 = std::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 = std::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<doubleT> foff(off._width);
+ CImg<T> resx, resy, resz, resc;
+ double curr, old;
+
+ if (sx!=_width) {
+ if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+ else if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx);
+ else {
+ const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.0)/(sx - 1):0):
+ (double)_width/sx;
+ resx.assign(sx,_height,_depth,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forX(resx,x) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(width() - 1.0,curr + fx);
+ *(poff++) = (unsigned int)curr - (unsigned int)old;
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536))
+ cimg_forYZC(resx,y,z,c) {
+ const T *ptrs = data(0,y,z,c), *const ptrsmax = ptrs + _width - 1;
+ T *ptrd = resx.data(0,y,z,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forX(resx,x) {
+ const double alpha = *(pfoff++);
+ const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs + 1):val1;
+ *(ptrd++) = (T)((1 - alpha)*val1 + alpha*val2);
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ } else resx.assign(*this,true);
+
+ if (sy!=_height) {
+ if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+ else {
+ if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy);
+ else {
+ const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.0)/(sy - 1):0):
+ (double)_height/sy;
+ resy.assign(sx,sy,_depth,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forY(resy,y) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(height() - 1.0,curr + fy);
+ *(poff++) = sx*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536))
+ cimg_forXZC(resy,x,z,c) {
+ const T *ptrs = resx.data(x,0,z,c), *const ptrsmax = ptrs + (_height - 1)*sx;
+ T *ptrd = resy.data(x,0,z,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forY(resy,y) {
+ const double 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 {
+ if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz);
+ else {
+ const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.0)/(sz - 1):0):
+ (double)_depth/sz;
+ const unsigned int sxy = sx*sy;
+ resz.assign(sx,sy,sz,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forZ(resz,z) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(depth() - 1.0,curr + fz);
+ *(poff++) = sxy*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536))
+ cimg_forXYC(resz,x,y,c) {
+ const T *ptrs = resy.data(x,y,0,c), *const ptrsmax = ptrs + (_depth - 1)*sxy;
+ T *ptrd = resz.data(x,y,0,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forZ(resz,z) {
+ const double 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 {
+ if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc);
+ else {
+ const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.0)/(sc - 1):0):
+ (double)_spectrum/sc;
+ const unsigned int sxyz = sx*sy*sz;
+ resc.assign(sx,sy,sz,sc);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forC(resc,c) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(spectrum() - 1.0,curr + fc);
+ *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536))
+ cimg_forXYZ(resc,x,y,z) {
+ const T *ptrs = resz.data(x,y,z,0), *const ptrsmax = ptrs + (_spectrum - 1)*sxyz;
+ T *ptrd = resc.data(x,y,z,0);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forC(resc,c) {
+ const double 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;
+ if (sx!=_width) {
+ if (sx<_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+ else {
+ resx.assign(sx,_height,_depth,_spectrum,(T)0);
+ const int dx = (int)(2*sx), dy = 2*width();
+ int err = (int)(dy + centering_x*(sx*dy/width() - dy)), xs = 0;
+ cimg_forX(resx,x) if ((err-=dy)<=0) {
+ cimg_forYZC(resx,y,z,c) resx(x,y,z,c) = (*this)(xs,y,z,c);
+ ++xs;
+ err+=dx;
+ }
+ }
+ } 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,(T)0);
+ const int dx = (int)(2*sy), dy = 2*height();
+ int err = (int)(dy + centering_y*(sy*dy/height() - dy)), ys = 0;
+ cimg_forY(resy,y) if ((err-=dy)<=0) {
+ cimg_forXZC(resy,x,z,c) resy(x,y,z,c) = resx(x,ys,z,c);
+ ++ys;
+ err+=dx;
+ }
+ }
+ 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,(T)0);
+ const int dx = (int)(2*sz), dy = 2*depth();
+ int err = (int)(dy + centering_z*(sz*dy/depth() - dy)), zs = 0;
+ cimg_forZ(resz,z) if ((err-=dy)<=0) {
+ cimg_forXYC(resz,x,y,c) resz(x,y,z,c) = resy(x,y,zs,c);
+ ++zs;
+ err+=dx;
+ }
+ }
+ 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,(T)0);
+ const int dx = (int)(2*sc), dy = 2*spectrum();
+ int err = (int)(dy + centering_c*(sc*dy/spectrum() - dy)), cs = 0;
+ cimg_forC(resc,c) if ((err-=dy)<=0) {
+ cimg_forXYZ(resc,x,y,z) resc(x,y,z,c) = resz(x,y,z,cs);
+ ++cs;
+ err+=dx;
+ }
+ }
+ 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;
+
+ // Cubic interpolation.
+ //
+ case 5 : {
+ const Tfloat vmin = (Tfloat)cimg::type<T>::min(), vmax = (Tfloat)cimg::type<T>::max();
+ CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+ CImg<doubleT> foff(off._width);
+ CImg<T> resx, resy, resz, resc;
+ double curr, old;
+
+ if (sx!=_width) {
+ if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+ else {
+ if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx);
+ else {
+ const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.0)/(sx - 1):0):
+ (double)_width/sx;
+ resx.assign(sx,_height,_depth,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forX(resx,x) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(width() - 1.0,curr + fx);
+ *(poff++) = (unsigned int)curr - (unsigned int)old;
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536))
+ cimg_forYZC(resx,y,z,c) {
+ const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_width - 2);
+ T *ptrd = resx.data(0,y,z,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forX(resx,x) {
+ const double
+ t = *(pfoff++),
+ val1 = (double)*ptrs,
+ val0 = ptrs>ptrs0?(double)*(ptrs - 1):val1,
+ val2 = ptrs<=ptrsmax?(double)*(ptrs + 1):val1,
+ val3 = ptrs<ptrsmax?(double)*(ptrs + 2):val2,
+ val = val1 + 0.5f*(t*(-val0 + val2) + t*t*(2*val0 - 5*val1 + 4*val2 - val3) +
+ t*t*t*(-val0 + 3*val1 - 3*val2 + val3));
+ *(ptrd++) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ } else resx.assign(*this,true);
+
+ if (sy!=_height) {
+ if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+ else {
+ if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy);
+ else {
+ const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.0)/(sy - 1):0):
+ (double)_height/sy;
+ resy.assign(sx,sy,_depth,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forY(resy,y) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(height() - 1.0,curr + fy);
+ *(poff++) = sx*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536))
+ cimg_forXZC(resy,x,z,c) {
+ const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_height - 2)*sx;
+ T *ptrd = resy.data(x,0,z,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forY(resy,y) {
+ const double
+ t = *(pfoff++),
+ val1 = (double)*ptrs,
+ val0 = ptrs>ptrs0?(double)*(ptrs - sx):val1,
+ val2 = ptrs<=ptrsmax?(double)*(ptrs + sx):val1,
+ val3 = ptrs<ptrsmax?(double)*(ptrs + 2*sx):val2,
+ val = val1 + 0.5f*(t*(-val0 + val2) + t*t*(2*val0 - 5*val1 + 4*val2 - val3) +
+ t*t*t*(-val0 + 3*val1 - 3*val2 + val3));
+ *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrd+=sx;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ resx.assign();
+ } else resy.assign(resx,true);
+
+ if (sz!=_depth) {
+ if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+ else {
+ if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz);
+ else {
+ const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.0)/(sz - 1):0):
+ (double)_depth/sz;
+ const unsigned int sxy = sx*sy;
+ resz.assign(sx,sy,sz,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forZ(resz,z) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(depth() - 1.0,curr + fz);
+ *(poff++) = sxy*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536))
+ cimg_forXYC(resz,x,y,c) {
+ const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmax = ptrs + (_depth - 2)*sxy;
+ T *ptrd = resz.data(x,y,0,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forZ(resz,z) {
+ const double
+ t = *(pfoff++),
+ val1 = (double)*ptrs,
+ val0 = ptrs>ptrs0?(double)*(ptrs - sxy):val1,
+ val2 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val1,
+ val3 = ptrs<ptrsmax?(double)*(ptrs + 2*sxy):val2,
+ val = val1 + 0.5f*(t*(-val0 + val2) + t*t*(2*val0 - 5*val1 + 4*val2 - val3) +
+ t*t*t*(-val0 + 3*val1 - 3*val2 + val3));
+ *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrd+=sxy;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ resy.assign();
+ } else resz.assign(resy,true);
+
+ if (sc!=_spectrum) {
+ if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+ else {
+ if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc);
+ else {
+ const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.0)/(sc - 1):0):
+ (double)_spectrum/sc;
+ const unsigned int sxyz = sx*sy*sz;
+ resc.assign(sx,sy,sz,sc);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forC(resc,c) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(spectrum() - 1.0,curr + fc);
+ *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536))
+ cimg_forXYZ(resc,x,y,z) {
+ const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmax = ptrs + (_spectrum - 2)*sxyz;
+ T *ptrd = resc.data(x,y,z,0);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forC(resc,c) {
+ const double
+ t = *(pfoff++),
+ val1 = (double)*ptrs,
+ val0 = ptrs>ptrs0?(double)*(ptrs - sxyz):val1,
+ val2 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val1,
+ val3 = ptrs<ptrsmax?(double)*(ptrs + 2*sxyz):val2,
+ val = val1 + 0.5f*(t*(-val0 + val2) + t*t*(2*val0 - 5*val1 + 4*val2 - val3) +
+ t*t*t*(-val0 + 3*val1 - 3*val2 + val3));
+ *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrd+=sxyz;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ resz.assign();
+ } else resc.assign(resz,true);
+
+ return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+ } break;
+
+ // Lanczos interpolation.
+ //
+ case 6 : {
+ const double vmin = (double)cimg::type<T>::min(), vmax = (double)cimg::type<T>::max();
+ CImg<uintT> off(cimg::max(sx,sy,sz,sc));
+ CImg<doubleT> foff(off._width);
+ CImg<T> resx, resy, resz, resc;
+ double curr, old;
+
+ if (sx!=_width) {
+ if (_width==1) get_resize(sx,_height,_depth,_spectrum,1).move_to(resx);
+ else {
+ if (_width>sx) get_resize(sx,_height,_depth,_spectrum,2).move_to(resx);
+ else {
+ const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.0)/(sx - 1):0):
+ (double)_width/sx;
+ resx.assign(sx,_height,_depth,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forX(resx,x) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(width() - 1.0,curr + fx);
+ *(poff++) = (unsigned int)curr - (unsigned int)old;
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536))
+ cimg_forYZC(resx,y,z,c) {
+ const T *const ptrs0 = data(0,y,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + 1,
+ *const ptrsmax = ptrs0 + (_width - 2);
+ T *ptrd = resx.data(0,y,z,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forX(resx,x) {
+ const double
+ 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),
+ val2 = (double)*ptrs,
+ val1 = ptrs>=ptrsmin?(double)*(ptrs - 1):val2,
+ val0 = ptrs>ptrsmin?(double)*(ptrs - 2):val1,
+ val3 = ptrs<=ptrsmax?(double)*(ptrs + 1):val2,
+ val4 = ptrs<ptrsmax?(double)*(ptrs + 2):val3,
+ val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+ *(ptrd++) = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ } else resx.assign(*this,true);
+
+ if (sy!=_height) {
+ if (_height==1) resx.get_resize(sx,sy,_depth,_spectrum,1).move_to(resy);
+ else {
+ if (_height>sy) resx.get_resize(sx,sy,_depth,_spectrum,2).move_to(resy);
+ else {
+ const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.0)/(sy - 1):0):
+ (double)_height/sy;
+ resy.assign(sx,sy,_depth,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forY(resy,y) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(height() - 1.0,curr + fy);
+ *(poff++) = sx*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536))
+ cimg_forXZC(resy,x,z,c) {
+ const T *const ptrs0 = resx.data(x,0,z,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sx,
+ *const ptrsmax = ptrs0 + (_height - 2)*sx;
+ T *ptrd = resy.data(x,0,z,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forY(resy,y) {
+ const double
+ 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),
+ val2 = (double)*ptrs,
+ val1 = ptrs>=ptrsmin?(double)*(ptrs - sx):val2,
+ val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sx):val1,
+ val3 = ptrs<=ptrsmax?(double)*(ptrs + sx):val2,
+ val4 = ptrs<ptrsmax?(double)*(ptrs + 2*sx):val3,
+ val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+ *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrd+=sx;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ resx.assign();
+ } else resy.assign(resx,true);
+
+ if (sz!=_depth) {
+ if (_depth==1) resy.get_resize(sx,sy,sz,_spectrum,1).move_to(resz);
+ else {
+ if (_depth>sz) resy.get_resize(sx,sy,sz,_spectrum,2).move_to(resz);
+ else {
+ const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.0)/(sz - 1):0):
+ (double)_depth/sz;
+ const unsigned int sxy = sx*sy;
+ resz.assign(sx,sy,sz,_spectrum);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forZ(resz,z) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(depth() - 1.0,curr + fz);
+ *(poff++) = sxy*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536))
+ cimg_forXYC(resz,x,y,c) {
+ const T *const ptrs0 = resy.data(x,y,0,c), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxy,
+ *const ptrsmax = ptrs0 + (_depth - 2)*sxy;
+ T *ptrd = resz.data(x,y,0,c);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forZ(resz,z) {
+ const double
+ 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),
+ val2 = (double)*ptrs,
+ val1 = ptrs>=ptrsmin?(double)*(ptrs - sxy):val2,
+ val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxy):val1,
+ val3 = ptrs<=ptrsmax?(double)*(ptrs + sxy):val2,
+ val4 = ptrs<ptrsmax?(double)*(ptrs + 2*sxy):val3,
+ val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+ *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrd+=sxy;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ resy.assign();
+ } else resz.assign(resy,true);
+
+ if (sc!=_spectrum) {
+ if (_spectrum==1) resz.get_resize(sx,sy,sz,sc,1).move_to(resc);
+ else {
+ if (_spectrum>sc) resz.get_resize(sx,sy,sz,sc,2).move_to(resc);
+ else {
+ const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.0)/(sc - 1):0):
+ (double)_spectrum/sc;
+ const unsigned int sxyz = sx*sy*sz;
+ resc.assign(sx,sy,sz,sc);
+ curr = old = 0;
+ unsigned int *poff = off._data;
+ double *pfoff = foff._data;
+ cimg_forC(resc,c) {
+ *(pfoff++) = curr - (unsigned int)curr;
+ old = curr;
+ curr = std::min(spectrum() - 1.0,curr + fc);
+ *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old);
+ }
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536))
+ cimg_forXYZ(resc,x,y,z) {
+ const T *const ptrs0 = resz.data(x,y,z,0), *ptrs = ptrs0, *const ptrsmin = ptrs0 + sxyz,
+ *const ptrsmax = ptrs + (_spectrum - 2)*sxyz;
+ T *ptrd = resc.data(x,y,z,0);
+ const unsigned int *poff = off._data;
+ const double *pfoff = foff._data;
+ cimg_forC(resc,c) {
+ const double
+ 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),
+ val2 = (double)*ptrs,
+ val1 = ptrs>=ptrsmin?(double)*(ptrs - sxyz):val2,
+ val0 = ptrs>ptrsmin?(double)*(ptrs - 2*sxyz):val1,
+ val3 = ptrs<=ptrsmax?(double)*(ptrs + sxyz):val2,
+ val4 = ptrs<ptrsmax?(double)*(ptrs + 2*sxyz):val3,
+ val = (val0*w0 + val1*w1 + val2*w2 + val3*w3 + val4*w4)/(w1 + w2 + w3 + w4);
+ *ptrd = (T)(val<vmin?vmin:val>vmax?vmax:val);
+ ptrd+=sxyz;
+ ptrs+=*(poff++);
+ }
+ }
+ }
+ }
+ resz.assign();
+ } else resc.assign(resz,true);
+
+ return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc;
+ } break;
+
+ // Unknow interpolation.
+ //
+ default :
+ throw CImgArgumentException(_cimg_instance
+ "resize(): Invalid specified interpolation %d "
+ "(should be { -1=raw | 0=none | 1=nearest | 2=average | 3=linear | 4=grid | "
+ "5=cubic | 6=lanczos }).",
+ cimg_instance,
+ interpolation_type);
+ }
+ return res;
+ }
+
+ //! Resize image to dimensions of another image.
+ /**
+ \param src Reference image used for dimensions.
+ \param interpolation_type Interpolation method.
+ \param boundary_conditions Boundary conditions.
+ \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).
+ **/
+ template<typename t>
+ CImg<T>& resize(const CImg<t>& src,
+ const int interpolation_type=1, const unsigned int boundary_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,boundary_conditions,
+ centering_x,centering_y,centering_z,centering_c);
+ }
+
+ //! Resize image to dimensions of another image \newinstance.
+ template<typename t>
+ CImg<T> get_resize(const CImg<t>& src,
+ const int interpolation_type=1, const unsigned int boundary_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,boundary_conditions,
+ centering_x,centering_y,centering_z,centering_c);
+ }
+
+ //! Resize image to dimensions of a display window.
+ /**
+ \param disp Reference display window used for dimensions.
+ \param interpolation_type Interpolation method.
+ \param boundary_conditions Boundary conditions.
+ \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).
+ **/
+ CImg<T>& resize(const CImgDisplay& disp,
+ const int interpolation_type=1, const unsigned int boundary_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,boundary_conditions,
+ centering_x,centering_y,centering_z,centering_c);
+ }
+
+ //! Resize image to dimensions of a display window \newinstance.
+ CImg<T> get_resize(const CImgDisplay& disp,
+ const int interpolation_type=1, const unsigned int boundary_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,boundary_conditions,
+ centering_x,centering_y,centering_z,centering_c);
+ }
+
+ //! Resize image to half-size along XY axes, using an optimized filter.
+ CImg<T>& resize_halfXY() {
+ return get_resize_halfXY().move_to(*this);
+ }
+
+ //! Resize image to half-size along XY axes, using an optimized filter \newinstance.
+ CImg<T> get_resize_halfXY() const {
+ if (is_empty()) return *this;
+ static const Tfloat kernel[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f,
+ 0.1231940459f, 0.1935127547f, 0.1231940459f,
+ 0.07842776544f, 0.1231940459f, 0.07842776544f };
+ CImg<T> I(9), 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]*kernel[0] + I[1]*kernel[1] + I[2]*kernel[2] +
+ I[3]*kernel[3] + I[4]*kernel[4] + I[5]*kernel[5] +
+ I[6]*kernel[6] + I[7]*kernel[7] + I[8]*kernel[8]);
+ return res;
+ }
+
+ //! Resize image to double-size, using the Scale2X algorithm.
+ /**
+ \note Use anisotropic upscaling algorithm
+ <a href="http://scale2x.sourceforge.net/algorithm.html">described here</a>.
+ **/
+ CImg<T>& resize_doubleXY() {
+ return get_resize_doubleXY().move_to(*this);
+ }
+
+ //! Resize image to double-size, using the Scale2X algorithm \newinstance.
+ CImg<T> get_resize_doubleXY() const {
+#define _cimg_gs2x_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound) - 1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ _p1##i = i++, ++_n1##i, ptrd1+=(res)._width, ptrd2+=(res)._width)
+
+#define _cimg_gs2x_for3x3(img,x,y,z,c,I,T) \
+ _cimg_gs2x_for3((img)._height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+ (I[7] = (T)(img)(0,_n1##y,z,c)), \
+ 1>=(img)._width?(img).width() - 1:1); \
+ (_n1##x<(img).width() && ( \
+ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,z,c)), \
+ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+ x==--_n1##x; \
+ I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+ if (is_empty()) return *this;
+ CImg<T> res(_width<<1,_height<<1,_depth,_spectrum);
+ CImg_3x3(I,T);
+ cimg_forZC(*this,z,c) {
+ T
+ *ptrd1 = res.data(0,0,z,c),
+ *ptrd2 = ptrd1 + res._width;
+ _cimg_gs2x_for3x3(*this,x,y,z,c,I,T) {
+ if (Icp!=Icn && Ipc!=Inc) {
+ *(ptrd1++) = Ipc==Icp?Ipc:Icc;
+ *(ptrd1++) = Icp==Inc?Inc:Icc;
+ *(ptrd2++) = Ipc==Icn?Ipc:Icc;
+ *(ptrd2++) = Icn==Inc?Inc:Icc;
+ } else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; }
+ }
+ }
+ return res;
+ }
+
+ //! Resize image to triple-size, using the Scale3X algorithm.
+ /**
+ \note Use anisotropic upscaling algorithm
+ <a href="http://scale2x.sourceforge.net/algorithm.html">described here</a>.
+ **/
+ CImg<T>& resize_tripleXY() {
+ return get_resize_tripleXY().move_to(*this);
+ }
+
+ //! Resize image to triple-size, using the Scale3X algorithm \newinstance.
+ CImg<T> get_resize_tripleXY() const {
+#define _cimg_gs3x_for3(bound,i) \
+ for (int i = 0, _p1##i = 0, \
+ _n1##i = 1>=(bound)?(int)(bound) - 1:1; \
+ _n1##i<(int)(bound) || i==--_n1##i; \
+ _p1##i = i++, ++_n1##i, ptrd1+=2*(res)._width, ptrd2+=2*(res)._width, ptrd3+=2*(res)._width)
+
+#define _cimg_gs3x_for3x3(img,x,y,z,c,I,T) \
+ _cimg_gs3x_for3((img)._height,y) for (int x = 0, \
+ _p1##x = 0, \
+ _n1##x = (int)( \
+ (I[0] = I[1] = (T)(img)(_p1##x,_p1##y,z,c)), \
+ (I[3] = I[4] = (T)(img)(0,y,z,c)), \
+ (I[6] = I[7] = (T)(img)(0,_n1##y,z,c)), \
+ 1>=(img)._width?(img).width() - 1:1); \
+ (_n1##x<(img).width() && ( \
+ (I[2] = (T)(img)(_n1##x,_p1##y,z,c)), \
+ (I[5] = (T)(img)(_n1##x,y,z,c)), \
+ (I[8] = (T)(img)(_n1##x,_n1##y,z,c)),1)) || \
+ x==--_n1##x; \
+ I[0] = I[1], I[1] = I[2], \
+ I[3] = I[4], I[4] = I[5], \
+ I[6] = I[7], I[7] = I[8], \
+ _p1##x = x++, ++_n1##x)
+
+ if (is_empty()) return *this;
+ CImg<T> res(3*_width,3*_height,_depth,_spectrum);
+ CImg_3x3(I,T);
+ cimg_forZC(*this,z,c) {
+ T
+ *ptrd1 = res.data(0,0,z,c),
+ *ptrd2 = ptrd1 + res._width,
+ *ptrd3 = ptrd2 + res._width;
+ _cimg_gs3x_for3x3(*this,x,y,z,c,I,T) {
+ if (Icp != Icn && Ipc != Inc) {
+ *(ptrd1++) = Ipc==Icp?Ipc:Icc;
+ *(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc;
+ *(ptrd1++) = Icp==Inc?Inc:Icc;
+ *(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc;
+ *(ptrd2++) = Icc;
+ *(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc;
+ *(ptrd3++) = Ipc==Icn?Ipc:Icc;
+ *(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc;
+ *(ptrd3++) = Icn==Inc?Inc:Icc;
+ } else {
+ *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc;
+ *(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc;
+ *(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc;
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Mirror image content along specified axis.
+ /**
+ \param axis Mirror axis
+ **/
+ CImg<T>& mirror(const char axis) {
+ if (is_empty()) return *this;
+ T *pf, *pb, *buf = 0;
+ switch (cimg::lowercase(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+=(ulongT)_width*(_height - height2);
+ pb+=(ulongT)_width*(_height + height2);
+ }
+ } break;
+ case 'z' : {
+ buf = new T[(ulongT)_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+=(ulongT)_width*_height;
+ pb-=(ulongT)_width*_height;
+ }
+ pf+=(ulongT)_width*_height*(_depth - depth2);
+ pb+=(ulongT)_width*_height*(_depth + depth2);
+ }
+ } break;
+ case 'c' : {
+ buf = new T[(ulongT)_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+=(ulongT)_width*_height*_depth;
+ pb-=(ulongT)_width*_height*_depth;
+ }
+ } break;
+ default :
+ throw CImgArgumentException(_cimg_instance
+ "mirror(): Invalid specified axis '%c'.",
+ cimg_instance,
+ axis);
+ }
+ delete[] buf;
+ return *this;
+ }
+
+ //! Mirror image content along specified axis \newinstance.
+ CImg<T> get_mirror(const char axis) const {
+ return (+*this).mirror(axis);
+ }
+
+ //! Mirror image content along specified axes.
+ /**
+ \param axes Mirror axes, as a C-string.
+ \note \c axes may contains multiple characters, e.g. \c "xyz"
+ **/
+ CImg<T>& mirror(const char *const axes) {
+ for (const char *s = axes; *s; ++s) mirror(*s);
+ return *this;
+ }
+
+ //! Mirror image content along specified axes \newinstance.
+ CImg<T> get_mirror(const char *const axes) const {
+ return (+*this).mirror(axes);
+ }
+
+ //! Shift image content.
+ /**
+ \param delta_x Amount of displacement along the X-axis.
+ \param delta_y Amount of displacement along the Y-axis.
+ \param delta_z Amount of displacement along the Z-axis.
+ \param delta_c Amount of displacement along the C-axis.
+ \param boundary_conditions Border condition. Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }.
+ **/
+ CImg<T>& shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0,
+ const unsigned int boundary_conditions=0) {
+ if (is_empty()) return *this;
+ if (boundary_conditions==3)
+ return get_crop(-delta_x,-delta_y,-delta_z,-delta_c,
+ width() - delta_x - 1,
+ height() - delta_y - 1,
+ depth() - delta_z - 1,
+ spectrum() - delta_c - 1,3).move_to(*this);
+ if (delta_x) // Shift along X-axis
+ switch (boundary_conditions) {
+ case 2 : { // Periodic
+ const int ml = cimg::mod(-delta_x,width()), ndelta_x = (ml<=width()/2)?ml:(ml-width());
+ if (!ndelta_x) return *this;
+ CImg<T> buf(cimg::abs(ndelta_x));
+ if (ndelta_x>0) cimg_forYZC(*this,y,z,c) {
+ std::memcpy(buf,data(0,y,z,c),ndelta_x*sizeof(T));
+ std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T));
+ std::memcpy(data(_width-ndelta_x,y,z,c),buf,ndelta_x*sizeof(T));
+ } else cimg_forYZC(*this,y,z,c) {
+ std::memcpy(buf,data(_width + ndelta_x,y,z,c),-ndelta_x*sizeof(T));
+ std::memmove(data(-ndelta_x,y,z,c),data(0,y,z,c),(_width + ndelta_x)*sizeof(T));
+ std::memcpy(data(0,y,z,c),buf,-ndelta_x*sizeof(T));
+ }
+ } break;
+ case 1 : // Neumann
+ if (delta_x<0) {
+ const int ndelta_x = (-delta_x>=width())?width() - 1:-delta_x;
+ if (!ndelta_x) return *this;
+ cimg_forYZC(*this,y,z,c) {
+ std::memmove(data(0,y,z,c),data(ndelta_x,y,z,c),(_width-ndelta_x)*sizeof(T));
+ T *ptrd = data(_width - 1,y,z,c);
+ const T val = *ptrd;
+ for (int l = 0; l<ndelta_x - 1; ++l) *(--ptrd) = val;
+ }
+ } else {
+ const int ndelta_x = (delta_x>=width())?width() - 1:delta_x;
+ if (!ndelta_x) return *this;
+ cimg_forYZC(*this,y,z,c) {
+ std::memmove(data(ndelta_x,y,z,c),data(0,y,z,c),(_width-ndelta_x)*sizeof(T));
+ T *ptrd = data(0,y,z,c);
+ const T val = *ptrd;
+ for (int l = 0; l<ndelta_x - 1; ++l) *(++ptrd) = val;
+ }
+ }
+ break;
+ default : // Dirichlet
+ if (cimg::abs(delta_x)>=width()) return fill((T)0);
+ if (delta_x<0) cimg_forYZC(*this,y,z,c) {
+ std::memmove(data(0,y,z,c),data(-delta_x,y,z,c),(_width + delta_x)*sizeof(T));
+ std::memset(data(_width + delta_x,y,z,c),0,-delta_x*sizeof(T));
+ } else cimg_forYZC(*this,y,z,c) {
+ std::memmove(data(delta_x,y,z,c),data(0,y,z,c),(_width-delta_x)*sizeof(T));
+ std::memset(data(0,y,z,c),0,delta_x*sizeof(T));
+ }
+ }
+
+ if (delta_y) // Shift along Y-axis
+ switch (boundary_conditions) {
+ case 2 : { // Periodic
+ const int ml = cimg::mod(-delta_y,height()), ndelta_y = (ml<=height()/2)?ml:(ml-height());
+ if (!ndelta_y) return *this;
+ CImg<T> buf(width(),cimg::abs(ndelta_y));
+ if (ndelta_y>0) cimg_forZC(*this,z,c) {
+ std::memcpy(buf,data(0,0,z,c),_width*ndelta_y*sizeof(T));
+ std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T));
+ std::memcpy(data(0,_height-ndelta_y,z,c),buf,_width*ndelta_y*sizeof(T));
+ } else cimg_forZC(*this,z,c) {
+ std::memcpy(buf,data(0,_height + ndelta_y,z,c),-ndelta_y*_width*sizeof(T));
+ std::memmove(data(0,-ndelta_y,z,c),data(0,0,z,c),_width*(_height + ndelta_y)*sizeof(T));
+ std::memcpy(data(0,0,z,c),buf,-ndelta_y*_width*sizeof(T));
+ }
+ } break;
+ case 1 : // Neumann
+ if (delta_y<0) {
+ const int ndelta_y = (-delta_y>=height())?height() - 1:-delta_y;
+ if (!ndelta_y) return *this;
+ cimg_forZC(*this,z,c) {
+ std::memmove(data(0,0,z,c),data(0,ndelta_y,z,c),_width*(_height-ndelta_y)*sizeof(T));
+ T *ptrd = data(0,_height-ndelta_y,z,c), *ptrs = data(0,_height - 1,z,c);
+ for (int l = 0; l<ndelta_y - 1; ++l) { std::memcpy(ptrd,ptrs,_width*sizeof(T)); ptrd+=_width; }
+ }
+ } else {
+ const int ndelta_y = (delta_y>=height())?height() - 1:delta_y;
+ if (!ndelta_y) return *this;
+ cimg_forZC(*this,z,c) {
+ std::memmove(data(0,ndelta_y,z,c),data(0,0,z,c),_width*(_height-ndelta_y)*sizeof(T));
+ T *ptrd = data(0,1,z,c), *ptrs = data(0,0,z,c);
+ for (int l = 0; l<ndelta_y - 1; ++l) { std::memcpy(ptrd,ptrs,_width*sizeof(T)); ptrd+=_width; }
+ }
+ }
+ break;
+ default : // Dirichlet
+ if (cimg::abs(delta_y)>=height()) return fill((T)0);
+ if (delta_y<0) cimg_forZC(*this,z,c) {
+ std::memmove(data(0,0,z,c),data(0,-delta_y,z,c),_width*(_height + delta_y)*sizeof(T));
+ std::memset(data(0,_height + delta_y,z,c),0,-delta_y*_width*sizeof(T));
+ } else cimg_forZC(*this,z,c) {
+ std::memmove(data(0,delta_y,z,c),data(0,0,z,c),_width*(_height-delta_y)*sizeof(T));
+ std::memset(data(0,0,z,c),0,delta_y*_width*sizeof(T));
+ }
+ }
+
+ if (delta_z) // Shift along Z-axis
+ switch (boundary_conditions) {
+ case 2 : { // Periodic
+ const int ml = cimg::mod(-delta_z,depth()), ndelta_z = (ml<=depth()/2)?ml:(ml-depth());
+ if (!ndelta_z) return *this;
+ CImg<T> buf(width(),height(),cimg::abs(ndelta_z));
+ if (ndelta_z>0) cimg_forC(*this,c) {
+ std::memcpy(buf,data(0,0,0,c),_width*_height*ndelta_z*sizeof(T));
+ std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T));
+ std::memcpy(data(0,0,_depth-ndelta_z,c),buf,_width*_height*ndelta_z*sizeof(T));
+ } else cimg_forC(*this,c) {
+ std::memcpy(buf,data(0,0,_depth + ndelta_z,c),-ndelta_z*_width*_height*sizeof(T));
+ std::memmove(data(0,0,-ndelta_z,c),data(0,0,0,c),_width*_height*(_depth + ndelta_z)*sizeof(T));
+ std::memcpy(data(0,0,0,c),buf,-ndelta_z*_width*_height*sizeof(T));
+ }
+ } break;
+ case 1 : // Neumann
+ if (delta_z<0) {
+ const int ndelta_z = (-delta_z>=depth())?depth() - 1:-delta_z;
+ if (!ndelta_z) return *this;
+ cimg_forC(*this,c) {
+ std::memmove(data(0,0,0,c),data(0,0,ndelta_z,c),_width*_height*(_depth-ndelta_z)*sizeof(T));
+ T *ptrd = data(0,0,_depth-ndelta_z,c), *ptrs = data(0,0,_depth - 1,c);
+ for (int l = 0; l<ndelta_z - 1; ++l) {
+ std::memcpy(ptrd,ptrs,_width*_height*sizeof(T)); ptrd+=(ulongT)_width*_height;
+ }
+ }
+ } else {
+ const int ndelta_z = (delta_z>=depth())?depth() - 1:delta_z;
+ if (!ndelta_z) return *this;
+ cimg_forC(*this,c) {
+ std::memmove(data(0,0,ndelta_z,c),data(0,0,0,c),_width*_height*(_depth-ndelta_z)*sizeof(T));
+ T *ptrd = data(0,0,1,c), *ptrs = data(0,0,0,c);
+ for (int l = 0; l<ndelta_z - 1; ++l) {
+ std::memcpy(ptrd,ptrs,_width*_height*sizeof(T)); ptrd+=(ulongT)_width*_height;
+ }
+ }
+ }
+ break;
+ default : // Dirichlet
+ if (cimg::abs(delta_z)>=depth()) return fill((T)0);
+ if (delta_z<0) cimg_forC(*this,c) {
+ std::memmove(data(0,0,0,c),data(0,0,-delta_z,c),_width*_height*(_depth + delta_z)*sizeof(T));
+ std::memset(data(0,0,_depth + delta_z,c),0,_width*_height*(-delta_z)*sizeof(T));
+ } else cimg_forC(*this,c) {
+ std::memmove(data(0,0,delta_z,c),data(0,0,0,c),_width*_height*(_depth-delta_z)*sizeof(T));
+ std::memset(data(0,0,0,c),0,delta_z*_width*_height*sizeof(T));
+ }
+ }
+
+ if (delta_c) // Shift along C-axis
+ switch (boundary_conditions) {
+ case 2 : { // Periodic
+ const int ml = cimg::mod(-delta_c,spectrum()), ndelta_c = (ml<=spectrum()/2)?ml:(ml-spectrum());
+ if (!ndelta_c) return *this;
+ CImg<T> buf(width(),height(),depth(),cimg::abs(ndelta_c));
+ if (ndelta_c>0) {
+ std::memcpy(buf,_data,_width*_height*_depth*ndelta_c*sizeof(T));
+ std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T));
+ std::memcpy(data(0,0,0,_spectrum-ndelta_c),buf,_width*_height*_depth*ndelta_c*sizeof(T));
+ } else {
+ std::memcpy(buf,data(0,0,0,_spectrum + ndelta_c),-ndelta_c*_width*_height*_depth*sizeof(T));
+ std::memmove(data(0,0,0,-ndelta_c),_data,_width*_height*_depth*(_spectrum + ndelta_c)*sizeof(T));
+ std::memcpy(_data,buf,-ndelta_c*_width*_height*_depth*sizeof(T));
+ }
+ } break;
+ case 1 : // Neumann
+ if (delta_c<0) {
+ const int ndelta_c = (-delta_c>=spectrum())?spectrum() - 1:-delta_c;
+ if (!ndelta_c) return *this;
+ std::memmove(_data,data(0,0,0,ndelta_c),_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T));
+ T *ptrd = data(0,0,0,_spectrum-ndelta_c), *ptrs = data(0,0,0,_spectrum - 1);
+ for (int l = 0; l<ndelta_c - 1; ++l) {
+ std::memcpy(ptrd,ptrs,_width*_height*_depth*sizeof(T)); ptrd+=(ulongT)_width*_height*_depth;
+ }
+ } else {
+ const int ndelta_c = (delta_c>=spectrum())?spectrum() - 1:delta_c;
+ if (!ndelta_c) return *this;
+ std::memmove(data(0,0,0,ndelta_c),_data,_width*_height*_depth*(_spectrum-ndelta_c)*sizeof(T));
+ T *ptrd = data(0,0,0,1);
+ for (int l = 0; l<ndelta_c - 1; ++l) {
+ std::memcpy(ptrd,_data,_width*_height*_depth*sizeof(T)); ptrd+=(ulongT)_width*_height*_depth;
+ }
+ }
+ break;
+ default : // Dirichlet
+ if (cimg::abs(delta_c)>=spectrum()) return fill((T)0);
+ if (delta_c<0) {
+ std::memmove(_data,data(0,0,0,-delta_c),_width*_height*_depth*(_spectrum + delta_c)*sizeof(T));
+ std::memset(data(0,0,0,_spectrum + delta_c),0,_width*_height*_depth*(-delta_c)*sizeof(T));
+ } else {
+ std::memmove(data(0,0,0,delta_c),_data,_width*_height*_depth*(_spectrum-delta_c)*sizeof(T));
+ std::memset(_data,0,delta_c*_width*_height*_depth*sizeof(T));
+ }
+ }
+ return *this;
+ }
+
+ //! Shift image content \newinstance.
+ CImg<T> get_shift(const int delta_x, const int delta_y=0, const int delta_z=0, const int delta_c=0,
+ const unsigned int boundary_conditions=0) const {
+ return (+*this).shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions);
+ }
+
+ //! Permute axes order.
+ /**
+ \param order Axes permutations, as a C-string of 4 characters.
+ This function permutes image content regarding the specified axes permutation.
+ **/
+ CImg<T>& permute_axes(const char *const order) {
+ return get_permute_axes(order).move_to(*this);
+ }
+
+ //! Permute axes order \newinstance.
+ CImg<T> get_permute_axes(const char *const order) const {
+ const T foo = (T)0;
+ return _permute_axes(order,foo);
+ }
+
+ template<typename t>
+ CImg<t> _permute_axes(const char *const order, const t&) const {
+ if (is_empty() || !order) return CImg<t>(*this,false);
+ CImg<t> res;
+ const T* ptrs = _data;
+ unsigned char s_code[4] = { 0,1,2,3 }, n_code[4] = { 0 };
+ for (unsigned int l = 0; order[l]; ++l) {
+ int c = cimg::lowercase(order[l]);
+ if (c!='x' && c!='y' && c!='z' && c!='c') { *s_code = 4; break; }
+ else { ++n_code[c%=4]; s_code[l] = c; }
+ }
+ if (*order && *s_code<4 && *n_code<=1 && n_code[1]<=1 && n_code[2]<=1 && n_code[3]<=1) {
+ const unsigned int code = (s_code[0]<<12) | (s_code[1]<<8) | (s_code[2]<<4) | (s_code[3]);
+ ulongT wh, whd;
+ switch (code) {
+ case 0x0123 : // xyzc
+ return +*this;
+ case 0x0132 : // xycz
+ res.assign(_width,_height,_spectrum,_depth);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(x,y,c,z,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x0213 : // xzyc
+ res.assign(_width,_depth,_height,_spectrum);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(x,z,y,c,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x0231 : // xzcy
+ res.assign(_width,_depth,_spectrum,_height);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(x,z,c,y,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x0312 : // xcyz
+ res.assign(_width,_spectrum,_height,_depth);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(x,c,y,z,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x0321 : // xczy
+ res.assign(_width,_spectrum,_depth,_height);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(x,c,z,y,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x1023 : // yxzc
+ res.assign(_height,_width,_depth,_spectrum);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(y,x,z,c,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x1032 : // yxcz
+ res.assign(_height,_width,_spectrum,_depth);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(y,x,c,z,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x1203 : // yzxc
+ res.assign(_height,_depth,_width,_spectrum);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(y,z,x,c,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x1230 : // yzcx
+ 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[0];
+ *(ptr_g++) = (t)ptrs[1];
+ ptrs+=2;
+ }
+ } 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[0];
+ *(ptr_g++) = (t)ptrs[1];
+ *(ptr_b++) = (t)ptrs[2];
+ ptrs+=3;
+ }
+ } 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[0];
+ *(ptr_g++) = (t)ptrs[1];
+ *(ptr_b++) = (t)ptrs[2];
+ *(ptr_a++) = (t)ptrs[3];
+ ptrs+=4;
+ }
+ } break;
+ default : {
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(y,z,c,x,wh,whd) = *(ptrs++);
+ return res;
+ }
+ }
+ break;
+ case 0x1302 : // ycxz
+ res.assign(_height,_spectrum,_width,_depth);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(y,c,x,z,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x1320 : // yczx
+ res.assign(_height,_spectrum,_depth,_width);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(y,c,z,x,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x2013 : // zxyc
+ res.assign(_depth,_width,_height,_spectrum);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(z,x,y,c,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x2031 : // zxcy
+ res.assign(_depth,_width,_spectrum,_height);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(z,x,c,y,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x2103 : // zyxc
+ res.assign(_depth,_height,_width,_spectrum);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(z,y,x,c,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x2130 : // zycx
+ res.assign(_depth,_height,_spectrum,_width);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(z,y,c,x,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x2301 : // zcxy
+ res.assign(_depth,_spectrum,_width,_height);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(z,c,x,y,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x2310 : // zcyx
+ res.assign(_depth,_spectrum,_height,_width);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(z,c,y,x,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x3012 : // cxyz
+ res.assign(_spectrum,_width,_height,_depth);
+ switch (_spectrum) {
+ case 1 : {
+ const T *ptr_r = data(0,0,0,0);
+ t *ptrd = res._data;
+ for (ulongT siz = (ulongT)_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 (ulongT siz = (ulongT)_width*_height*_depth; siz; --siz) {
+ ptrd[0] = (t)*(ptr_r++);
+ ptrd[1] = (t)*(ptr_g++);
+ ptrd+=2;
+ }
+ } 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 (ulongT siz = (ulongT)_width*_height*_depth; siz; --siz) {
+ ptrd[0] = (t)*(ptr_r++);
+ ptrd[1] = (t)*(ptr_g++);
+ ptrd[2] = (t)*(ptr_b++);
+ ptrd+=3;
+ }
+ } 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 (ulongT siz = (ulongT)_width*_height*_depth; siz; --siz) {
+ ptrd[0] = (t)*(ptr_r++);
+ ptrd[1] = (t)*(ptr_g++);
+ ptrd[2] = (t)*(ptr_b++);
+ ptrd[3] = (t)*(ptr_a++);
+ ptrd+=4;
+ }
+ } break;
+ default : {
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(c,x,y,z,wh,whd) = (t)*(ptrs++);
+ }
+ }
+ break;
+ case 0x3021 : // cxzy
+ res.assign(_spectrum,_width,_depth,_height);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(c,x,z,y,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x3102 : // cyxz
+ res.assign(_spectrum,_height,_width,_depth);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(c,y,x,z,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x3120 : // cyzx
+ res.assign(_spectrum,_height,_depth,_width);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(c,y,z,x,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x3201 : // czxy
+ res.assign(_spectrum,_depth,_width,_height);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(c,z,x,y,wh,whd) = (t)*(ptrs++);
+ break;
+ case 0x3210 : // czyx
+ res.assign(_spectrum,_depth,_height,_width);
+ wh = (ulongT)res._width*res._height; whd = wh*res._depth;
+ cimg_forXYZC(*this,x,y,z,c) res(c,z,y,x,wh,whd) = (t)*(ptrs++);
+ break;
+ }
+ }
+ if (!res)
+ throw CImgArgumentException(_cimg_instance
+ "permute_axes(): Invalid specified permutation '%s'.",
+ cimg_instance,
+ order);
+ return res;
+ }
+
+ //! Unroll pixel values along specified axis.
+ /**
+ \param axis Unroll axis (can be \c 'x', \c 'y', \c 'z' or c 'c').
+ **/
+ CImg<T>& unroll(const char axis) {
+ const unsigned int siz = (unsigned int)size();
+ if (siz) switch (cimg::lowercase(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;
+ }
+
+ //! Unroll pixel values along specified axis \newinstance.
+ CImg<T> get_unroll(const char axis) const {
+ return (+*this).unroll(axis);
+ }
+
+ //! Rotate image with arbitrary angle.
+ /**
+ \param angle Rotation angle, in degrees.
+ \param interpolation Type of interpolation. Can be <tt>{ 0=nearest | 1=linear | 2=cubic }</tt>.
+ \param boundary Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }</tt>.
+ \note Most of the time, size of the image is modified.
+ **/
+ CImg<T>& rotate(const float angle, const unsigned int interpolation=1,
+ const unsigned int boundary_conditions=0) {
+ const float nangle = cimg::mod(angle,360.0f);
+ if (nangle==0.0f) return *this;
+ return get_rotate(nangle,interpolation,boundary_conditions).move_to(*this);
+ }
+
+ //! Rotate image with arbitrary angle \newinstance.
+ CImg<T> get_rotate(const float angle, const unsigned int interpolation=1,
+ const unsigned int boundary_conditions=0) const {
+ if (is_empty()) return *this;
+ CImg<T> res;
+ const float nangle = cimg::mod(angle,360.0f);
+ if (boundary_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 : { // 90 deg
+ 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 : { // 180 deg
+ 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 : { // 270 deg
+ 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 : // 0 deg
+ return *this;
+ }
+ } else { // Generic angle
+ const float
+ rad = (float)(nangle*cimg::PI/180.0),
+ ca = (float)std::cos(rad), sa = (float)std::sin(rad),
+ ux = cimg::abs((_width - 1)*ca), uy = cimg::abs((_width - 1)*sa),
+ vx = cimg::abs((_height - 1)*sa), vy = cimg::abs((_height - 1)*ca),
+ w2 = 0.5f*(_width - 1), h2 = 0.5f*(_height - 1);
+ res.assign((int)cimg::round(1 + ux + vx),(int)cimg::round(1 + uy + vy),_depth,_spectrum);
+ const float rw2 = 0.5f*(res._width - 1), rh2 = 0.5f*(res._height - 1);
+ _rotate(res,nangle,interpolation,boundary_conditions,w2,h2,rw2,rh2);
+ }
+ return res;
+ }
+
+ //! Rotate image with arbitrary angle, around a center point.
+ /**
+ \param angle Rotation angle, in degrees.
+ \param cx X-coordinate of the rotation center.
+ \param cy Y-coordinate of the rotation center.
+ \param boundary_conditions Boundary conditions, <tt>{ 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }</tt>.
+ \param interpolation_type Type of interpolation, <tt>{ 0=nearest | 1=linear | 2=cubic | 3=mirror }</tt>.
+ **/
+ CImg<T>& rotate(const float angle, const float cx, const float cy,
+ const unsigned int interpolation, const unsigned int boundary_conditions=0) {
+ return get_rotate(angle,cx,cy,interpolation,boundary_conditions).move_to(*this);
+ }
+
+ //! Rotate image with arbitrary angle, around a center point \newinstance.
+ CImg<T> get_rotate(const float angle, const float cx, const float cy,
+ const unsigned int interpolation, const unsigned int boundary_conditions=0) const {
+ if (is_empty()) return *this;
+ CImg<T> res(_width,_height,_depth,_spectrum);
+ _rotate(res,angle,interpolation,boundary_conditions,cx,cy,cx,cy);
+ return res;
+ }
+
+ // [internal] Perform 2d rotation with arbitrary angle.
+ void _rotate(CImg<T>& res, const float angle,
+ const unsigned int interpolation, const unsigned int boundary_conditions,
+ const float w2, const float h2,
+ const float rw2, const float rh2) const {
+ const float
+ rad = (float)(angle*cimg::PI/180.0),
+ ca = (float)std::cos(rad), sa = (float)std::sin(rad);
+
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ const float ww = 2.0f*width(), hh = 2.0f*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2,
+ mx = cimg::mod(w2 + xc*ca + yc*sa,ww),
+ my = cimg::mod(h2 - xc*sa + yc*ca,hh);
+ const Tfloat val = _cubic_atXY(mx<width()?mx:ww - mx - 1,my<height()?my:hh - my - 1,z,c);
+ res(x,y,z,c) = cimg::type<T>::cut(val);
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ const float ww = 2.0f*width(), hh = 2.0f*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2,
+ mx = cimg::mod(w2 + xc*ca + yc*sa,ww),
+ my = cimg::mod(h2 - xc*sa + yc*ca,hh);
+ res(x,y,z,c) = (T)_linear_atXY(mx<width()?mx:ww - mx - 1,my<height()?my:hh - my - 1,z,c);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ const int ww = 2*width(), hh = 2*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2,
+ mx = cimg::mod((int)cimg::round(w2 + xc*ca + yc*sa),ww),
+ my = cimg::mod((int)cimg::round(h2 - xc*sa + yc*ca),hh);
+ res(x,y,z,c) = (*this)(mx<width()?mx:ww - mx - 1,my<height()?my:hh - my - 1,z,c);
+ }
+ }
+ }
+ } break;
+
+ case 2 : // Periodic
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ const Tfloat val = _cubic_atXY(cimg::mod(w2 + xc*ca + yc*sa,(float)width()),
+ cimg::mod(h2 - xc*sa + yc*ca,(float)height()),z,c);
+ res(x,y,z,c) = cimg::type<T>::cut(val);
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ res(x,y,z,c) = (T)_linear_atXY(cimg::mod(w2 + xc*ca + yc*sa,(float)width()),
+ cimg::mod(h2 - xc*sa + yc*ca,(float)height()),z,c);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ res(x,y,z,c) = (*this)(cimg::mod((int)cimg::round(w2 + xc*ca + yc*sa),(float)width()),
+ cimg::mod((int)cimg::round(h2 - xc*sa + yc*ca),(float)height()),z,c);
+ }
+ }
+ } break;
+
+ case 1 : // Neumann
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ const Tfloat val = _cubic_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c);
+ res(x,y,z,c) = cimg::type<T>::cut(val);
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ res(x,y,z,c) = (T)_linear_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ res(x,y,z,c) = _atXY((int)cimg::round(w2 + xc*ca + yc*sa),
+ (int)cimg::round(h2 - xc*sa + yc*ca),z,c);
+ }
+ }
+ } break;
+
+ default : // Dirichlet
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ const Tfloat val = cubic_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c,(T)0);
+ res(x,y,z,c) = cimg::type<T>::cut(val);
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ res(x,y,z,c) = (T)linear_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c,(T)0);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZC(res,x,y,z,c) {
+ const float xc = x - rw2, yc = y - rh2;
+ res(x,y,z,c) = atXY((int)cimg::round(w2 + xc*ca + yc*sa),
+ (int)cimg::round(h2 - xc*sa + yc*ca),z,c,(T)0);
+ }
+ }
+ }
+ }
+ }
+
+ //! Rotate volumetric image with arbitrary angle and axis.
+ /**
+ \param u X-coordinate of the 3d rotation axis.
+ \param v Y-coordinate of the 3d rotation axis.
+ \param w Z-coordinate of the 3d rotation axis.
+ \param angle Rotation angle, in degrees.
+ \param interpolation Type of interpolation. Can be <tt>{ 0=nearest | 1=linear | 2=cubic }</tt>.
+ \param boundary Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }</tt>.
+ \note Most of the time, size of the image is modified.
+ **/
+ CImg<T> rotate(const float u, const float v, const float w, const float angle,
+ const unsigned int interpolation, const unsigned int boundary_conditions) {
+ const float nangle = cimg::mod(angle,360.0f);
+ if (nangle==0.0f) return *this;
+ return get_rotate(u,v,w,nangle,interpolation,boundary_conditions).move_to(*this);
+ }
+
+ //! Rotate volumetric image with arbitrary angle and axis \newinstance.
+ CImg<T> get_rotate(const float u, const float v, const float w, const float angle,
+ const unsigned int interpolation, const unsigned int boundary_conditions) const {
+ if (is_empty()) return *this;
+ CImg<T> res;
+ const float
+ w1 = _width - 1, h1 = _height - 1, d1 = _depth -1,
+ w2 = 0.5f*w1, h2 = 0.5f*h1, d2 = 0.5f*d1;
+ CImg<floatT> R = CImg<floatT>::rotation_matrix(u,v,w,angle);
+ const CImg<Tfloat>
+ X = R*CImg<Tfloat>(8,3,1,1,
+ 0.0f,w1,w1,0.0f,0.0f,w1,w1,0.0f,
+ 0.0f,0.0f,h1,h1,0.0f,0.0f,h1,h1,
+ 0.0f,0.0f,0.0f,0.0f,d1,d1,d1,d1);
+ float
+ xm, xM = X.get_shared_row(0).max_min(xm),
+ ym, yM = X.get_shared_row(1).max_min(ym),
+ zm, zM = X.get_shared_row(2).max_min(zm);
+ const int
+ dx = (int)cimg::round(xM - xm),
+ dy = (int)cimg::round(yM - ym),
+ dz = (int)cimg::round(zM - zm);
+ R.transpose();
+ res.assign(1 + dx,1 + dy,1 + dz,_spectrum);
+ const float rw2 = 0.5f*dx, rh2 = 0.5f*dy, rd2 = 0.5f*dz;
+ _rotate(res,R,interpolation,boundary_conditions,w2,h2,d2,rw2,rh2,rd2);
+ return res;
+ }
+
+ //! Rotate volumetric image with arbitrary angle and axis, around a center point.
+ /**
+ \param u X-coordinate of the 3d rotation axis.
+ \param v Y-coordinate of the 3d rotation axis.
+ \param w Z-coordinate of the 3d rotation axis.
+ \param cx X-coordinate of the rotation center.
+ \param cy Y-coordinate of the rotation center.
+ \param cz Z-coordinate of the rotation center.
+ \param angle Rotation angle, in degrees.
+ \param interpolation Type of interpolation. Can be <tt>{ 0=nearest | 1=linear | 2=cubic | 3=mirror }</tt>.
+ \param boundary Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann | 2=periodic }</tt>.
+ \note Most of the time, size of the image is modified.
+ **/
+ CImg<T> rotate(const float u, const float v, const float w, const float angle,
+ const float cx, const float cy, const float cz,
+ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) {
+ const float nangle = cimg::mod(angle,360.0f);
+ if (nangle==0.0f) return *this;
+ return get_rotate(u,v,w,nangle,cx,cy,cz,interpolation,boundary_conditions).move_to(*this);
+ }
+
+ //! Rotate volumetric image with arbitrary angle and axis, around a center point \newinstance.
+ CImg<T> get_rotate(const float u, const float v, const float w, const float angle,
+ const float cx, const float cy, const float cz,
+ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const {
+ if (is_empty()) return *this;
+ CImg<T> res(_width,_height,_depth,_spectrum);
+ CImg<floatT> R = CImg<floatT>::rotation_matrix(u,v,w,-angle);
+ _rotate(res,R,interpolation,boundary_conditions,cx,cy,cz,cx,cy,cz);
+ return res;
+ }
+
+ // [internal] Perform 3d rotation with arbitrary axis and angle.
+ void _rotate(CImg<T>& res, const CImg<Tfloat>& R,
+ const unsigned int interpolation, const unsigned int boundary_conditions,
+ const float w2, const float h2, const float d2,
+ const float rw2, const float rh2, const float rd2) const {
+ switch (boundary_conditions) {
+ case 3 : // Mirror
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ const float ww = 2.0f*width(), hh = 2.0f*height(), dd = 2.0f*depth();
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),ww),
+ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),hh),
+ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),dd);
+ cimg_forC(res,c) res(x,y,z,c) = cimg::type<T>::cut(_cubic_atXYZ(X<width()?X:ww - X - 1,
+ Y<height()?Y:hh - Y - 1,
+ Z<depth()?Z:dd - Z - z,c));
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ const float ww = 2.0f*width(), hh = 2.0f*height(), dd = 2.0f*depth();
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),ww),
+ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),hh),
+ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),dd);
+ cimg_forC(res,c) res(x,y,z,c) = (T)_linear_atXYZ(X<width()?X:ww - X - 1,
+ Y<height()?Y:hh - Y - 1,
+ Z<depth()?Z:dd - Z - 1,c);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ const int ww = 2*width(), hh = 2*height(), dd = 2*depth();
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float xc = x - rw2, yc = y - rh2, zc = z - rd2;
+ const int
+ X = cimg::mod((int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),ww),
+ Y = cimg::mod((int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),hh),
+ Z = cimg::mod((int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),dd);
+ cimg_forC(res,c) res(x,y,z,c) = (*this)(X<width()?X:ww - X - 1,
+ Y<height()?Y:hh - Y - 1,
+ Z<depth()?Z:dd - Z - 1,c);
+ }
+ }
+ } break;
+
+ case 2 : // Periodic
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),(float)width()),
+ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),(float)height()),
+ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),(float)depth());
+ cimg_forC(res,c) res(x,y,z,c) = cimg::type<T>::cut(_cubic_atXYZ(X,Y,Z,c));
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = cimg::mod((float)(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),(float)width()),
+ Y = cimg::mod((float)(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),(float)height()),
+ Z = cimg::mod((float)(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),(float)depth());
+ cimg_forC(res,c) res(x,y,z,c) = (T)_linear_atXYZ(X,Y,Z,c);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float xc = x - rw2, yc = y - rh2, zc = z - rd2;
+ const int
+ X = cimg::mod((int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),width()),
+ Y = cimg::mod((int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),height()),
+ Z = cimg::mod((int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc),depth());
+ cimg_forC(res,c) res(x,y,z,c) = (*this)(X,Y,Z,c);
+ }
+ }
+ } break;
+
+ case 1 : // Neumann
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc,
+ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc,
+ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc;
+ cimg_forC(res,c) res(x,y,z,c) = cimg::type<T>::cut(_cubic_atXYZ(X,Y,Z,c));
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc,
+ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc,
+ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc;
+ cimg_forC(res,c) res(x,y,z,c) = _linear_atXYZ(X,Y,Z,c);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float xc = x - rw2, yc = y - rh2, zc = z - rd2;
+ const int
+ X = (int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),
+ Y = (int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),
+ Z = (int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc);
+ cimg_forC(res,c) res(x,y,z,c) = _atXYZ(X,Y,Z,c);
+ }
+ }
+ } break;
+
+ default : // Dirichlet
+ switch (interpolation) {
+ case 2 : { // Cubic interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc,
+ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc,
+ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc;
+ cimg_forC(res,c) res(x,y,z,c) = cimg::type<T>::cut(cubic_atXYZ(X,Y,Z,c,(T)0));
+ }
+ } break;
+ case 1 : { // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float
+ xc = x - rw2, yc = y - rh2, zc = z - rd2,
+ X = w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc,
+ Y = h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc,
+ Z = d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc;
+ cimg_forC(res,c) res(x,y,z,c) = linear_atXYZ(X,Y,Z,c,(T)0);
+ }
+ } break;
+ default : { // Nearest-neighbor interpolation
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048))
+ cimg_forXYZ(res,x,y,z) {
+ const float xc = x - rw2, yc = y - rh2, zc = z - rd2;
+ const int
+ X = (int)cimg::round(w2 + R(0,0)*xc + R(1,0)*yc + R(2,0)*zc),
+ Y = (int)cimg::round(h2 + R(0,1)*xc + R(1,1)*yc + R(2,1)*zc),
+ Z = (int)cimg::round(d2 + R(0,2)*xc + R(1,2)*yc + R(2,2)*zc);
+ cimg_forC(res,c) res(x,y,z,c) = atXYZ(X,Y,Z,c,(T)0);
+ }
+ }
+ } break;
+ }
+ }
+
+ //! Warp image content by a warping field.
+ /**
+ \param warp Warping field.
+ \param mode Can be { 0=backward-absolute | 1=backward-relative | 2=forward-absolute | 3=foward-relative }
+ \param is_relative Tells if warping field gives absolute or relative warping coordinates.
+ \param interpolation Can be <tt>{ 0=nearest | 1=linear | 2=cubic }</tt>.
+ \param boundary_conditions Boundary conditions <tt>{ 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }</tt>.
+ **/
+ template<typename t>
+ CImg<T>& warp(const CImg<t>& warp, const unsigned int mode=0,
+ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) {
+ return get_warp(warp,mode,interpolation,boundary_conditions).move_to(*this);
+ }
+
+ //! Warp image content by a warping field \newinstance
+ template<typename t>
+ CImg<T> get_warp(const CImg<t>& warp, const unsigned int mode=0,
+ const unsigned int interpolation=1, const unsigned int boundary_conditions=0) const {
+ if (is_empty() || !warp) return *this;
+ if (mode && !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);
+
+ if (warp._spectrum==1) { // 1d warping
+ if (mode>=3) { // Forward-relative warp
+ res.fill((T)0);
+ if (interpolation>=1) // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) res.set_linear_atX(*(ptrs++),x + (float)*(ptrs0++),y,z,c);
+ }
+ else // Nearest-neighbor interpolation
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int X = x + (int)cimg::round(*(ptrs0++));
+ if (X>=0 && X<width()) res(X,y,z,c) = *(ptrs++);
+ }
+ }
+ } else if (mode==2) { // Forward-absolute warp
+ res.fill((T)0);
+ if (interpolation>=1) // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) res.set_linear_atX(*(ptrs++),(float)*(ptrs0++),y,z,c);
+ }
+ else // Nearest-neighbor interpolation
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int X = (int)cimg::round(*(ptrs0++));
+ if (X>=0 && X<width()) res(X,y,z,c) = *(ptrs++);
+ }
+ }
+ } else if (mode==1) { // Backward-relative warp
+ if (interpolation==2) // Cubic interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float mx = cimg::mod(x - (float)*(ptrs0++),w2);
+ *(ptrd++) = (T)_cubic_atX(mx<width()?mx:w2 - mx - 1,y,z,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atX(cimg::mod(x - (float)*(ptrs0++),(float)_width),y,z,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atX(x - (float)*(ptrs0++),y,z,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)cubic_atX(x - (float)*(ptrs0++),y,z,c,(T)0);
+ }
+ }
+ else if (interpolation==1) // Linear interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float mx = cimg::mod(x - (float)*(ptrs0++),w2);
+ *(ptrd++) = (T)_linear_atX(mx<width()?mx:w2 - mx - 1,y,z,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX(cimg::mod(x - (float)*(ptrs0++),(float)_width),y,z,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX(x - (float)*(ptrs0++),y,z,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)linear_atX(x - (float)*(ptrs0++),y,z,c,(T)0);
+ }
+ }
+ else // Nearest-neighbor interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const int w2 = 2*width();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int mx = cimg::mod(x - (int)cimg::round(*(ptrs0++)),w2);
+ *(ptrd++) = (*this)(mx<width()?mx:w2 - mx - 1,y,z,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod(x - (int)cimg::round(*(ptrs0++)),(int)_width),y,z,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = _atX(x - (int)*(ptrs0++),y,z,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = atX(x - (int)*(ptrs0++),y,z,c,(T)0);
+ }
+ }
+ }
+ else { // Backward-absolute warp
+ if (interpolation==2) // Cubic interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float mx = cimg::mod((float)*(ptrs0++),w2);
+ *(ptrd++) = (T)_cubic_atX(mx<width()?mx:w2 - mx - 1,0,0,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atX(cimg::mod((float)*(ptrs0++),(float)_width),0,0,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atX((float)*(ptrs0++),0,0,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)cubic_atX((float)*(ptrs0++),0,0,c,(T)0);
+ }
+ }
+ else if (interpolation==1) // Linear interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float mx = cimg::mod((float)*(ptrs0++),w2);
+ *(ptrd++) = (T)_linear_atX(mx<width()?mx:w2 - mx - 1,0,0,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX(cimg::mod((float)*(ptrs0++),(float)_width),0,0,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atX((float)*(ptrs0++),0,0,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)linear_atX((float)*(ptrs0++),0,0,c,(T)0);
+ }
+ }
+ else // Nearest-neighbor interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const int w2 = 2*width();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int mx = cimg::mod((int)cimg::round(*(ptrs0++)),w2);
+ *(ptrd++) = (*this)(mx<width()?mx:w2 - mx - 1,0,0,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod((int)cimg::round(*(ptrs0++)),(int)_width),0,0,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = _atX((int)*(ptrs0++),0,0,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = atX((int)*(ptrs0++),0,0,c,(T)0);
+ }
+ }
+ }
+
+ } else if (warp._spectrum==2) { // 2d warping
+ if (mode>=3) { // Forward-relative warp
+ res.fill((T)0);
+ if (interpolation>=1) // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++),z,c);
+ }
+ else // Nearest-neighbor interpolation
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int X = x + (int)cimg::round(*(ptrs0++)), Y = y + (int)cimg::round(*(ptrs1++));
+ if (X>=0 && X<width() && Y>=0 && Y<height()) res(X,Y,z,c) = *(ptrs++);
+ }
+ }
+ } else if (mode==2) { // Forward-absolute warp
+ res.fill((T)0);
+ if (interpolation>=1) // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) res.set_linear_atXY(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),z,c);
+ }
+ else // Nearest-neighbor interpolation
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int X = (int)cimg::round(*(ptrs0++)), Y = (int)cimg::round(*(ptrs1++));
+ if (X>=0 && X<width() && Y>=0 && Y<height()) res(X,Y,z,c) = *(ptrs++);
+ }
+ }
+ } else if (mode==1) { // Backward-relative warp
+ if (interpolation==2) // Cubic interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod(x - (float)*(ptrs0++),w2),
+ my = cimg::mod(y - (float)*(ptrs1++),h2);
+ *(ptrd++) = (T)_cubic_atXY(mx<width()?mx:w2 - mx - 1,my<height()?my:h2 - my - 1,z,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atXY(cimg::mod(x - (float)*(ptrs0++),(float)_width),
+ cimg::mod(y - (float)*(ptrs1++),(float)_height),z,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)cubic_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c,(T)0);
+ }
+ }
+ else if (interpolation==1) // Linear interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod(x - (float)*(ptrs0++),w2),
+ my = cimg::mod(y - (float)*(ptrs1++),h2);
+ *(ptrd++) = (T)_linear_atXY(mx<width()?mx:w2 - mx - 1,my<height()?my:h2 - my - 1,z,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY(cimg::mod(x - (float)*(ptrs0++),(float)_width),
+ cimg::mod(y - (float)*(ptrs1++),(float)_height),z,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)linear_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c,(T)0);
+ }
+ }
+ else // Nearest-neighbor interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const int w2 = 2*width(), h2 = 2*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int
+ mx = cimg::mod(x - (int)cimg::round(*(ptrs0++)),w2),
+ my = cimg::mod(y - (int)cimg::round(*(ptrs1++)),h2);
+ *(ptrd++) = (*this)(mx<width()?mx:w2 - mx - 1,my<height()?my:h2 - my - 1,z,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod(x - (int)cimg::round(*(ptrs0++)),(int)_width),
+ cimg::mod(y - (int)cimg::round(*(ptrs1++)),(int)_height),z,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = _atXY(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = atXY(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z,c,(T)0);
+ }
+ }
+ } else { // Backward-absolute warp
+ if (interpolation==2) // Cubic interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod((float)*(ptrs0++),w2),
+ my = cimg::mod((float)*(ptrs1++),h2);
+ *(ptrd++) = (T)_cubic_atXY(mx<width()?mx:w2 - mx - 1,my<height()?my:h2 - my - 1,0,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atXY(cimg::mod((float)*(ptrs0++),(float)_width),
+ cimg::mod((float)*(ptrs1++),(float)_height),0,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)cubic_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c,(T)0);
+ }
+ }
+ else if (interpolation==1) // Linear interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod((float)*(ptrs0++),w2),
+ my = cimg::mod((float)*(ptrs1++),h2);
+ *(ptrd++) = (T)_linear_atXY(mx<width()?mx:w2 - mx - 1,my<height()?my:h2 - my - 1,0,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY(cimg::mod((float)*(ptrs0++),(float)_width),
+ cimg::mod((float)*(ptrs1++),(float)_height),0,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)linear_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c,(T)0);
+ }
+ }
+ else // Nearest-neighbor interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const int w2 = 2*width(), h2 = 2*height();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int
+ mx = cimg::mod((int)cimg::round(*(ptrs0++)),w2),
+ my = cimg::mod((int)cimg::round(*(ptrs1++)),h2);
+ *(ptrd++) = (*this)(mx<width()?mx:w2 - mx - 1,my<height()?my:h2 - my - 1,0,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod((int)cimg::round(*(ptrs0++)),(int)_width),
+ cimg::mod((int)cimg::round(*(ptrs1++)),(int)_height),0,c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = _atXY((int)*(ptrs0++),(int)*(ptrs1++),0,c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = atXY((int)*(ptrs0++),(int)*(ptrs1++),0,c,(T)0);
+ }
+ }
+ }
+
+ } else { // 3d warping
+ if (mode>=3) { // Forward-relative warp
+ res.fill((T)0);
+ if (interpolation>=1) // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),x + (float)*(ptrs0++),y + (float)*(ptrs1++),
+ z + (float)*(ptrs2++),c);
+ }
+ else // Nearest-neighbor interpolation
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int
+ X = x + (int)cimg::round(*(ptrs0++)),
+ Y = y + (int)cimg::round(*(ptrs1++)),
+ Z = z + (int)cimg::round(*(ptrs2++));
+ if (X>=0 && X<width() && Y>=0 && Y<height() && Z>=0 && Z<depth()) res(X,Y,Z,c) = *(ptrs++);
+ }
+ }
+ } else if (mode==2) { // Forward-absolute warp
+ res.fill((T)0);
+ if (interpolation>=1) // Linear interpolation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) res.set_linear_atXYZ(*(ptrs++),(float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c);
+ }
+ else // Nearest-neighbor interpolation
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ const T *ptrs = data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int
+ X = (int)cimg::round(*(ptrs0++)),
+ Y = (int)cimg::round(*(ptrs1++)),
+ Z = (int)cimg::round(*(ptrs2++));
+ if (X>=0 && X<width() && Y>=0 && Y<height() && Z>=0 && Z<depth()) res(X,Y,Z,c) = *(ptrs++);
+ }
+ }
+ } else if (mode==1) { // Backward-relative warp
+ if (interpolation==2) // Cubic interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height(), d2 = 2.0f*depth();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod(x - (float)*(ptrs0++),w2),
+ my = cimg::mod(y - (float)*(ptrs1++),h2),
+ mz = cimg::mod(z - (float)*(ptrs2++),d2);
+ *(ptrd++) = (T)_cubic_atXYZ(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_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 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x)
+ *(ptrd++) = (T)_cubic_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x)
+ *(ptrd++) = (T)cubic_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c,(T)0);
+ }
+ }
+ else if (interpolation==1) // Linear interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height(), d2 = 2.0f*depth();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod(x - (float)*(ptrs0++),w2),
+ my = cimg::mod(y - (float)*(ptrs1++),h2),
+ mz = cimg::mod(z - (float)*(ptrs2++),d2);
+ *(ptrd++) = (T)_linear_atXYZ(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(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 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x)
+ *(ptrd++) = (T)_linear_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x)
+ *(ptrd++) = (T)linear_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c,(T)0);
+ }
+ }
+ else // Nearest neighbor interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int
+ mx = cimg::mod(x - (int)cimg::round(*(ptrs0++)),w2),
+ my = cimg::mod(y - (int)cimg::round(*(ptrs1++)),h2),
+ mz = cimg::mod(z - (int)cimg::round(*(ptrs2++)),d2);
+ *(ptrd++) = (*this)(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod(x - (int)cimg::round(*(ptrs0++)),(int)_width),
+ cimg::mod(y - (int)cimg::round(*(ptrs1++)),(int)_height),
+ cimg::mod(z - (int)cimg::round(*(ptrs2++)),(int)_depth),c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = _atXYZ(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z - (int)*(ptrs2++),c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = atXYZ(x - (int)*(ptrs0++),y - (int)*(ptrs1++),z - (int)*(ptrs2++),c,(T)0);
+ }
+ }
+ } else { // Backward-absolute warp
+ if (interpolation==2) // Cubic interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height(), d2 = 2.0f*depth();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod((float)*(ptrs0++),w2),
+ my = cimg::mod((float)*(ptrs1++),h2),
+ mz = cimg::mod((float)*(ptrs2++),d2);
+ *(ptrd++) = (T)_cubic_atXYZ(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atXYZ(cimg::mod((float)*(ptrs0++),(float)_width),
+ cimg::mod((float)*(ptrs1++),(float)_height),
+ cimg::mod((float)*(ptrs2++),(float)_depth),c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_cubic_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)cubic_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),
+ c,(T)0);
+ }
+ }
+ else if (interpolation==1) // Linear interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const float w2 = 2.0f*width(), h2 = 2.0f*height(), d2 = 2.0f*depth();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const float
+ mx = cimg::mod((float)*(ptrs0++),w2),
+ my = cimg::mod((float)*(ptrs1++),h2),
+ mz = cimg::mod((float)*(ptrs2++),d2);
+ *(ptrd++) = (T)_linear_atXYZ(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,c);
+ }
+ }
+ } break;
+ case 2 :// Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(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 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)_linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (T)linear_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),
+ c,(T)0);
+ }
+ }
+ else // Nearest-neighbor interpolation
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096))
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) {
+ const int
+ mx = cimg::mod((int)cimg::round(*(ptrs0++)),w2),
+ my = cimg::mod((int)cimg::round(*(ptrs1++)),h2),
+ mz = cimg::mod((int)cimg::round(*(ptrs2++)),d2);
+ *(ptrd++) = (*this)(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,c);
+ }
+ }
+ } break;
+ case 2 : // Periodic
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod((int)cimg::round(*(ptrs0++)),(int)_width),
+ cimg::mod((int)cimg::round(*(ptrs1++)),(int)_height),
+ cimg::mod((int)cimg::round(*(ptrs2++)),(int)_depth),c);
+ }
+ break;
+ case 1 : // Neumann
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = _atXYZ((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),c);
+ }
+ break;
+ default : // Dirichlet
+ cimg_forYZC(res,y,z,c) {
+ const t *ptrs0 = warp.data(0,y,z,0), *ptrs1 = warp.data(0,y,z,1), *ptrs2 = warp.data(0,y,z,2);
+ T *ptrd = res.data(0,y,z,c);
+ cimg_forX(res,x) *(ptrd++) = atXYZ((int)*(ptrs0++),(int)*(ptrs1++),(int)*(ptrs2++),c,(T)0);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Generate a 2d representation of a 3d image, with XY,XZ and YZ views.
+ /**
+ \param x0 X-coordinate of the projection point.
+ \param y0 Y-coordinate of the projection point.
+ \param z0 Z-coordinate of the projection point.
+ **/
+ CImg<T> get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) const {
+ if (is_empty() || _depth<2) return +*this;
+ const unsigned int
+ _x0 = (x0>=_width)?_width - 1:x0,
+ _y0 = (y0>=_height)?_height - 1:y0,
+ _z0 = (z0>=_depth)?_depth - 1:z0;
+ const CImg<T>
+ img_xy = get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1),
+ img_zy = get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).permute_axes("xzyc").
+ resize(_depth,_height,1,-100,-1),
+ img_xz = get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1);
+ return CImg<T>(_width + _depth,_height + _depth,1,_spectrum,cimg::min(img_xy.min(),img_zy.min(),img_xz.min())).
+ draw_image(0,0,img_xy).draw_image(img_xy._width,0,img_zy).
+ draw_image(0,img_xy._height,img_xz);
+ }
+
+ //! Construct a 2d representation of a 3d image, with XY,XZ and YZ views \inplace.
+ CImg<T>& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) {
+ if (_depth<2) return *this;
+ return get_projections2d(x0,y0,z0).move_to(*this);
+ }
+
+ //! Crop image region.
+ /**
+ \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 boundary_conditions = Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }.
+ **/
+ 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 unsigned int boundary_conditions=0) {
+ return get_crop(x0,y0,z0,c0,x1,y1,z1,c1,boundary_conditions).move_to(*this);
+ }
+
+ //! Crop image region \newinstance.
+ 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 unsigned int boundary_conditions=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "crop(): Empty instance.",
+ cimg_instance);
+ const int
+ nx0 = x0<x1?x0:x1, nx1 = x0^x1^nx0,
+ ny0 = y0<y1?y0:y1, ny1 = y0^y1^ny0,
+ nz0 = z0<z1?z0:z1, nz1 = z0^z1^nz0,
+ nc0 = c0<c1?c0:c1, nc1 = c0^c1^nc0;
+ CImg<T> res(1U + nx1 - nx0,1U + ny1 - ny0,1U + nz1 - nz0,1U + nc1 - nc0);
+ if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height() || nz0<0 || nz1>=depth() || nc0<0 || nc1>=spectrum())
+ switch (boundary_conditions) {
+ case 3 : { // Mirror
+ const int w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(), s2 = 2*spectrum();
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4))
+ cimg_forXYZC(res,x,y,z,c) {
+ const int
+ mx = cimg::mod(nx0 + x,w2),
+ my = cimg::mod(ny0 + y,h2),
+ mz = cimg::mod(nz0 + z,d2),
+ mc = cimg::mod(nc0 + c,s2);
+ res(x,y,z,c) = (*this)(mx<width()?mx:w2 - mx - 1,
+ my<height()?my:h2 - my - 1,
+ mz<depth()?mz:d2 - mz - 1,
+ mc<spectrum()?mc:s2 - mc - 1);
+ }
+ } break;
+ case 2 : { // Periodic
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4))
+ cimg_forXYZC(res,x,y,z,c) {
+ res(x,y,z,c) = (*this)(cimg::mod(nx0 + x,width()),cimg::mod(ny0 + y,height()),
+ cimg::mod(nz0 + z,depth()),cimg::mod(nc0 + c,spectrum()));
+ }
+ } break;
+ case 1 : // Neumann
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4))
+ cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _atXYZC(nx0 + x,ny0 + y,nz0 + z,nc0 + c);
+ break;
+ default : // Dirichlet
+ res.fill((T)0).draw_image(-nx0,-ny0,-nz0,-nc0,*this);
+ }
+ else res.draw_image(-nx0,-ny0,-nz0,-nc0,*this);
+ return res;
+ }
+
+ //! Crop image region \overloading.
+ CImg<T>& crop(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const unsigned int boundary_conditions=0) {
+ return crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions);
+ }
+
+ //! Crop image region \newinstance.
+ CImg<T> get_crop(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const unsigned int boundary_conditions=0) const {
+ return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions);
+ }
+
+ //! Crop image region \overloading.
+ CImg<T>& crop(const int x0, const int y0,
+ const int x1, const int y1,
+ const unsigned int boundary_conditions=0) {
+ return crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions);
+ }
+
+ //! Crop image region \newinstance.
+ CImg<T> get_crop(const int x0, const int y0,
+ const int x1, const int y1,
+ const unsigned int boundary_conditions=0) const {
+ return get_crop(x0,y0,0,0,x1,y1,_depth - 1,_spectrum - 1,boundary_conditions);
+ }
+
+ //! Crop image region \overloading.
+ CImg<T>& crop(const int x0, const int x1, const unsigned int boundary_conditions=0) {
+ return crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions);
+ }
+
+ //! Crop image region \newinstance.
+ CImg<T> get_crop(const int x0, const int x1, const unsigned int boundary_conditions=0) const {
+ return get_crop(x0,0,0,0,x1,_height - 1,_depth - 1,_spectrum - 1,boundary_conditions);
+ }
+
+ //! Autocrop image region, regarding the specified background 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::lowercase(*s);
+ const CImg<intT> coords = _autocrop(value,axis);
+ if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels.
+ else switch (axis) {
+ case 'x' : {
+ const int x0 = coords[0], x1 = coords[1];
+ if (x0>=0 && x1>=0) crop(x0,x1);
+ } break;
+ case 'y' : {
+ const int y0 = coords[0], y1 = coords[1];
+ if (y0>=0 && y1>=0) crop(0,y0,_width - 1,y1);
+ } break;
+ case 'z' : {
+ const int z0 = coords[0], z1 = coords[1];
+ if (z0>=0 && z1>=0) crop(0,0,z0,_width - 1,_height - 1,z1);
+ } break;
+ default : {
+ const int c0 = coords[0], c1 = coords[1];
+ if (c0>=0 && c1>=0) crop(0,0,0,c0,_width - 1,_height - 1,_depth - 1,c1);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Autocrop image region, regarding the specified background value \newinstance.
+ CImg<T> get_autocrop(const T& value, const char *const axes="czyx") const {
+ return (+*this).autocrop(value,axes);
+ }
+
+ //! Autocrop image region, regarding the specified background color.
+ /**
+ \param color Color used for the crop. If \c 0, color is guessed.
+ \param axes Axes used for the crop.
+ **/
+ CImg<T>& autocrop(const T *const color=0, const char *const axes="zyx") {
+ if (is_empty()) return *this;
+ if (!color) { // Guess color.
+ const CImg<T> col1 = get_vector_at(0,0,0);
+ const unsigned int w = _width, h = _height, d = _depth, s = _spectrum;
+ autocrop(col1,axes);
+ if (_width==w && _height==h && _depth==d && _spectrum==s) {
+ const CImg<T> col2 = get_vector_at(w - 1,h - 1,d - 1);
+ autocrop(col2,axes);
+ }
+ return *this;
+ }
+ for (const char *s = axes; *s; ++s) {
+ const char axis = cimg::lowercase(*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 = std::min(x0,nx0); x1 = std::max(x1,nx1); }
+ }
+ if (x0==width() && x1==-1) return assign(); else crop(x0,x1);
+ } break;
+ case 'y' : {
+ int y0 = height(), y1 = -1;
+ cimg_forC(*this,c) {
+ const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'y');
+ const int ny0 = coords[0], ny1 = coords[1];
+ if (ny0>=0 && ny1>=0) { y0 = std::min(y0,ny0); y1 = std::max(y1,ny1); }
+ }
+ if (y0==height() && y1==-1) return assign(); else crop(0,y0,_width - 1,y1);
+ } break;
+ default : {
+ int z0 = depth(), z1 = -1;
+ cimg_forC(*this,c) {
+ const CImg<intT> coords = get_shared_channel(c)._autocrop(color[c],'z');
+ const int nz0 = coords[0], nz1 = coords[1];
+ if (nz0>=0 && nz1>=0) { z0 = std::min(z0,nz0); z1 = std::max(z1,nz1); }
+ }
+ if (z0==depth() && z1==-1) return assign(); else crop(0,0,z0,_width - 1,_height - 1,z1);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Autocrop image region, regarding the specified background color \newinstance.
+ CImg<T> get_autocrop(const T *const color=0, const char *const axes="zyx") const {
+ return (+*this).autocrop(color,axes);
+ }
+
+ //! Autocrop image region, regarding the specified background color \overloading.
+ template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char *const axes="zyx") {
+ return get_autocrop(color,axes).move_to(*this);
+ }
+
+ //! Autocrop image region, regarding the specified background color \newinstance.
+ template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char *const axes="zyx") const {
+ return get_autocrop(color._data,axes);
+ }
+
+ CImg<intT> _autocrop(const T& value, const char axis) const {
+ CImg<intT> res;
+ switch (cimg::lowercase(axis)) {
+ case 'x' : {
+ int x0 = -1, x1 = -1;
+ cimg_forX(*this,x) cimg_forYZC(*this,y,z,c)
+ if ((*this)(x,y,z,c)!=value) { x0 = x; x = width(); y = height(); z = depth(); c = spectrum(); }
+ if (x0>=0) {
+ for (int x = width() - 1; x>=0; --x) cimg_forYZC(*this,y,z,c)
+ if ((*this)(x,y,z,c)!=value) { x1 = x; x = 0; y = height(); z = depth(); c = spectrum(); }
+ }
+ res = CImg<intT>::vector(x0,x1);
+ } break;
+ case 'y' : {
+ int y0 = -1, y1 = -1;
+ cimg_forY(*this,y) cimg_forXZC(*this,x,z,c)
+ if ((*this)(x,y,z,c)!=value) { y0 = y; x = width(); y = height(); z = depth(); c = spectrum(); }
+ if (y0>=0) {
+ for (int y = height() - 1; y>=0; --y) cimg_forXZC(*this,x,z,c)
+ if ((*this)(x,y,z,c)!=value) { y1 = y; x = width(); y = 0; z = depth(); c = spectrum(); }
+ }
+ res = CImg<intT>::vector(y0,y1);
+ } break;
+ case 'z' : {
+ int z0 = -1, z1 = -1;
+ cimg_forZ(*this,z) cimg_forXYC(*this,x,y,c)
+ if ((*this)(x,y,z,c)!=value) { z0 = z; x = width(); y = height(); z = depth(); c = spectrum(); }
+ if (z0>=0) {
+ for (int z = depth() - 1; z>=0; --z) cimg_forXYC(*this,x,y,c)
+ if ((*this)(x,y,z,c)!=value) { z1 = z; x = width(); y = height(); z = 0; c = spectrum(); }
+ }
+ res = CImg<intT>::vector(z0,z1);
+ } break;
+ default : {
+ int c0 = -1, c1 = -1;
+ cimg_forC(*this,c) cimg_forXYZ(*this,x,y,z)
+ if ((*this)(x,y,z,c)!=value) { c0 = c; x = width(); y = height(); z = depth(); c = spectrum(); }
+ if (c0>=0) {
+ for (int c = spectrum() - 1; c>=0; --c) cimg_forXYZ(*this,x,y,z)
+ if ((*this)(x,y,z,c)!=value) { c1 = c; x = width(); y = height(); z = depth(); c = 0; }
+ }
+ res = CImg<intT>::vector(c0,c1);
+ }
+ }
+ return res;
+ }
+
+ //! Return specified image column.
+ /**
+ \param x0 Image column.
+ **/
+ CImg<T> get_column(const int x0) const {
+ return get_columns(x0,x0);
+ }
+
+ //! Return specified image column \inplace.
+ CImg<T>& column(const int x0) {
+ return columns(x0,x0);
+ }
+
+ //! Return specified range of image columns.
+ /**
+ \param x0 Starting image column.
+ \param x1 Ending image column.
+ **/
+ CImg<T>& columns(const int x0, const int x1) {
+ return get_columns(x0,x1).move_to(*this);
+ }
+
+ //! Return specified range of image columns \inplace.
+ CImg<T> get_columns(const int x0, const int x1) const {
+ return get_crop(x0,0,0,0,x1,height() - 1,depth() - 1,spectrum() - 1);
+ }
+
+ //! Return specified image row.
+ CImg<T> get_row(const int y0) const {
+ return get_rows(y0,y0);
+ }
+
+ //! Return specified image row \inplace.
+ /**
+ \param y0 Image row.
+ **/
+ CImg<T>& row(const int y0) {
+ return rows(y0,y0);
+ }
+
+ //! Return specified range of image rows.
+ /**
+ \param y0 Starting image row.
+ \param y1 Ending image row.
+ **/
+ CImg<T> get_rows(const int y0, const int y1) const {
+ return get_crop(0,y0,0,0,width() - 1,y1,depth() - 1,spectrum() - 1);
+ }
+
+ //! Return specified range of image rows \inplace.
+ CImg<T>& rows(const int y0, const int y1) {
+ return get_rows(y0,y1).move_to(*this);
+ }
+
+ //! Return specified image slice.
+ /**
+ \param z0 Image slice.
+ **/
+ CImg<T> get_slice(const int z0) const {
+ return get_slices(z0,z0);
+ }
+
+ //! Return specified image slice \inplace.
+ CImg<T>& slice(const int z0) {
+ return slices(z0,z0);
+ }
+
+ //! Return specified range of image slices.
+ /**
+ \param z0 Starting image slice.
+ \param z1 Ending image slice.
+ **/
+ CImg<T> get_slices(const int z0, const int z1) const {
+ return get_crop(0,0,z0,0,width() - 1,height() - 1,z1,spectrum() - 1);
+ }
+
+ //! Return specified range of image slices \inplace.
+ CImg<T>& slices(const int z0, const int z1) {
+ return get_slices(z0,z1).move_to(*this);
+ }
+
+ //! Return specified image channel.
+ /**
+ \param c0 Image channel.
+ **/
+ CImg<T> get_channel(const int c0) const {
+ return get_channels(c0,c0);
+ }
+
+ //! Return specified image channel \inplace.
+ CImg<T>& channel(const int c0) {
+ return channels(c0,c0);
+ }
+
+ //! Return specified range of image channels.
+ /**
+ \param c0 Starting image channel.
+ \param c1 Ending image channel.
+ **/
+ CImg<T> get_channels(const int c0, const int c1) const {
+ return get_crop(0,0,0,c0,width() - 1,height() - 1,depth() - 1,c1);
+ }
+
+ //! Return specified range of image channels \inplace.
+ CImg<T>& channels(const int c0, const int c1) {
+ return get_channels(c0,c1).move_to(*this);
+ }
+
+ //! 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 func Vector field function.
+ \param x X-coordinate of the starting point of the streamline.
+ \param y Y-coordinate of the starting point of the streamline.
+ \param z Z-coordinate of the starting point of the streamline.
+ \param L Streamline length.
+ \param dl Streamline length increment.
+ \param interpolation_type Type of interpolation.
+ Can be <tt>{ 0=nearest int | 1=linear | 2=2nd-order RK | 3=4th-order RK. }</tt>.
+ \param is_backward_tracking Tells if the streamline is estimated forward or backward.
+ \param is_oriented_only Tells if the direction of the vectors must be ignored.
+ \param x0 X-coordinate of the first bounding-box vertex.
+ \param y0 Y-coordinate of the first bounding-box vertex.
+ \param z0 Z-coordinate of the first bounding-box vertex.
+ \param x1 X-coordinate of the second bounding-box vertex.
+ \param y1 Y-coordinate of the second bounding-box vertex.
+ \param z1 Z-coordinate of the second bounding-box vertex.
+ **/
+ template<typename tfunc>
+ static CImg<floatT> streamline(const tfunc& func,
+ const float x, const float y, const float z,
+ const float L=256, const float dl=0.1f,
+ const unsigned int interpolation_type=2, const bool is_backward_tracking=false,
+ const bool is_oriented_only=false,
+ const float x0=0, const float y0=0, const float z0=0,
+ const float x1=0, const float y1=0, const float z1=0) {
+ if (dl<=0)
+ throw CImgArgumentException("CImg<%s>::streamline(): Invalid specified integration length %g "
+ "(should be >0).",
+ pixel_type(),
+ dl);
+
+ const bool is_bounded = (x0!=x1 || y0!=y1 || z0!=z1);
+ if (L<=0 || (is_bounded && (x<x0 || x>x1 || y<y0 || y>y1 || z<z0 || z>z1))) return CImg<floatT>();
+ const unsigned int size_L = (unsigned int)cimg::round(L/dl + 1);
+ CImg<floatT> coordinates(size_L,3);
+ const float dl2 = dl/2;
+ float
+ *ptr_x = coordinates.data(0,0),
+ *ptr_y = coordinates.data(0,1),
+ *ptr_z = coordinates.data(0,2),
+ pu = (float)(dl*func(x,y,z,0)),
+ pv = (float)(dl*func(x,y,z,1)),
+ pw = (float)(dl*func(x,y,z,2)),
+ X = x, Y = y, Z = z;
+
+ switch (interpolation_type) {
+ case 0 : { // Nearest integer interpolation.
+ cimg_forX(coordinates,l) {
+ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+ const int
+ xi = (int)(X>0?X + 0.5f:X - 0.5f),
+ yi = (int)(Y>0?Y + 0.5f:Y - 0.5f),
+ zi = (int)(Z>0?Z + 0.5f:Z - 0.5f);
+ float
+ u = (float)(dl*func((float)xi,(float)yi,(float)zi,0)),
+ v = (float)(dl*func((float)xi,(float)yi,(float)zi,1)),
+ w = (float)(dl*func((float)xi,(float)yi,(float)zi,2));
+ if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+ if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+ }
+ } break;
+ case 1 : { // First-order interpolation.
+ cimg_forX(coordinates,l) {
+ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+ float
+ u = (float)(dl*func(X,Y,Z,0)),
+ v = (float)(dl*func(X,Y,Z,1)),
+ w = (float)(dl*func(X,Y,Z,2));
+ if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+ if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+ }
+ } break;
+ case 2 : { // Second order interpolation.
+ cimg_forX(coordinates,l) {
+ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+ float
+ u0 = (float)(dl2*func(X,Y,Z,0)),
+ v0 = (float)(dl2*func(X,Y,Z,1)),
+ w0 = (float)(dl2*func(X,Y,Z,2));
+ if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; }
+ float
+ u = (float)(dl*func(X + u0,Y + v0,Z + w0,0)),
+ v = (float)(dl*func(X + u0,Y + v0,Z + w0,1)),
+ w = (float)(dl*func(X + u0,Y + v0,Z + w0,2));
+ if (is_oriented_only && u*pu + v*pv + w*pw<0) { u = -u; v = -v; w = -w; }
+ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+ if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+ }
+ } break;
+ default : { // Fourth order interpolation.
+ cimg_forX(coordinates,x) {
+ *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z;
+ float
+ u0 = (float)(dl2*func(X,Y,Z,0)),
+ v0 = (float)(dl2*func(X,Y,Z,1)),
+ w0 = (float)(dl2*func(X,Y,Z,2));
+ if (is_oriented_only && u0*pu + v0*pv + w0*pw<0) { u0 = -u0; v0 = -v0; w0 = -w0; }
+ float
+ u1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,0)),
+ v1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,1)),
+ w1 = (float)(dl2*func(X + u0,Y + v0,Z + w0,2));
+ if (is_oriented_only && u1*pu + v1*pv + w1*pw<0) { u1 = -u1; v1 = -v1; w1 = -w1; }
+ float
+ u2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,0)),
+ v2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,1)),
+ w2 = (float)(dl2*func(X + u1,Y + v1,Z + w1,2));
+ if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u2 = -u2; v2 = -v2; w2 = -w2; }
+ float
+ u3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,0)),
+ v3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,1)),
+ w3 = (float)(dl2*func(X + u2,Y + v2,Z + w2,2));
+ if (is_oriented_only && u2*pu + v2*pv + w2*pw<0) { u3 = -u3; v3 = -v3; w3 = -w3; }
+ const float
+ u = (u0 + u3)/3 + (u1 + u2)/1.5f,
+ v = (v0 + v3)/3 + (v1 + v2)/1.5f,
+ w = (w0 + w3)/3 + (w1 + w2)/1.5f;
+ if (is_backward_tracking) { X-=(pu=u); Y-=(pv=v); Z-=(pw=w); } else { X+=(pu=u); Y+=(pv=v); Z+=(pw=w); }
+ if (is_bounded && (X<x0 || X>x1 || Y<y0 || Y>y1 || Z<z0 || Z>z1)) break;
+ }
+ }
+ }
+ if (ptr_x!=coordinates.data(0,1)) coordinates.resize((int)(ptr_x-coordinates.data()),3,1,1,0);
+ return coordinates;
+ }
+
+ //! Return stream line of a 3d vector field \overloading.
+ static CImg<floatT> streamline(const char *const expression,
+ const float x, const float y, const float z,
+ const float L=256, const float dl=0.1f,
+ const unsigned int interpolation_type=2, const bool is_backward_tracking=true,
+ const bool is_oriented_only=false,
+ const float x0=0, const float y0=0, const float z0=0,
+ const float x1=0, const float y1=0, const float z1=0) {
+ _functor4d_streamline_expr func(expression);
+ return streamline(func,x,y,z,L,dl,interpolation_type,is_backward_tracking,is_oriented_only,x0,y0,z0,x1,y1,z1);
+ }
+
+ struct _functor4d_streamline2d_directed {
+ const CImg<T>& ref;
+ _functor4d_streamline2d_directed(const CImg<T>& pref):ref(pref) {}
+ float operator()(const float x, const float y, const float z, const unsigned int c) const {
+ return c<2?(float)ref._linear_atXY(x,y,(int)z,c):0;
+ }
+ };
+
+ struct _functor4d_streamline3d_directed {
+ const CImg<T>& ref;
+ _functor4d_streamline3d_directed(const CImg<T>& pref):ref(pref) {}
+ float operator()(const float x, const float y, const float z, const unsigned int c) const {
+ return (float)ref._linear_atXYZ(x,y,z,c);
+ }
+ };
+
+ struct _functor4d_streamline2d_oriented {
+ const CImg<T>& ref;
+ CImg<floatT> *pI;
+ _functor4d_streamline2d_oriented(const CImg<T>& pref):ref(pref),pI(0) { pI = new CImg<floatT>(2,2,1,2); }
+ ~_functor4d_streamline2d_oriented() { delete pI; }
+ float operator()(const float x, const float y, const float z, const unsigned int c) const {
+#define _cimg_vecalign2d(i,j) \
+ if (I(i,j,0)*I(0,0,0) + I(i,j,1)*I(0,0,1)<0) { I(i,j,0) = -I(i,j,0); I(i,j,1) = -I(i,j,1); }
+ int
+ xi = (int)x - (x>=0?0:1), nxi = xi + 1,
+ yi = (int)y - (y>=0?0:1), nyi = yi + 1,
+ zi = (int)z;
+ const float
+ dx = x - xi,
+ dy = y - yi;
+ if (c==0) {
+ CImg<floatT>& I = *pI;
+ if (xi<0) xi = 0;
+ if (nxi<0) nxi = 0;
+ if (xi>=ref.width()) xi = ref.width() - 1;
+ if (nxi>=ref.width()) nxi = ref.width() - 1;
+ if (yi<0) yi = 0;
+ if (nyi<0) nyi = 0;
+ if (yi>=ref.height()) yi = ref.height() - 1;
+ if (nyi>=ref.height()) nyi = ref.height() - 1;
+ I(0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,1) = (float)ref(xi,yi,zi,1);
+ I(1,0,0) = (float)ref(nxi,yi,zi,0); I(1,0,1) = (float)ref(nxi,yi,zi,1);
+ I(1,1,0) = (float)ref(nxi,nyi,zi,0); I(1,1,1) = (float)ref(nxi,nyi,zi,1);
+ I(0,1,0) = (float)ref(xi,nyi,zi,0); I(0,1,1) = (float)ref(xi,nyi,zi,1);
+ _cimg_vecalign2d(1,0); _cimg_vecalign2d(1,1); _cimg_vecalign2d(0,1);
+ }
+ return c<2?(float)pI->_linear_atXY(dx,dy,0,c):0;
+ }
+ };
+
+ struct _functor4d_streamline3d_oriented {
+ const CImg<T>& ref;
+ CImg<floatT> *pI;
+ _functor4d_streamline3d_oriented(const CImg<T>& pref):ref(pref),pI(0) { pI = new CImg<floatT>(2,2,2,3); }
+ ~_functor4d_streamline3d_oriented() { delete pI; }
+ float operator()(const float x, const float y, const float z, const unsigned int c) const {
+#define _cimg_vecalign3d(i,j,k) if (I(i,j,k,0)*I(0,0,0,0) + I(i,j,k,1)*I(0,0,0,1) + I(i,j,k,2)*I(0,0,0,2)<0) { \
+ I(i,j,k,0) = -I(i,j,k,0); I(i,j,k,1) = -I(i,j,k,1); I(i,j,k,2) = -I(i,j,k,2); }
+ int
+ xi = (int)x - (x>=0?0:1), nxi = xi + 1,
+ yi = (int)y - (y>=0?0:1), nyi = yi + 1,
+ zi = (int)z - (z>=0?0:1), nzi = zi + 1;
+ const float
+ dx = x - xi,
+ dy = y - yi,
+ dz = z - zi;
+ if (c==0) {
+ CImg<floatT>& I = *pI;
+ if (xi<0) xi = 0;
+ if (nxi<0) nxi = 0;
+ if (xi>=ref.width()) xi = ref.width() - 1;
+ if (nxi>=ref.width()) nxi = ref.width() - 1;
+ if (yi<0) yi = 0;
+ if (nyi<0) nyi = 0;
+ if (yi>=ref.height()) yi = ref.height() - 1;
+ if (nyi>=ref.height()) nyi = ref.height() - 1;
+ if (zi<0) zi = 0;
+ if (nzi<0) nzi = 0;
+ if (zi>=ref.depth()) zi = ref.depth() - 1;
+ if (nzi>=ref.depth()) nzi = ref.depth() - 1;
+ I(0,0,0,0) = (float)ref(xi,yi,zi,0); I(0,0,0,1) = (float)ref(xi,yi,zi,1);
+ I(0,0,0,2) = (float)ref(xi,yi,zi,2); I(1,0,0,0) = (float)ref(nxi,yi,zi,0);
+ I(1,0,0,1) = (float)ref(nxi,yi,zi,1); I(1,0,0,2) = (float)ref(nxi,yi,zi,2);
+ I(1,1,0,0) = (float)ref(nxi,nyi,zi,0); I(1,1,0,1) = (float)ref(nxi,nyi,zi,1);
+ I(1,1,0,2) = (float)ref(nxi,nyi,zi,2); I(0,1,0,0) = (float)ref(xi,nyi,zi,0);
+ I(0,1,0,1) = (float)ref(xi,nyi,zi,1); I(0,1,0,2) = (float)ref(xi,nyi,zi,2);
+ I(0,0,1,0) = (float)ref(xi,yi,nzi,0); I(0,0,1,1) = (float)ref(xi,yi,nzi,1);
+ I(0,0,1,2) = (float)ref(xi,yi,nzi,2); I(1,0,1,0) = (float)ref(nxi,yi,nzi,0);
+ I(1,0,1,1) = (float)ref(nxi,yi,nzi,1); I(1,0,1,2) = (float)ref(nxi,yi,nzi,2);
+ I(1,1,1,0) = (float)ref(nxi,nyi,nzi,0); I(1,1,1,1) = (float)ref(nxi,nyi,nzi,1);
+ I(1,1,1,2) = (float)ref(nxi,nyi,nzi,2); I(0,1,1,0) = (float)ref(xi,nyi,nzi,0);
+ I(0,1,1,1) = (float)ref(xi,nyi,nzi,1); I(0,1,1,2) = (float)ref(xi,nyi,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() { mp->end(); delete mp; }
+ _functor4d_streamline_expr(const char *const expr):mp(0) {
+ mp = new _cimg_math_parser(expr,"streamline",CImg<T>::const_empty(),0);
+ }
+ float operator()(const float x, const float y, const float z, const unsigned int c) const {
+ return (float)(*mp)(x,y,z,c);
+ }
+ };
+
+ //! Return a shared-memory image referencing a range of pixels of the image instance.
+ /**
+ \param x0 X-coordinate of the starting pixel.
+ \param x1 X-coordinate of the ending pixel.
+ \param y0 Y-coordinate.
+ \param z0 Z-coordinate.
+ \param c0 C-coordinate.
+ **/
+ 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 = (unsigned int)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 range of pixels of the image instance \const.
+ 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 = (unsigned int)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 range of rows of the image instance.
+ /**
+ \param y0 Y-coordinate of the starting row.
+ \param y1 Y-coordinate of the ending row.
+ \param z0 Z-coordinate.
+ \param c0 C-coordinate.
+ **/
+ CImg<T> get_shared_rows(const unsigned int y0, const unsigned int y1,
+ const unsigned int z0=0, const unsigned int c0=0) {
+ const unsigned int
+ beg = (unsigned int)offset(0,y0,z0,c0),
+ end = (unsigned int)offset(0,y1,z0,c0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException(_cimg_instance
+ "get_shared_rows(): 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 a range of rows of the image instance \const.
+ const CImg<T> get_shared_rows(const unsigned int y0, const unsigned int y1,
+ const unsigned int z0=0, const unsigned int c0=0) const {
+ const unsigned int
+ beg = (unsigned int)offset(0,y0,z0,c0),
+ end = (unsigned int)offset(0,y1,z0,c0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException(_cimg_instance
+ "get_shared_rows(): 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 row of the image instance.
+ /**
+ \param y0 Y-coordinate.
+ \param z0 Z-coordinate.
+ \param c0 C-coordinate.
+ **/
+ CImg<T> get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) {
+ return get_shared_rows(y0,y0,z0,c0);
+ }
+
+ //! Return a shared-memory image referencing one row of the image instance \const.
+ const CImg<T> get_shared_row(const unsigned int y0, const unsigned int z0=0, const unsigned int c0=0) const {
+ return get_shared_rows(y0,y0,z0,c0);
+ }
+
+ //! Return a shared memory image referencing a range of slices of the image instance.
+ /**
+ \param z0 Z-coordinate of the starting slice.
+ \param z1 Z-coordinate of the ending slice.
+ \param c0 C-coordinate.
+ **/
+ CImg<T> get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) {
+ const unsigned int
+ beg = (unsigned int)offset(0,0,z0,c0),
+ end = (unsigned int)offset(0,0,z1,c0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException(_cimg_instance
+ "get_shared_slices(): 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 a range of slices of the image instance \const.
+ const CImg<T> get_shared_slices(const unsigned int z0, const unsigned int z1, const unsigned int c0=0) const {
+ const unsigned int
+ beg = (unsigned int)offset(0,0,z0,c0),
+ end = (unsigned int)offset(0,0,z1,c0);
+ if (beg>end || beg>=size() || end>=size())
+ throw CImgArgumentException(_cimg_instance
+ "get_shared_slices(): 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 slice of the image instance.
+ /**
+ \param z0 Z-coordinate.
+ \param c0 C-coordinate.
+ **/
+ CImg<T> get_shared_slice(const unsigned int z0, const unsigned int c0=0) {
+ return get_shared_slices(z0,z0,c0);
+ }
+
+ //! Return a shared-memory image referencing one slice of the image instance \const.
+ const CImg<T> get_shared_slice(const unsigned int z0, const unsigned int c0=0) const {
+ return get_shared_slices(z0,z0,c0);
+ }
+
+ //! Return a shared-memory image referencing a range of channels of the image instance.
+ /**
+ \param c0 C-coordinate of the starting channel.
+ \param c1 C-coordinate of the ending channel.
+ **/
+ CImg<T> get_shared_channels(const unsigned int c0, const unsigned int c1) {
+ const unsigned int
+ beg = (unsigned int)offset(0,0,0,c0),
+ end = (unsigned int)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 a range of channels of the image instance \const.
+ const CImg<T> get_shared_channels(const unsigned int c0, const unsigned int c1) const {
+ const unsigned int
+ beg = (unsigned int)offset(0,0,0,c0),
+ end = (unsigned int)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 of the image instance.
+ /**
+ \param c0 C-coordinate.
+ **/
+ CImg<T> get_shared_channel(const unsigned int c0) {
+ return get_shared_channels(c0,c0);
+ }
+
+ //! Return a shared-memory image referencing one channel of the image instance \const.
+ const CImg<T> get_shared_channel(const unsigned int c0) const {
+ return get_shared_channels(c0,c0);
+ }
+
+ //! Return a shared-memory version of the image instance.
+ CImg<T> get_shared() {
+ return CImg<T>(_data,_width,_height,_depth,_spectrum,true);
+ }
+
+ //! Return a shared-memory version of the image instance \const.
+ const CImg<T> get_shared() const {
+ return CImg<T>(_data,_width,_height,_depth,_spectrum,true);
+ }
+
+ //! Split image into a list along specified axis.
+ /**
+ \param axis Splitting axis. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param nb Number of splitted parts.
+ \note
+ - If \c nb==0, instance image is splitted into blocs of egal values along the specified axis.
+ - If \c nb<=0, instance image is splitted into blocs of -\c nb pixel wide.
+ - If \c nb>0, instance image is splitted into \c nb blocs.
+ **/
+ CImgList<T> get_split(const char axis, const int nb=-1) const {
+ CImgList<T> res;
+ if (is_empty()) return res;
+ const char _axis = cimg::lowercase(axis);
+
+ if (nb<0) { // Split by bloc size.
+ const unsigned int dp = (unsigned int)(nb?-nb:1);
+ switch (_axis) {
+ case 'x': {
+ if (_width>dp) {
+ res.assign(_width/dp + (_width%dp?1:0),1,1);
+ const unsigned int pe = _width - dp;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _height*_depth*_spectrum>=128))
+ for (unsigned int p = 0; p<pe; p+=dp)
+ get_crop(p,0,0,0,p + dp - 1,_height - 1,_depth - 1,_spectrum - 1).move_to(res[p/dp]);
+ get_crop((res._width - 1)*dp,0,0,0,_width - 1,_height - 1,_depth - 1,_spectrum - 1).move_to(res.back());
+ } else res.assign(*this);
+ } break;
+ case 'y': {
+ if (_height>dp) {
+ res.assign(_height/dp + (_height%dp?1:0),1,1);
+ const unsigned int pe = _height - dp;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _width*_depth*_spectrum>=128))
+ for (unsigned int p = 0; p<pe; p+=dp)
+ get_crop(0,p,0,0,_width - 1,p + dp - 1,_depth - 1,_spectrum - 1).move_to(res[p/dp]);
+ get_crop(0,(res._width - 1)*dp,0,0,_width - 1,_height - 1,_depth - 1,_spectrum - 1).move_to(res.back());
+ } else res.assign(*this);
+ } break;
+ case 'z': {
+ if (_depth>dp) {
+ res.assign(_depth/dp + (_depth%dp?1:0),1,1);
+ const unsigned int pe = _depth - dp;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _width*_height*_spectrum>=128))
+ for (unsigned int p = 0; p<pe; p+=dp)
+ get_crop(0,0,p,0,_width - 1,_height - 1,p + dp - 1,_spectrum - 1).move_to(res[p/dp]);
+ get_crop(0,0,(res._width - 1)*dp,0,_width - 1,_height - 1,_depth - 1,_spectrum - 1).move_to(res.back());
+ } else res.assign(*this);
+ } break;
+ case 'c' : {
+ if (_spectrum>dp) {
+ res.assign(_spectrum/dp + (_spectrum%dp?1:0),1,1);
+ const unsigned int pe = _spectrum - dp;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=128 && _width*_height*_depth>=128))
+ for (unsigned int p = 0; p<pe; p+=dp)
+ get_crop(0,0,0,p,_width - 1,_height - 1,_depth - 1,p + dp - 1).move_to(res[p/dp]);
+ get_crop(0,0,0,(res._width - 1)*dp,_width - 1,_height - 1,_depth - 1,_spectrum - 1).move_to(res.back());
+ } else res.assign(*this);
+ }
+ }
+ } else if (nb>0) { // Split by number of (non-homogeneous) blocs.
+ const unsigned int siz = _axis=='x'?_width:_axis=='y'?_height:_axis=='z'?_depth:_axis=='c'?_spectrum:0;
+ if ((unsigned int)nb>siz)
+ throw CImgArgumentException(_cimg_instance
+ "get_split(): Instance cannot be split along %c-axis into %u blocs.",
+ cimg_instance,
+ axis,nb);
+ if (nb==1) res.assign(*this);
+ else {
+ int err = (int)siz;
+ unsigned int _p = 0;
+ switch (_axis) {
+ case 'x' : {
+ cimg_forX(*this,p) if ((err-=nb)<=0) {
+ get_crop(_p,0,0,0,p,_height - 1,_depth - 1,_spectrum - 1).move_to(res);
+ err+=(int)siz;
+ _p = p + 1U;
+ }
+ } break;
+ case 'y' : {
+ cimg_forY(*this,p) if ((err-=nb)<=0) {
+ get_crop(0,_p,0,0,_width - 1,p,_depth - 1,_spectrum - 1).move_to(res);
+ err+=(int)siz;
+ _p = p + 1U;
+ }
+ } break;
+ case 'z' : {
+ cimg_forZ(*this,p) if ((err-=nb)<=0) {
+ get_crop(0,0,_p,0,_width - 1,_height - 1,p,_spectrum - 1).move_to(res);
+ err+=(int)siz;
+ _p = p + 1U;
+ }
+ } break;
+ case 'c' : {
+ cimg_forC(*this,p) if ((err-=nb)<=0) {
+ get_crop(0,0,0,_p,_width - 1,_height - 1,_depth - 1,p).move_to(res);
+ err+=(int)siz;
+ _p = p + 1U;
+ }
+ }
+ }
+ }
+ } else { // Split by egal values according to specified axis.
+ T current = *_data;
+ switch (_axis) {
+ case 'x' : {
+ int i0 = 0;
+ cimg_forX(*this,i)
+ if ((*this)(i)!=current) { get_columns(i0,i - 1).move_to(res); i0 = i; current = (*this)(i); }
+ get_columns(i0,width() - 1).move_to(res);
+ } break;
+ case 'y' : {
+ int i0 = 0;
+ cimg_forY(*this,i)
+ if ((*this)(0,i)!=current) { get_rows(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,i); }
+ get_rows(i0,height() - 1).move_to(res);
+ } break;
+ case 'z' : {
+ int i0 = 0;
+ cimg_forZ(*this,i)
+ if ((*this)(0,0,i)!=current) { get_slices(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,i); }
+ get_slices(i0,depth() - 1).move_to(res);
+ } break;
+ case 'c' : {
+ int i0 = 0;
+ cimg_forC(*this,i)
+ if ((*this)(0,0,0,i)!=current) { get_channels(i0,i - 1).move_to(res); i0 = i; current = (*this)(0,0,0,i); }
+ get_channels(i0,spectrum() - 1).move_to(res);
+ } break;
+ default : {
+ longT i0 = 0;
+ cimg_foroff(*this,i)
+ if ((*this)[i]!=current) {
+ CImg<T>(_data + i0,1,(unsigned int)(i - i0)).move_to(res);
+ i0 = (longT)i; current = (*this)[i];
+ }
+ CImg<T>(_data + i0,1,(unsigned int)(size() - i0)).move_to(res);
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Split image into a list of sub-images, according to a specified splitting value sequence and optionally axis.
+ /**
+ \param values Splitting value sequence.
+ \param axis Axis along which the splitting is performed. Can be '0' to ignore axis.
+ \param keep_values Tells if the splitting sequence must be kept in the splitted blocs.
+ **/
+ template<typename t>
+ CImgList<T> get_split(const CImg<t>& values, const char axis=0, const bool keep_values=true) const {
+ CImgList<T> res;
+ if (is_empty()) return res;
+ const ulongT vsiz = values.size();
+ const char _axis = cimg::lowercase(axis);
+ if (!vsiz) return CImgList<T>(*this);
+ if (vsiz==1) { // Split according to a single value.
+ const T value = (T)*values;
+ switch (_axis) {
+ case 'x' : {
+ unsigned int i0 = 0, i = 0;
+ do {
+ while (i<_width && (*this)(i)==value) ++i;
+ if (i>i0) { if (keep_values) get_columns(i0,i - 1).move_to(res); i0 = i; }
+ while (i<_width && (*this)(i)!=value) ++i;
+ if (i>i0) { get_columns(i0,i - 1).move_to(res); i0 = i; }
+ } while (i<_width);
+ } break;
+ case 'y' : {
+ unsigned int i0 = 0, i = 0;
+ do {
+ while (i<_height && (*this)(0,i)==value) ++i;
+ if (i>i0) { if (keep_values) get_rows(i0,i - 1).move_to(res); i0 = i; }
+ while (i<_height && (*this)(0,i)!=value) ++i;
+ if (i>i0) { get_rows(i0,i - 1).move_to(res); i0 = i; }
+ } while (i<_height);
+ } break;
+ case 'z' : {
+ unsigned int i0 = 0, i = 0;
+ do {
+ while (i<_depth && (*this)(0,0,i)==value) ++i;
+ if (i>i0) { if (keep_values) get_slices(i0,i - 1).move_to(res); i0 = i; }
+ while (i<_depth && (*this)(0,0,i)!=value) ++i;
+ if (i>i0) { get_slices(i0,i - 1).move_to(res); i0 = i; }
+ } while (i<_depth);
+ } break;
+ case 'c' : {
+ unsigned int i0 = 0, i = 0;
+ do {
+ while (i<_spectrum && (*this)(0,0,0,i)==value) ++i;
+ if (i>i0) { if (keep_values) get_channels(i0,i - 1).move_to(res); i0 = i; }
+ while (i<_spectrum && (*this)(0,0,0,i)!=value) ++i;
+ if (i>i0) { get_channels(i0,i - 1).move_to(res); i0 = i; }
+ } while (i<_spectrum);
+ } break;
+ default : {
+ const ulongT siz = size();
+ ulongT i0 = 0, i = 0;
+ do {
+ while (i<siz && (*this)[i]==value) ++i;
+ if (i>i0) { if (keep_values) CImg<T>(_data + i0,1,(unsigned int)(i - i0)).move_to(res); i0 = i; }
+ while (i<siz && (*this)[i]!=value) ++i;
+ if (i>i0) { CImg<T>(_data + i0,1,(unsigned int)(i - i0)).move_to(res); i0 = i; }
+ } while (i<siz);
+ }
+ }
+ } else { // Split according to multiple values.
+ ulongT j = 0;
+ switch (_axis) {
+ case 'x' : {
+ unsigned int i0 = 0, i1 = 0, i = 0;
+ do {
+ if ((*this)(i)==*values) {
+ i1 = i; j = 0;
+ while (i<_width && (*this)(i)==values[j]) { ++i; if (++j>=vsiz) j = 0; }
+ i-=j;
+ if (i>i1) {
+ if (i1>i0) get_columns(i0,i1 - 1).move_to(res);
+ if (keep_values) get_columns(i1,i - 1).move_to(res);
+ i0 = i;
+ } else ++i;
+ } else ++i;
+ } while (i<_width);
+ if (i0<_width) get_columns(i0,width() - 1).move_to(res);
+ } break;
+ case 'y' : {
+ unsigned int i0 = 0, i1 = 0, i = 0;
+ do {
+ if ((*this)(0,i)==*values) {
+ i1 = i; j = 0;
+ while (i<_height && (*this)(0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; }
+ i-=j;
+ if (i>i1) {
+ if (i1>i0) get_rows(i0,i1 - 1).move_to(res);
+ if (keep_values) get_rows(i1,i - 1).move_to(res);
+ i0 = i;
+ } else ++i;
+ } else ++i;
+ } while (i<_height);
+ if (i0<_height) get_rows(i0,height() - 1).move_to(res);
+ } break;
+ case 'z' : {
+ unsigned int i0 = 0, i1 = 0, i = 0;
+ do {
+ if ((*this)(0,0,i)==*values) {
+ i1 = i; j = 0;
+ while (i<_depth && (*this)(0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; }
+ i-=j;
+ if (i>i1) {
+ if (i1>i0) get_slices(i0,i1 - 1).move_to(res);
+ if (keep_values) get_slices(i1,i - 1).move_to(res);
+ i0 = i;
+ } else ++i;
+ } else ++i;
+ } while (i<_depth);
+ if (i0<_depth) get_slices(i0,depth() - 1).move_to(res);
+ } break;
+ case 'c' : {
+ unsigned int i0 = 0, i1 = 0, i = 0;
+ do {
+ if ((*this)(0,0,0,i)==*values) {
+ i1 = i; j = 0;
+ while (i<_spectrum && (*this)(0,0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; }
+ i-=j;
+ if (i>i1) {
+ if (i1>i0) get_channels(i0,i1 - 1).move_to(res);
+ if (keep_values) get_channels(i1,i - 1).move_to(res);
+ i0 = i;
+ } else ++i;
+ } else ++i;
+ } while (i<_spectrum);
+ if (i0<_spectrum) get_channels(i0,spectrum() - 1).move_to(res);
+ } break;
+ default : {
+ ulongT i0 = 0, i1 = 0, i = 0;
+ const ulongT siz = size();
+ do {
+ if ((*this)[i]==*values) {
+ i1 = i; j = 0;
+ while (i<siz && (*this)[i]==values[j]) { ++i; if (++j>=vsiz) j = 0; }
+ i-=j;
+ if (i>i1) {
+ if (i1>i0) CImg<T>(_data + i0,1,(unsigned int)(i1 - i0)).move_to(res);
+ if (keep_values) CImg<T>(_data + i1,1,(unsigned int)(i - i1)).move_to(res);
+ i0 = i;
+ } else ++i;
+ } else ++i;
+ } while (i<siz);
+ if (i0<siz) CImg<T>(_data + i0,1,(unsigned int)(siz - i0)).move_to(res);
+ } break;
+ }
+ }
+ return res;
+ }
+
+ //! Append two images along specified axis.
+ /**
+ \param img Image to append with instance image.
+ \param axis Appending axis. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Append alignment in \c [0,1].
+ **/
+ template<typename t>
+ CImg<T>& append(const CImg<t>& img, const char axis='x', const float align=0) {
+ if (is_empty()) return assign(img,false);
+ if (!img) return *this;
+ return CImgList<T>(*this,true).insert(img).get_append(axis,align).move_to(*this);
+ }
+
+ //! Append two images along specified axis \specialization.
+ CImg<T>& append(const CImg<T>& img, const char axis='x', const float align=0) {
+ if (is_empty()) return assign(img,false);
+ if (!img) return *this;
+ return CImgList<T>(*this,img,true).get_append(axis,align).move_to(*this);
+ }
+
+ //! Append two images along specified axis \const.
+ template<typename t>
+ CImg<_cimg_Tt> get_append(const CImg<T>& img, const char axis='x', const float align=0) const {
+ if (is_empty()) return +img;
+ if (!img) return +*this;
+ return CImgList<_cimg_Tt>(*this,true).insert(img).get_append(axis,align);
+ }
+
+ //! Append two images along specified axis \specialization.
+ CImg<T> get_append(const CImg<T>& img, const char axis='x', const float align=0) const {
+ if (is_empty()) return +img;
+ if (!img) return +*this;
+ return CImgList<T>(*this,img,true).get_append(axis,align);
+ }
+
+ //@}
+ //---------------------------------------
+ //
+ //! \name Filtering / Transforms
+ //@{
+ //---------------------------------------
+
+ //! Correlate image by a kernel.
+ /**
+ \param kernel = the correlation kernel.
+ \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann)
+ \param is_normalized = enable local normalization.
+ \note
+ - The correlation of the image instance \p *this by the kernel \p kernel is defined to be:
+ res(x,y,z) = sum_{i,j,k} (*this)(x + i,y + j,z + k)*kernel(i,j,k).
+ **/
+ template<typename t>
+ CImg<T>& correlate(const CImg<t>& kernel, const bool boundary_conditions=true,
+ const bool is_normalized=false) {
+ if (is_empty() || !kernel) return *this;
+ return get_correlate(kernel,boundary_conditions,is_normalized).move_to(*this);
+ }
+
+ template<typename t>
+ CImg<_cimg_Ttfloat> get_correlate(const CImg<t>& kernel, const bool boundary_conditions=true,
+ const bool is_normalized=false) const {
+ return _correlate(kernel,boundary_conditions,is_normalized,false);
+ }
+
+ //! Correlate image by a kernel \newinstance.
+ template<typename t>
+ CImg<_cimg_Ttfloat> _correlate(const CImg<t>& kernel, const bool boundary_conditions,
+ const bool is_normalized, const bool is_convolution) const {
+ if (is_empty() || !kernel) return *this;
+ typedef _cimg_Ttfloat Ttfloat;
+ CImg<Ttfloat> res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum));
+ cimg_abort_init;
+ if (boundary_conditions && kernel._width==kernel._height &&
+ ((kernel._depth==1 && kernel._width<=5) || (kernel._depth==kernel._width && kernel._width<=3))) {
+ // Special optimization done for 2x2, 3x3, 4x4, 5x5, 2x2x2 and 3x3x3 kernel (with boundary_conditions=true).
+ CImg<t> _kernel;
+ if (is_convolution) { // Add empty column/row/slice to shift kernel center in case of convolution
+ const int dw = !(kernel.width()%2), dh = !(kernel.height()%2), dd = !(kernel.depth()%2);
+ if (dw || dh || dd)
+ kernel.get_resize(kernel.width() + dw,kernel.height() + dh,kernel.depth() + dd,-100,0,0).
+ move_to(_kernel);
+ }
+ if (!_kernel) _kernel = kernel.get_shared();
+
+ Ttfloat *ptrd = res._data;
+ CImg<T> I;
+ switch (_kernel._depth) {
+ case 3 : {
+ I.assign(27);
+ cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> _K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ if (is_normalized) {
+ const Ttfloat _M = (Ttfloat)_K.magnitude(2), M = _M*_M;
+ cimg_for3x3x3(_img,x,y,z,0,I,T) {
+ const Ttfloat N = M*(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] +
+ I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
+ I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] +
+ I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
+ I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
+ I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
+ I[18]*I[18] + I[19]*I[19] + I[20]*I[20] +
+ I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
+ I[24]*I[24] + I[25]*I[25] + I[26]*I[26]);
+ *(ptrd++) = (Ttfloat)(N?(I[ 0]*_K[ 0] + I[ 1]*_K[ 1] + I[ 2]*_K[ 2] +
+ I[ 3]*_K[ 3] + I[ 4]*_K[ 4] + I[ 5]*_K[ 5] +
+ I[ 6]*_K[ 6] + I[ 7]*_K[ 7] + I[ 8]*_K[ 8] +
+ I[ 9]*_K[ 9] + I[10]*_K[10] + I[11]*_K[11] +
+ I[12]*_K[12] + I[13]*_K[13] + I[14]*_K[14] +
+ I[15]*_K[15] + I[16]*_K[16] + I[17]*_K[17] +
+ I[18]*_K[18] + I[19]*_K[19] + I[20]*_K[20] +
+ I[21]*_K[21] + I[22]*_K[22] + I[23]*_K[23] +
+ I[24]*_K[24] + I[25]*_K[25] + I[26]*_K[26])/std::sqrt(N):0);
+ }
+ } else cimg_for3x3x3(_img,x,y,z,0,I,T)
+ *(ptrd++) = (Ttfloat)(I[ 0]*_K[ 0] + I[ 1]*_K[ 1] + I[ 2]*_K[ 2] +
+ I[ 3]*_K[ 3] + I[ 4]*_K[ 4] + I[ 5]*_K[ 5] +
+ I[ 6]*_K[ 6] + I[ 7]*_K[ 7] + I[ 8]*_K[ 8] +
+ I[ 9]*_K[ 9] + I[10]*_K[10] + I[11]*_K[11] +
+ I[12]*_K[12] + I[13]*_K[13] + I[14]*_K[14] +
+ I[15]*_K[15] + I[16]*_K[16] + I[17]*_K[17] +
+ I[18]*_K[18] + I[19]*_K[19] + I[20]*_K[20] +
+ I[21]*_K[21] + I[22]*_K[22] + I[23]*_K[23] +
+ I[24]*_K[24] + I[25]*_K[25] + I[26]*_K[26]);
+ }
+ } break;
+ case 2 : {
+ I.assign(8);
+ cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ if (is_normalized) {
+ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M;
+ cimg_for2x2x2(_img,x,y,z,0,I,T) {
+ const Ttfloat N = M*(I[0]*I[0] + I[1]*I[1] +
+ I[2]*I[2] + I[3]*I[3] +
+ I[4]*I[4] + I[5]*I[5] +
+ I[6]*I[6] + I[7]*I[7]);
+ *(ptrd++) = (Ttfloat)(N?(I[0]*K[0] + I[1]*K[1] +
+ I[2]*K[2] + I[3]*K[3] +
+ I[4]*K[4] + I[5]*K[5] +
+ I[6]*K[6] + I[7]*K[7])/std::sqrt(N):0);
+ }
+ } else cimg_for2x2x2(_img,x,y,z,0,I,T)
+ *(ptrd++) = (Ttfloat)(I[0]*K[0] + I[1]*K[1] +
+ I[2]*K[2] + I[3]*K[3] +
+ I[4]*K[4] + I[5]*K[5] +
+ I[6]*K[6] + I[7]*K[7]);
+ }
+ } break;
+ default :
+ case 1 :
+ switch (_kernel._width) {
+ case 6 : {
+ I.assign(36);
+ cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ if (is_normalized) {
+ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M;
+ cimg_forZ(_img,z) cimg_for6x6(_img,x,y,z,0,I,T) {
+ const Ttfloat N = M*(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] +
+ I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] +
+ I[10]*I[10] + I[11]*I[11] + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
+ I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + I[18]*I[18] + I[19]*I[19] +
+ I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + I[24]*I[24] +
+ I[25]*I[25] + I[26]*I[26] + I[27]*I[27] + I[28]*I[28] + I[29]*I[29] +
+ I[30]*I[30] + I[31]*I[31] + I[32]*I[32] + I[33]*I[33] + I[34]*I[34] +
+ I[35]*I[35]);
+ *(ptrd++) = (Ttfloat)(N?(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] +
+ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] +
+ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] +
+ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] +
+ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] +
+ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] +
+ I[24]*K[24] + I[25]*K[25] + I[26]*K[26] + I[27]*K[27] +
+ I[28]*K[28] + I[29]*K[29] + I[30]*K[30] + I[31]*K[31] +
+ I[32]*K[32] + I[33]*K[33] + I[34]*K[34] + I[35]*K[35])/
+ std::sqrt(N):0);
+ }
+ } else cimg_forZ(_img,z) cimg_for6x6(_img,x,y,z,0,I,T)
+ *(ptrd++) = (Ttfloat)(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] +
+ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] +
+ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] +
+ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] +
+ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] +
+ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] +
+ I[24]*K[24] + I[25]*K[25] + I[26]*K[26] + I[27]*K[27] +
+ I[28]*K[28] + I[29]*K[29] + I[30]*K[30] + I[31]*K[31] +
+ I[32]*K[32] + I[33]*K[33] + I[34]*K[34] + I[35]*K[35]);
+ }
+ } break;
+ case 5 : {
+ I.assign(25);
+ cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ if (is_normalized) {
+ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M;
+ cimg_forZ(_img,z) cimg_for5x5(_img,x,y,z,0,I,T) {
+ const Ttfloat N = M*(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] +
+ I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] +
+ I[10]*I[10] + I[11]*I[11] + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
+ I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + I[18]*I[18] + I[19]*I[19] +
+ I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + I[24]*I[24]);
+ *(ptrd++) = (Ttfloat)(N?(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] +
+ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] +
+ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] +
+ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] +
+ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] +
+ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] +
+ I[24]*K[24])/std::sqrt(N):0);
+ }
+ } else cimg_forZ(_img,z) cimg_for5x5(_img,x,y,z,0,I,T)
+ *(ptrd++) = (Ttfloat)(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] +
+ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] +
+ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] +
+ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15] +
+ I[16]*K[16] + I[17]*K[17] + I[18]*K[18] + I[19]*K[19] +
+ I[20]*K[20] + I[21]*K[21] + I[22]*K[22] + I[23]*K[23] +
+ I[24]*K[24]);
+ }
+ } break;
+ case 4 : {
+ I.assign(16);
+ cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ if (is_normalized) {
+ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M;
+ cimg_forZ(_img,z) cimg_for4x4(_img,x,y,z,0,I,T) {
+ const Ttfloat N = M*(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] +
+ I[ 4]*I[ 4] + I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] +
+ I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
+ I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15]);
+ *(ptrd++) = (Ttfloat)(N?(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] +
+ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] +
+ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] +
+ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15])/
+ std::sqrt(N):0);
+ }
+ } else cimg_forZ(_img,z) cimg_for4x4(_img,x,y,z,0,I,T)
+ *(ptrd++) = (Ttfloat)(I[ 0]*K[ 0] + I[ 1]*K[ 1] + I[ 2]*K[ 2] + I[ 3]*K[ 3] +
+ I[ 4]*K[ 4] + I[ 5]*K[ 5] + I[ 6]*K[ 6] + I[ 7]*K[ 7] +
+ I[ 8]*K[ 8] + I[ 9]*K[ 9] + I[10]*K[10] + I[11]*K[11] +
+ I[12]*K[12] + I[13]*K[13] + I[14]*K[14] + I[15]*K[15]);
+ }
+ } break;
+ case 3 : {
+ I.assign(9);
+ cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ if (is_normalized) {
+ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M;
+ cimg_forZ(_img,z) cimg_for3x3(_img,x,y,z,0,I,T) {
+ const Ttfloat N = M*(I[0]*I[0] + I[1]*I[1] + I[2]*I[2] +
+ I[3]*I[3] + I[4]*I[4] + I[5]*I[5] +
+ I[6]*I[6] + I[7]*I[7] + I[8]*I[8]);
+ *(ptrd++) = (Ttfloat)(N?(I[0]*K[0] + I[1]*K[1] + I[2]*K[2] +
+ I[3]*K[3] + I[4]*K[4] + I[5]*K[5] +
+ I[6]*K[6] + I[7]*K[7] + I[8]*K[8])/std::sqrt(N):0);
+ }
+ } else cimg_forZ(_img,z) cimg_for3x3(_img,x,y,z,0,I,T)
+ *(ptrd++) = (Ttfloat)(I[0]*K[0] + I[1]*K[1] + I[2]*K[2] +
+ I[3]*K[3] + I[4]*K[4] + I[5]*K[5] +
+ I[6]*K[6] + I[7]*K[7] + I[8]*K[8]);
+ }
+ } break;
+ case 2 : {
+ I.assign(4);
+ cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ if (is_normalized) {
+ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M;
+ cimg_forZ(_img,z) cimg_for2x2(_img,x,y,z,0,I,T) {
+ const Ttfloat N = M*(I[0]*I[0] + I[1]*I[1] +
+ I[2]*I[2] + I[3]*I[3]);
+ *(ptrd++) = (Ttfloat)(N?(I[0]*K[0] + I[1]*K[1] +
+ I[2]*K[2] + I[3]*K[3])/std::sqrt(N):0);
+ }
+ } else cimg_forZ(_img,z) cimg_for2x2(_img,x,y,z,0,I,T)
+ *(ptrd++) = (Ttfloat)(I[0]*K[0] + I[1]*K[1] +
+ I[2]*K[2] + I[3]*K[3]);
+ }
+ } break;
+ case 1 :
+ if (is_normalized) res.fill(1);
+ else cimg_forC(res,c) {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = _kernel.get_shared_channel(c%_kernel._spectrum);
+ res.get_shared_channel(c).assign(_img)*=K[0];
+ }
+ break;
+ }
+ }
+ } else { // Generic version for other kernels and boundary conditions.
+ int
+ mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2,
+ mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1;
+ if (is_convolution) cimg::swap(mx1,mx2,my1,my2,mz1,mz2); // Shift kernel center in case of convolution
+ const int
+ mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+ cimg_forC(res,c) cimg_abort_try {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = kernel.get_shared_channel(c%kernel._spectrum);
+ if (is_normalized) { // Normalized correlation.
+ const Ttfloat _M = (Ttfloat)K.magnitude(2), M = _M*_M;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=32768))
+ for (int z = mz1; z<mze; ++z)
+ for (int y = my1; y<mye; ++y)
+ for (int x = mx1; x<mxe; ++x) cimg_abort_try2 {
+ cimg_abort_test2();
+ Ttfloat val = 0, N = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm)
+ for (int ym = -my1; ym<=my2; ++ym)
+ for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Ttfloat _val = (Ttfloat)_img(x + xm,y + ym,z + zm);
+ val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm);
+ N+=_val*_val;
+ }
+ N*=M;
+ res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0);
+ } cimg_abort_catch2()
+ if (boundary_conditions)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ for (int x = 0; x<width();
+ (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1 - 1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0, N = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm)
+ for (int ym = -my1; ym<=my2; ++ym)
+ for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Ttfloat _val = (Ttfloat)_img._atXYZ(x + xm,y + ym,z + zm);
+ val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm);
+ N+=_val*_val;
+ }
+ N*=M;
+ res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0);
+ }
+ } cimg_abort_catch2()
+ else
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ for (int x = 0; x<width();
+ (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1 - 1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0, N = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm)
+ for (int ym = -my1; ym<=my2; ++ym)
+ for (int xm = -mx1; xm<=mx2; ++xm) {
+ const Ttfloat _val = (Ttfloat)_img.atXYZ(x + xm,y + ym,z + zm,0,(T)0);
+ val+=_val*K(mx1 + xm,my1 + ym,mz1 + zm);
+ N+=_val*_val;
+ }
+ N*=M;
+ res(x,y,z,c) = (Ttfloat)(N?val/std::sqrt(N):0);
+ }
+ } cimg_abort_catch2()
+ } else { // Classical correlation.
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=32768))
+ for (int z = mz1; z<mze; ++z)
+ for (int y = my1; y<mye; ++y)
+ for (int x = mx1; x<mxe; ++x) cimg_abort_try2 {
+ cimg_abort_test2();
+ Ttfloat val = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm)
+ for (int ym = -my1; ym<=my2; ++ym)
+ for (int xm = -mx1; xm<=mx2; ++xm)
+ val+=_img(x + xm,y + ym,z + zm)*K(mx1 + xm,my1 + ym,mz1 + zm);
+ res(x,y,z,c) = (Ttfloat)val;
+ } cimg_abort_catch2()
+ if (boundary_conditions)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ for (int x = 0; x<width();
+ (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1 - 1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm)
+ for (int ym = -my1; ym<=my2; ++ym)
+ for (int xm = -mx1; xm<=mx2; ++xm)
+ val+=_img._atXYZ(x + xm,y + ym,z + zm)*K(mx1 + xm,my1 + ym,mz1 + zm);
+ res(x,y,z,c) = (Ttfloat)val;
+ }
+ } cimg_abort_catch2()
+ else
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ for (int x = 0; x<width();
+ (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1 - 1 || x>=mxe)?++x:(x=mxe))) {
+ Ttfloat val = 0;
+ for (int zm = -mz1; zm<=mz2; ++zm)
+ for (int ym = -my1; ym<=my2; ++ym)
+ for (int xm = -mx1; xm<=mx2; ++xm)
+ val+=_img.atXYZ(x + xm,y + ym,z + zm,0,(T)0)*K(mx1 + xm,my1 + ym,mz1 + zm);
+ res(x,y,z,c) = (Ttfloat)val;
+ }
+ } cimg_abort_catch2()
+ }
+ } cimg_abort_catch()
+ }
+ cimg_abort_test();
+ return res;
+ }
+
+ //! Convolve image by a kernel.
+ /**
+ \param kernel = the correlation kernel.
+ \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann)
+ \param is_normalized = enable local normalization.
+ \note
+ - The result \p res of the convolution of an image \p img by a kernel \p kernel is defined to be:
+ res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*kernel(i,j,k)
+ **/
+ template<typename t>
+ CImg<T>& convolve(const CImg<t>& kernel, const bool boundary_conditions=true, const bool is_normalized=false) {
+ if (is_empty() || !kernel) return *this;
+ return get_convolve(kernel,boundary_conditions,is_normalized).move_to(*this);
+ }
+
+ //! Convolve image by a kernel \newinstance.
+ template<typename t>
+ CImg<_cimg_Ttfloat> get_convolve(const CImg<t>& kernel, const bool boundary_conditions=true,
+ const bool is_normalized=false) const {
+ return _correlate(CImg<t>(kernel._data,kernel.size(),1,1,1,true).get_mirror('x').
+ resize(kernel,-1),boundary_conditions,is_normalized,true);
+ }
+
+ //! Cumulate image values, optionally along specified axis.
+ /**
+ \param axis Cumulation axis. Set it to 0 to cumulate all values globally without taking axes into account.
+ **/
+ CImg<T>& cumulate(const char axis=0) {
+ switch (cimg::lowercase(axis)) {
+ case 'x' :
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=512 && _height*_depth*_spectrum>=16))
+ cimg_forYZC(*this,y,z,c) {
+ T *ptrd = data(0,y,z,c);
+ Tlong cumul = (Tlong)0;
+ cimg_forX(*this,x) { cumul+=(Tlong)*ptrd; *(ptrd++) = (T)cumul; }
+ }
+ break;
+ case 'y' : {
+ const ulongT w = (ulongT)_width;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_height>=512 && _width*_depth*_spectrum>=16))
+ cimg_forXZC(*this,x,z,c) {
+ T *ptrd = data(x,0,z,c);
+ Tlong cumul = (Tlong)0;
+ cimg_forY(*this,y) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=w; }
+ }
+ } break;
+ case 'z' : {
+ const ulongT wh = (ulongT)_width*_height;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_depth>=512 && _width*_depth*_spectrum>=16))
+ cimg_forXYC(*this,x,y,c) {
+ T *ptrd = data(x,y,0,c);
+ Tlong cumul = (Tlong)0;
+ cimg_forZ(*this,z) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=wh; }
+ }
+ } break;
+ case 'c' : {
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_spectrum>=512 && _width*_height*_depth>=16))
+ cimg_forXYZ(*this,x,y,z) {
+ T *ptrd = data(x,y,z,0);
+ Tlong cumul = (Tlong)0;
+ cimg_forC(*this,c) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; ptrd+=whd; }
+ }
+ } break;
+ default : { // Global cumulation.
+ Tlong cumul = (Tlong)0;
+ cimg_for(*this,ptrd,T) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; }
+ }
+ }
+ return *this;
+ }
+
+ //! Cumulate image values, optionally along specified axis \newinstance.
+ CImg<Tlong> get_cumulate(const char axis=0) const {
+ return CImg<Tlong>(*this,false).cumulate(axis);
+ }
+
+ //! Cumulate image values, along specified axes.
+ /**
+ \param axes Cumulation axes, as a C-string.
+ \note \c axes may contains multiple characters, e.g. \c "xyz"
+ **/
+ CImg<T>& cumulate(const char *const axes) {
+ for (const char *s = axes; *s; ++s) cumulate(*s);
+ return *this;
+ }
+
+ //! Cumulate image values, along specified axes \newintance.
+ CImg<Tlong> get_cumulate(const char *const axes) const {
+ return CImg<Tlong>(*this,false).cumulate(axes);
+ }
+
+ //! Erode image by a structuring element.
+ /**
+ \param kernel Structuring element.
+ \param boundary_conditions Boundary conditions.
+ \param is_real Do the erosion in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false).
+ **/
+ template<typename t>
+ CImg<T>& erode(const CImg<t>& kernel, const bool boundary_conditions=true,
+ const bool is_real=false) {
+ if (is_empty() || !kernel) return *this;
+ return get_erode(kernel,boundary_conditions,is_real).move_to(*this);
+ }
+
+ //! Erode image by a structuring element \newinstance.
+ template<typename t>
+ CImg<_cimg_Tt> get_erode(const CImg<t>& kernel, const bool boundary_conditions=true,
+ const bool is_real=false) const {
+ if (is_empty() || !kernel) return *this;
+ if (!is_real && kernel==0) return CImg<T>(width(),height(),depth(),spectrum(),0);
+ typedef _cimg_Tt Tt;
+ CImg<Tt> res(_width,_height,_depth,std::max(_spectrum,kernel._spectrum));
+ const int
+ mx2 = kernel.width()/2, my2 = kernel.height()/2, mz2 = kernel.depth()/2,
+ mx1 = kernel.width() - mx2 - 1, my1 = kernel.height() - my2 - 1, mz1 = kernel.depth() - mz2 - 1,
+ mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+ cimg_abort_init;
+ cimg_forC(*this,c) cimg_abort_try {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = kernel.get_shared_channel(c%kernel._spectrum);
+ if (is_real) { // Real erosion
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=32768))
+ for (int z = mz1; z<mze; ++z)
+ for (int y = my1; y<mye; ++y)
+ for (int x = mx1; x<mxe; ++x) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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 = K(mx1 + xm,my1 + ym,mz1 + zm);
+ const Tt cval = (Tt)(_img(x + xm,y + ym,z + zm) - mval);
+ if (cval<min_val) min_val = cval;
+ }
+ res(x,y,z,c) = min_val;
+ } cimg_abort_catch2()
+ if (boundary_conditions)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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 = K(mx1 + xm,my1 + ym,mz1 + zm);
+ const Tt cval = (Tt)(_img._atXYZ(x + xm,y + ym,z + zm) - mval);
+ if (cval<min_val) min_val = cval;
+ }
+ res(x,y,z,c) = min_val;
+ }
+ } cimg_abort_catch2()
+ else
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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 = K(mx1 + xm,my1 + ym,mz1 + zm);
+ const Tt cval = (Tt)(_img.atXYZ(x + xm,y + ym,z + zm,0,(T)0) - mval);
+ if (cval<min_val) min_val = cval;
+ }
+ res(x,y,z,c) = min_val;
+ }
+ } cimg_abort_catch2()
+
+ } else { // Binary erosion
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=32768))
+ for (int z = mz1; z<mze; ++z)
+ for (int y = my1; y<mye; ++y)
+ for (int x = mx1; x<mxe; ++x) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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)
+ if (K(mx1 + xm,my1 + ym,mz1 + zm)) {
+ const Tt cval = (Tt)_img(x + xm,y + ym,z + zm);
+ if (cval<min_val) min_val = cval;
+ }
+ res(x,y,z,c) = min_val;
+ } cimg_abort_catch2()
+ if (boundary_conditions)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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)
+ if (K(mx1 + xm,my1 + ym,mz1 + zm)) {
+ const T cval = (Tt)_img._atXYZ(x + xm,y + ym,z + zm);
+ if (cval<min_val) min_val = cval;
+ }
+ res(x,y,z,c) = min_val;
+ }
+ } cimg_abort_catch2()
+ else
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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)
+ if (K(mx1 + xm,my1 + ym,mz1 + zm)) {
+ const T cval = (Tt)_img.atXYZ(x + xm,y + ym,z + zm,0,(T)0);
+ if (cval<min_val) min_val = cval;
+ }
+ res(x,y,z,c) = min_val;
+ }
+ } cimg_abort_catch2()
+ }
+ } cimg_abort_catch()
+ cimg_abort_test();
+ return res;
+ }
+
+ //! Erode image by a rectangular structuring element of specified size.
+ /**
+ \param sx Width of the structuring element.
+ \param sy Height of the structuring element.
+ \param sz Depth of the structuring element.
+ **/
+ CImg<T>& erode(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) {
+ if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this;
+ if (sx>1 && _width>1) { // Along X-axis.
+ const int L = width(), off = 1, s = (int)sx, _s2 = s/2 + 1, _s1 = s - _s2, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+ CImg<T> buf(L);
+ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288))
+ cimg_forYZC(*this,y,z,c) {
+ T *const ptrdb = buf._data, *ptrd = buf._data, *const ptrde = buf._data + L - 1;
+ const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+ 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;
+ if (ptrs>=ptrse) {
+ T *pd = data(0,y,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; }
+ } else {
+ 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(0,y,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; }
+ }
+ }
+ }
+
+ 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);
+ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288))
+ cimg_forXZC(*this,x,z,c) {
+ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+ const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+ 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;
+ if (ptrs>=ptrse) {
+ T *pd = data(x,0,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; }
+ } else {
+ 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,0,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; }
+ }
+ }
+ }
+
+ 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);
+ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288))
+ cimg_forXYC(*this,x,y,c) {
+ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+ const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+ 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;
+ if (ptrs>=ptrse) {
+ T *pd = data(x,y,0,c); cur = std::min(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; }
+ } else {
+ 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,0,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Erode image by a rectangular structuring element of specified size \newinstance.
+ 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 specified size.
+ /**
+ \param s Size of the structuring element.
+ **/
+ CImg<T>& erode(const unsigned int s) {
+ return erode(s,s,s);
+ }
+
+ //! Erode the image by a square structuring element of specified size \newinstance.
+ CImg<T> get_erode(const unsigned int s) const {
+ return (+*this).erode(s);
+ }
+
+ //! Dilate image by a structuring element.
+ /**
+ \param kernel Structuring element.
+ \param boundary_conditions Boundary conditions.
+ \param is_real Do the dilation in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false).
+ **/
+ template<typename t>
+ CImg<T>& dilate(const CImg<t>& kernel, const bool boundary_conditions=true,
+ const bool is_real=false) {
+ if (is_empty() || !kernel) return *this;
+ return get_dilate(kernel,boundary_conditions,is_real).move_to(*this);
+ }
+
+ //! Dilate image by a structuring element \newinstance.
+ template<typename t>
+ CImg<_cimg_Tt> get_dilate(const CImg<t>& kernel, const bool boundary_conditions=true,
+ const bool is_real=false) const {
+ if (is_empty() || !kernel || (!is_real && kernel==0)) return *this;
+ typedef _cimg_Tt Tt;
+ CImg<Tt> res(_width,_height,_depth,_spectrum);
+ const int
+ mx1 = kernel.width()/2, my1 = kernel.height()/2, mz1 = kernel.depth()/2,
+ mx2 = kernel.width() - mx1 - 1, my2 = kernel.height() - my1 - 1, mz2 = kernel.depth() - mz1 - 1,
+ mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2;
+ cimg_abort_init;
+ cimg_forC(*this,c) cimg_abort_try {
+ cimg_abort_test();
+ const CImg<T> _img = get_shared_channel(c%_spectrum);
+ const CImg<t> K = kernel.get_shared_channel(c%kernel._spectrum);
+ if (is_real) { // Real dilation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=32768))
+ for (int z = mz1; z<mze; ++z)
+ for (int y = my1; y<mye; ++y)
+ for (int x = mx1; x<mxe; ++x) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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 = K(mx2 - xm,my2 - ym,mz2 - zm);
+ const Tt cval = (Tt)(_img(x + xm,y + ym,z + zm) + mval);
+ if (cval>max_val) max_val = cval;
+ }
+ res(x,y,z,c) = max_val;
+ } cimg_abort_catch2()
+ if (boundary_conditions)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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 = K(mx2 - xm,my2 - ym,mz2 - zm);
+ const Tt cval = (Tt)(_img._atXYZ(x + xm,y + ym,z + zm) + mval);
+ if (cval>max_val) max_val = cval;
+ }
+ res(x,y,z,c) = max_val;
+ }
+ } cimg_abort_catch2()
+ else
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(*this,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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 = K(mx2 - xm,my2 - ym,mz2 - zm);
+ const Tt cval = (Tt)(_img.atXYZ(x + xm,y + ym,z + zm,0,(T)0) + mval);
+ if (cval>max_val) max_val = cval;
+ }
+ res(x,y,z,c) = max_val;
+ }
+ } cimg_abort_catch2()
+ } else { // Binary dilation
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ for (int z = mz1; z<mze; ++z)
+ for (int y = my1; y<mye; ++y)
+ for (int x = mx1; x<mxe; ++x) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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)
+ if (K(mx2 - xm,my2 - ym,mz2 - zm)) {
+ const Tt cval = (Tt)_img(x + xm,y + ym,z + zm);
+ if (cval>max_val) max_val = cval;
+ }
+ res(x,y,z,c) = max_val;
+ } cimg_abort_catch2()
+ if (boundary_conditions)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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)
+ if (K(mx2 - xm,my2 - ym,mz2 - zm)) {
+ const T cval = (Tt)_img._atXYZ(x + xm,y + ym,z + zm);
+ if (cval>max_val) max_val = cval;
+ }
+ res(x,y,z,c) = max_val;
+ }
+ } cimg_abort_catch2()
+ else
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=128))
+ cimg_forYZ(res,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ 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)
+ if (K(mx2 - xm,my2 - ym,mz2 - zm)) {
+ const T cval = (Tt)_img.atXYZ(x + xm,y + ym,z + zm,0,(T)0);
+ if (cval>max_val) max_val = cval;
+ }
+ res(x,y,z,c) = max_val;
+ }
+ } cimg_abort_catch2()
+ }
+ } cimg_abort_catch()
+ cimg_abort_test();
+ return res;
+ }
+
+ //! Dilate image by a rectangular structuring element of specified size.
+ /**
+ \param sx Width of the structuring element.
+ \param sy Height of the structuring element.
+ \param sz Depth of the structuring element.
+ **/
+ CImg<T>& dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) {
+ if (is_empty() || (sx==1 && sy==1 && sz==1)) return *this;
+ if (sx>1 && _width>1) { // Along X-axis.
+ const int L = width(), off = 1, s = (int)sx, _s1 = s/2, _s2 = s - _s1, s1 = _s1>L?L:_s1, s2 = _s2>L?L:_s2;
+ CImg<T> buf(L);
+ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288))
+ cimg_forYZC(*this,y,z,c) {
+ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+ const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+ 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;
+ if (ptrs>=ptrse) {
+ T *pd = data(0,y,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; }
+ } else {
+ 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(0,y,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; }
+ }
+ }
+ }
+
+ 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);
+ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288))
+ cimg_forXZC(*this,x,z,c) {
+ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+ const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+ 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;
+ if (ptrs>=ptrse) {
+ T *pd = data(x,0,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; }
+ } else {
+ 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,0,z,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; }
+ }
+ }
+ }
+
+ 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);
+ cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288))
+ cimg_forXYC(*this,x,y,c) {
+ T *const ptrdb = buf._data, *ptrd = ptrdb, *const ptrde = buf._data + L - 1;
+ const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + L*off - off;
+ 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;
+ if (ptrs>=ptrse) {
+ T *pd = data(x,y,0,c); cur = std::max(cur,*ptrse); cimg_forX(buf,x) { *pd = cur; pd+=off; }
+ } else {
+ 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,0,c); cimg_for(buf,ps,T) { *pd = *ps; pd+=off; }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Dilate image by a rectangular structuring element of specified size \newinstance.
+ CImg<T> get_dilate(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const {
+ return (+*this).dilate(sx,sy,sz);
+ }
+
+ //! Dilate image by a square structuring element of specified size.
+ /**
+ \param s Size of the structuring element.
+ **/
+ CImg<T>& dilate(const unsigned int s) {
+ return dilate(s,s,s);
+ }
+
+ //! Dilate image by a square structuring element of specified size \newinstance.
+ CImg<T> get_dilate(const unsigned int s) const {
+ return (+*this).dilate(s);
+ }
+
+ //! Compute watershed transform.
+ /**
+ \param priority Priority map.
+ \param is_high_connectivity Boolean that choose between 4(false)- or 8(true)-connectivity
+ in 2d case, and between 6(false)- or 26(true)-connectivity in 3d case.
+ \note Non-zero values of the instance instance are propagated to zero-valued ones according to
+ specified the priority map.
+ **/
+ template<typename t>
+ CImg<T>& watershed(const CImg<t>& priority, const bool is_high_connectivity=false) {
+#define _cimg_watershed_init(cond,X,Y,Z) \
+ if (cond && !(*this)(X,Y,Z)) Q._priority_queue_insert(labels,sizeQ,priority(X,Y,Z),X,Y,Z,nb_seeds)
+
+#define _cimg_watershed_propagate(cond,X,Y,Z) \
+ if (cond) { \
+ if ((*this)(X,Y,Z)) { \
+ ns = labels(X,Y,Z) - 1; xs = seeds(ns,0); ys = seeds(ns,1); zs = seeds(ns,2); \
+ d = cimg::sqr((float)x - xs) + cimg::sqr((float)y - ys) + cimg::sqr((float)z - zs); \
+ if (d<dmin) { dmin = d; nmin = ns; label = (*this)(xs,ys,zs); } \
+ } else Q._priority_queue_insert(labels,sizeQ,priority(X,Y,Z),X,Y,Z,n); \
+ }
+
+ if (is_empty()) return *this;
+ if (!is_sameXYZ(priority))
+ throw CImgArgumentException(_cimg_instance
+ "watershed(): image instance and specified priority (%u,%u,%u,%u,%p) "
+ "have different dimensions.",
+ cimg_instance,
+ priority._width,priority._height,priority._depth,priority._spectrum,priority._data);
+ if (_spectrum!=1) {
+ cimg_forC(*this,c)
+ get_shared_channel(c).watershed(priority.get_shared_channel(c%priority._spectrum));
+ return *this;
+ }
+
+ CImg<uintT> labels(_width,_height,_depth,1,0), seeds(64,3);
+ CImg<typename cimg::superset2<T,t,int>::type> Q;
+ unsigned int sizeQ = 0;
+ int px, nx, py, ny, pz, nz;
+ bool is_px, is_nx, is_py, is_ny, is_pz, is_nz;
+ const bool is_3d = _depth>1;
+
+ // Find seed points and insert them in priority queue.
+ unsigned int nb_seeds = 0;
+ const T *ptrs = _data;
+ cimg_forXYZ(*this,x,y,z) if (*(ptrs++)) { // 3d version
+ if (nb_seeds>=seeds._width) seeds.resize(2*seeds._width,3,1,1,0);
+ seeds(nb_seeds,0) = x; seeds(nb_seeds,1) = y; seeds(nb_seeds++,2) = z;
+ px = x - 1; nx = x + 1;
+ py = y - 1; ny = y + 1;
+ pz = z - 1; nz = z + 1;
+ is_px = px>=0; is_nx = nx<width();
+ is_py = py>=0; is_ny = ny<height();
+ is_pz = pz>=0; is_nz = nz<depth();
+ _cimg_watershed_init(is_px,px,y,z);
+ _cimg_watershed_init(is_nx,nx,y,z);
+ _cimg_watershed_init(is_py,x,py,z);
+ _cimg_watershed_init(is_ny,x,ny,z);
+ if (is_3d) {
+ _cimg_watershed_init(is_pz,x,y,pz);
+ _cimg_watershed_init(is_nz,x,y,nz);
+ }
+ if (is_high_connectivity) {
+ _cimg_watershed_init(is_px && is_py,px,py,z);
+ _cimg_watershed_init(is_nx && is_py,nx,py,z);
+ _cimg_watershed_init(is_px && is_ny,px,ny,z);
+ _cimg_watershed_init(is_nx && is_ny,nx,ny,z);
+ if (is_3d) {
+ _cimg_watershed_init(is_px && is_pz,px,y,pz);
+ _cimg_watershed_init(is_nx && is_pz,nx,y,pz);
+ _cimg_watershed_init(is_px && is_nz,px,y,nz);
+ _cimg_watershed_init(is_nx && is_nz,nx,y,nz);
+ _cimg_watershed_init(is_py && is_pz,x,py,pz);
+ _cimg_watershed_init(is_ny && is_pz,x,ny,pz);
+ _cimg_watershed_init(is_py && is_nz,x,py,nz);
+ _cimg_watershed_init(is_ny && is_nz,x,ny,nz);
+ _cimg_watershed_init(is_px && is_py && is_pz,px,py,pz);
+ _cimg_watershed_init(is_nx && is_py && is_pz,nx,py,pz);
+ _cimg_watershed_init(is_px && is_ny && is_pz,px,ny,pz);
+ _cimg_watershed_init(is_nx && is_ny && is_pz,nx,ny,pz);
+ _cimg_watershed_init(is_px && is_py && is_nz,px,py,nz);
+ _cimg_watershed_init(is_nx && is_py && is_nz,nx,py,nz);
+ _cimg_watershed_init(is_px && is_ny && is_nz,px,ny,nz);
+ _cimg_watershed_init(is_nx && is_ny && is_nz,nx,ny,nz);
+ }
+ }
+ labels(x,y,z) = nb_seeds;
+ }
+
+ // Start watershed computation.
+ while (sizeQ) {
+
+ // Get and remove point with maximal priority from the queue.
+ const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3);
+ const unsigned int n = labels(x,y,z);
+ px = x - 1; nx = x + 1;
+ py = y - 1; ny = y + 1;
+ pz = z - 1; nz = z + 1;
+ is_px = px>=0; is_nx = nx<width();
+ is_py = py>=0; is_ny = ny<height();
+ is_pz = pz>=0; is_nz = nz<depth();
+
+ // Check labels of the neighbors.
+ Q._priority_queue_remove(sizeQ);
+
+ unsigned int xs, ys, zs, ns, nmin = 0;
+ float d, dmin = cimg::type<float>::inf();
+ T label = (T)0;
+ _cimg_watershed_propagate(is_px,px,y,z);
+ _cimg_watershed_propagate(is_nx,nx,y,z);
+ _cimg_watershed_propagate(is_py,x,py,z);
+ _cimg_watershed_propagate(is_ny,x,ny,z);
+ if (is_3d) {
+ _cimg_watershed_propagate(is_pz,x,y,pz);
+ _cimg_watershed_propagate(is_nz,x,y,nz);
+ }
+ if (is_high_connectivity) {
+ _cimg_watershed_propagate(is_px && is_py,px,py,z);
+ _cimg_watershed_propagate(is_nx && is_py,nx,py,z);
+ _cimg_watershed_propagate(is_px && is_ny,px,ny,z);
+ _cimg_watershed_propagate(is_nx && is_ny,nx,ny,z);
+ if (is_3d) {
+ _cimg_watershed_propagate(is_px && is_pz,px,y,pz);
+ _cimg_watershed_propagate(is_nx && is_pz,nx,y,pz);
+ _cimg_watershed_propagate(is_px && is_nz,px,y,nz);
+ _cimg_watershed_propagate(is_nx && is_nz,nx,y,nz);
+ _cimg_watershed_propagate(is_py && is_pz,x,py,pz);
+ _cimg_watershed_propagate(is_ny && is_pz,x,ny,pz);
+ _cimg_watershed_propagate(is_py && is_nz,x,py,nz);
+ _cimg_watershed_propagate(is_ny && is_nz,x,ny,nz);
+ _cimg_watershed_propagate(is_px && is_py && is_pz,px,py,pz);
+ _cimg_watershed_propagate(is_nx && is_py && is_pz,nx,py,pz);
+ _cimg_watershed_propagate(is_px && is_ny && is_pz,px,ny,pz);
+ _cimg_watershed_propagate(is_nx && is_ny && is_pz,nx,ny,pz);
+ _cimg_watershed_propagate(is_px && is_py && is_nz,px,py,nz);
+ _cimg_watershed_propagate(is_nx && is_py && is_nz,nx,py,nz);
+ _cimg_watershed_propagate(is_px && is_ny && is_nz,px,ny,nz);
+ _cimg_watershed_propagate(is_nx && is_ny && is_nz,nx,ny,nz);
+ }
+ }
+ (*this)(x,y,z) = label;
+ labels(x,y,z) = ++nmin;
+ }
+ return *this;
+ }
+
+ //! Compute watershed transform \newinstance.
+ template<typename t>
+ CImg<T> get_watershed(const CImg<t>& priority, const bool is_high_connectivity=false) const {
+ return (+*this).watershed(priority,is_high_connectivity);
+ }
+
+ // [internal] Insert/Remove items in priority queue, for watershed/distance transforms.
+ template<typename tq, typename tv>
+ bool _priority_queue_insert(CImg<tq>& is_queued, unsigned int& siz, const tv value,
+ const unsigned int x, const unsigned int y, const unsigned int z,
+ const unsigned int n=1) {
+ if (is_queued(x,y,z)) return false;
+ is_queued(x,y,z) = (tq)n;
+ if (++siz>=_width) { if (!is_empty()) resize(_width*2,4,1,1,0); else assign(64,4); }
+ (*this)(siz - 1,0) = (T)value;
+ (*this)(siz - 1,1) = (T)x;
+ (*this)(siz - 1,2) = (T)y;
+ (*this)(siz - 1,3) = (T)z;
+ for (unsigned int pos = siz - 1, par = 0; pos && value>(*this)(par=(pos + 1)/2 - 1,0); pos = par) {
+ cimg::swap((*this)(pos,0),(*this)(par,0));
+ cimg::swap((*this)(pos,1),(*this)(par,1));
+ cimg::swap((*this)(pos,2),(*this)(par,2));
+ cimg::swap((*this)(pos,3),(*this)(par,3));
+ }
+ return true;
+ }
+
+ CImg<T>& _priority_queue_remove(unsigned int& siz) {
+ (*this)(0,0) = (*this)(--siz,0);
+ (*this)(0,1) = (*this)(siz,1);
+ (*this)(0,2) = (*this)(siz,2);
+ (*this)(0,3) = (*this)(siz,3);
+ const float value = (*this)(0,0);
+ for (unsigned int pos = 0, left = 0, right = 0;
+ ((right=2*(pos + 1),(left=right - 1))<siz && value<(*this)(left,0)) ||
+ (right<siz && value<(*this)(right,0));) {
+ if (right<siz) {
+ if ((*this)(left,0)>(*this)(right,0)) {
+ cimg::swap((*this)(pos,0),(*this)(left,0));
+ cimg::swap((*this)(pos,1),(*this)(left,1));
+ cimg::swap((*this)(pos,2),(*this)(left,2));
+ cimg::swap((*this)(pos,3),(*this)(left,3));
+ pos = left;
+ } else {
+ cimg::swap((*this)(pos,0),(*this)(right,0));
+ cimg::swap((*this)(pos,1),(*this)(right,1));
+ cimg::swap((*this)(pos,2),(*this)(right,2));
+ cimg::swap((*this)(pos,3),(*this)(right,3));
+ pos = right;
+ }
+ } else {
+ cimg::swap((*this)(pos,0),(*this)(left,0));
+ cimg::swap((*this)(pos,1),(*this)(left,1));
+ cimg::swap((*this)(pos,2),(*this)(left,2));
+ cimg::swap((*this)(pos,3),(*this)(left,3));
+ pos = left;
+ }
+ }
+ return *this;
+ }
+
+ //! Apply recursive Deriche filter.
+ /**
+ \param sigma Standard deviation of the filter.
+ \param order Order of the filter. Can be <tt>{ 0=smooth-filter | 1=1st-derivative | 2=2nd-derivative }</tt>.
+ \param axis Axis along which the filter is computed. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param boundary_conditions Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann }</tt>.
+ **/
+ CImg<T>& deriche(const float sigma, const unsigned int order=0, const char axis='x',
+ const bool boundary_conditions=true) {
+#define _cimg_deriche_apply \
+ CImg<Tfloat> Y(N); \
+ Tfloat *ptrY = Y._data, yb = 0, yp = 0; \
+ T xp = (T)0; \
+ if (boundary_conditions) { 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 (boundary_conditions) { 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::lowercase(axis);
+ const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100;
+ if (is_empty() || (nsigma<0.1f && !order)) return *this;
+ const float
+ nnsigma = nsigma<0.1f?0.1f:nsigma,
+ alpha = 1.695f/nnsigma,
+ ema = (float)std::exp(-alpha),
+ ema2 = (float)std::exp(-2*alpha),
+ b1 = -2*ema,
+ b2 = ema2;
+ float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0;
+ switch (order) {
+ case 0 : {
+ const float k = (1-ema)*(1-ema)/(1 + 2*alpha*ema-ema2);
+ a0 = k;
+ a1 = k*(alpha - 1)*ema;
+ a2 = k*(alpha + 1)*ema;
+ a3 = -k*ema2;
+ } break;
+ case 1 : {
+ const float k = -(1-ema)*(1-ema)*(1-ema)/(2*(ema + 1)*ema);
+ a0 = a3 = 0;
+ a1 = k*ema;
+ a2 = -a1;
+ } break;
+ case 2 : {
+ const float
+ ea = (float)std::exp(-alpha),
+ k = -(ema2 - 1)/(2*alpha*ema),
+ kn = (-2*(-1 + 3*ea - 3*ea*ea + ea*ea*ea)/(3*ea + 1 + 3*ea*ea + ea*ea*ea));
+ a0 = kn;
+ a1 = -kn*(1 + k*alpha)*ema;
+ a2 = kn*(1 - k*alpha)*ema;
+ a3 = -kn*ema2;
+ } break;
+ default :
+ throw CImgArgumentException(_cimg_instance
+ "deriche(): Invalid specified filter order %u "
+ "(should be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).",
+ cimg_instance,
+ order);
+ }
+ coefp = (a0 + a1)/(1 + b1 + b2);
+ coefn = (a2 + a3)/(1 + b1 + b2);
+ switch (naxis) {
+ case 'x' : {
+ const int N = width();
+ const ulongT off = 1U;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forYZC(*this,y,z,c) { T *ptrX = data(0,y,z,c); _cimg_deriche_apply; }
+ } break;
+ case 'y' : {
+ const int N = height();
+ const ulongT off = (ulongT)_width;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXZC(*this,x,z,c) { T *ptrX = data(x,0,z,c); _cimg_deriche_apply; }
+ } break;
+ case 'z' : {
+ const int N = depth();
+ const ulongT off = (ulongT)_width*_height;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXYC(*this,x,y,c) { T *ptrX = data(x,y,0,c); _cimg_deriche_apply; }
+ } break;
+ default : {
+ const int N = spectrum();
+ const ulongT off = (ulongT)_width*_height*_depth;
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXYZ(*this,x,y,z) { T *ptrX = data(x,y,z,0); _cimg_deriche_apply; }
+ }
+ }
+ return *this;
+ }
+
+ //! Apply recursive Deriche filter \newinstance.
+ CImg<Tfloat> get_deriche(const float sigma, const unsigned int order=0, const char axis='x',
+ const bool boundary_conditions=true) const {
+ return CImg<Tfloat>(*this,false).deriche(sigma,order,axis,boundary_conditions);
+ }
+
+ // [internal] Apply a recursive filter (used by CImg<T>::vanvliet()).
+ /*
+ \param ptr the pointer of the data
+ \param filter the coefficient of the filter in the following order [n,n - 1,n - 2,n - 3].
+ \param N size of the data
+ \param off the offset between two data point
+ \param order the order of the filter 0 (smoothing), 1st derivtive, 2nd derivative, 3rd derivative
+ \param boundary_conditions Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann }</tt>.
+ \note Boundary condition using B. Triggs method (IEEE trans on Sig Proc 2005).
+ */
+ static void _cimg_recursive_apply(T *data, const double filter[], const int N, const ulongT off,
+ const unsigned int order, const bool boundary_conditions) {
+ double val[4] = { 0 }; // res[n,n - 1,n - 2,n - 3,..] or res[n,n + 1,n + 2,n + 3,..]
+ const double
+ sumsq = filter[0], sum = sumsq * sumsq,
+ a1 = filter[1], a2 = filter[2], a3 = filter[3],
+ scaleM = 1.0 / ( (1.0 + a1 - a2 + a3) * (1.0 - a1 - a2 - a3) * (1.0 + a2 + (a1 - a3) * a3) );
+ double M[9]; // Triggs matrix
+ M[0] = scaleM * (-a3 * a1 + 1.0 - a3 * a3 - a2);
+ M[1] = scaleM * (a3 + a1) * (a2 + a3 * a1);
+ M[2] = scaleM * a3 * (a1 + a3 * a2);
+ M[3] = scaleM * (a1 + a3 * a2);
+ M[4] = -scaleM * (a2 - 1.0) * (a2 + a3 * a1);
+ M[5] = -scaleM * a3 * (a3 * a1 + a3 * a3 + a2 - 1.0);
+ M[6] = scaleM * (a3 * a1 + a2 + a1 * a1 - a2 * a2);
+ M[7] = scaleM * (a1 * a2 + a3 * a2 * a2 - a1 * a3 * a3 - a3 * a3 * a3 - a3 * a2 + a3);
+ M[8] = scaleM * a3 * (a1 + a3 * a2);
+ switch (order) {
+ case 0 : {
+ const double iplus = (boundary_conditions?data[(N - 1)*off]:(T)0);
+ for (int pass = 0; pass<2; ++pass) {
+ if (!pass) {
+ for (int k = 1; k<4; ++k) val[k] = (boundary_conditions?*data/sumsq:0);
+ } else {
+ // apply Triggs boundary conditions
+ const double
+ uplus = iplus/(1.0 - a1 - a2 - a3), vplus = uplus/(1.0 - a1 - a2 - a3),
+ unp = val[1] - uplus, unp1 = val[2] - uplus, unp2 = val[3] - uplus;
+ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2 + vplus) * sum;
+ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2 + vplus) * sum;
+ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2 + vplus) * sum;
+ *data = (T)val[0];
+ data -= off;
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ for (int n = pass; n<N; ++n) {
+ val[0] = (*data);
+ if (pass) val[0] *= sum;
+ for (int k = 1; k<4; ++k) val[0] += val[k] * filter[k];
+ *data = (T)val[0];
+ if (!pass) data += off; else data -= off;
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ if (!pass) data -= off;
+ }
+ } break;
+ case 1 : {
+ double x[3]; // [front,center,back]
+ for (int pass = 0; pass<2; ++pass) {
+ if (!pass) {
+ for (int k = 0; k<3; ++k) x[k] = (boundary_conditions?*data:(T)0);
+ for (int k = 0; k<4; ++k) val[k] = 0;
+ } else {
+ // apply Triggs boundary conditions
+ const double
+ unp = val[1], unp1 = val[2], unp2 = val[3];
+ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2) * sum;
+ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2) * sum;
+ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2) * sum;
+ *data = (T)val[0];
+ data -= off;
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ for (int n = pass; n<N - 1; ++n) {
+ if (!pass) {
+ x[0] = *(data + off);
+ val[0] = 0.5f * (x[0] - x[2]);
+ } else val[0] = (*data) * sum;
+ for (int k = 1; k<4; ++k) val[0] += val[k] * filter[k];
+ *data = (T)val[0];
+ if (!pass) {
+ data += off;
+ for (int k = 2; k>0; --k) x[k] = x[k - 1];
+ } else { data-=off;}
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ *data = (T)0;
+ }
+ } break;
+ case 2: {
+ double x[3]; // [front,center,back]
+ for (int pass = 0; pass<2; ++pass) {
+ if (!pass) {
+ for (int k = 0; k<3; ++k) x[k] = (boundary_conditions?*data:(T)0);
+ for (int k = 0; k<4; ++k) val[k] = 0;
+ } else {
+ // apply Triggs boundary conditions
+ const double
+ unp = val[1], unp1 = val[2], unp2 = val[3];
+ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2) * sum;
+ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2) * sum;
+ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2) * sum;
+ *data = (T)val[0];
+ data -= off;
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ for (int n = pass; n<N - 1; ++n) {
+ if (!pass) { x[0] = *(data + off); val[0] = (x[1] - x[2]); }
+ else { x[0] = *(data - off); val[0] = (x[2] - x[1]) * sum; }
+ for (int k = 1; k<4; ++k) val[0] += val[k]*filter[k];
+ *data = (T)val[0];
+ if (!pass) data += off; else data -= off;
+ for (int k = 2; k>0; --k) x[k] = x[k - 1];
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ *data = (T)0;
+ }
+ } break;
+ case 3: {
+ double x[3]; // [front,center,back]
+ for (int pass = 0; pass<2; ++pass) {
+ if (!pass) {
+ for (int k = 0; k<3; ++k) x[k] = (boundary_conditions?*data:(T)0);
+ for (int k = 0; k<4; ++k) val[k] = 0;
+ } else {
+ // apply Triggs boundary conditions
+ const double
+ unp = val[1], unp1 = val[2], unp2 = val[3];
+ val[0] = (M[0] * unp + M[1] * unp1 + M[2] * unp2) * sum;
+ val[1] = (M[3] * unp + M[4] * unp1 + M[5] * unp2) * sum;
+ val[2] = (M[6] * unp + M[7] * unp1 + M[8] * unp2) * sum;
+ *data = (T)val[0];
+ data -= off;
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ for (int n = pass; n<N - 1; ++n) {
+ if (!pass) { x[0] = *(data + off); val[0] = (x[0] - 2*x[1] + x[2]); }
+ else { x[0] = *(data - off); val[0] = 0.5f * (x[2] - x[0]) * sum; }
+ for (int k = 1; k<4; ++k) val[0] += val[k] * filter[k];
+ *data = (T)val[0];
+ if (!pass) data += off; else data -= off;
+ for (int k = 2; k>0; --k) x[k] = x[k - 1];
+ for (int k = 3; k>0; --k) val[k] = val[k - 1];
+ }
+ *data = (T)0;
+ }
+ } break;
+ }
+ }
+
+ //! Van Vliet recursive Gaussian filter.
+ /**
+ \param sigma standard deviation of the Gaussian filter
+ \param order the order of the filter 0,1,2,3
+ \param axis Axis along which the filter is computed. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param boundary_conditions Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann }</tt>.
+ \note dirichlet boundary condition has a strange behavior
+
+ I.T. Young, L.J. van Vliet, M. van Ginkel, Recursive Gabor filtering.
+ IEEE Trans. Sig. Proc., vol. 50, pp. 2799-2805, 2002.
+
+ (this is an improvement over Young-Van Vliet, Sig. Proc. 44, 1995)
+
+ Boundary conditions (only for order 0) using Triggs matrix, from
+ B. Triggs and M. Sdika. Boundary conditions for Young-van Vliet
+ recursive filtering. IEEE Trans. Signal Processing,
+ vol. 54, pp. 2365-2367, 2006.
+ **/
+ CImg<T>& vanvliet(const float sigma, const unsigned int order, const char axis='x',
+ const bool boundary_conditions=true) {
+ if (is_empty()) return *this;
+ const char naxis = cimg::lowercase(axis);
+ const float nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100;
+ if (is_empty() || (nsigma<0.5f && !order)) return *this;
+ const double
+ nnsigma = nsigma<0.5f?0.5f:nsigma,
+ m0 = 1.16680, m1 = 1.10783, m2 = 1.40586,
+ m1sq = m1 * m1, m2sq = m2 * m2,
+ q = (nnsigma<3.556?-0.2568 + 0.5784*nnsigma + 0.0561*nnsigma*nnsigma:2.5091 + 0.9804*(nnsigma - 3.556)),
+ qsq = q * q,
+ scale = (m0 + q) * (m1sq + m2sq + 2 * m1 * q + qsq),
+ b1 = -q * (2 * m0 * m1 + m1sq + m2sq + (2 * m0 + 4 * m1) * q + 3 * qsq) / scale,
+ b2 = qsq * (m0 + 2 * m1 + 3 * q) / scale,
+ b3 = -qsq * q / scale,
+ B = ( m0 * (m1sq + m2sq) ) / scale;
+ double filter[4];
+ filter[0] = B; filter[1] = -b1; filter[2] = -b2; filter[3] = -b3;
+ switch (naxis) {
+ case 'x' : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forYZC(*this,y,z,c)
+ _cimg_recursive_apply(data(0,y,z,c),filter,_width,1U,order,boundary_conditions);
+ } break;
+ case 'y' : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXZC(*this,x,z,c)
+ _cimg_recursive_apply(data(x,0,z,c),filter,_height,(ulongT)_width,order,boundary_conditions);
+ } break;
+ case 'z' : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXYC(*this,x,y,c)
+ _cimg_recursive_apply(data(x,y,0,c),filter,_depth,(ulongT)_width*_height,
+ order,boundary_conditions);
+ } break;
+ default : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXYZ(*this,x,y,z)
+ _cimg_recursive_apply(data(x,y,z,0),filter,_spectrum,(ulongT)_width*_height*_depth,
+ order,boundary_conditions);
+ }
+ }
+ return *this;
+ }
+
+ //! Blur image using Van Vliet recursive Gaussian filter. \newinstance.
+ CImg<Tfloat> get_vanvliet(const float sigma, const unsigned int order, const char axis='x',
+ const bool boundary_conditions=true) const {
+ return CImg<Tfloat>(*this,false).vanvliet(sigma,order,axis,boundary_conditions);
+ }
+
+ //! Blur image.
+ /**
+ \param sigma_x Standard deviation of the blur, along the X-axis.
+ \param sigma_y Standard deviation of the blur, along the Y-axis.
+ \param sigma_z Standard deviation of the blur, along the Z-axis.
+ \param boundary_conditions Boundary conditions. Can be <tt>{ false=dirichlet | true=neumann }</tt>.
+ \param is_gaussian Tells if the blur uses a gaussian (\c true) or quasi-gaussian (\c false) kernel.
+ \note
+ - The blur is computed as a 0-order Deriche filter. This is not a gaussian blur.
+ - This is a recursive algorithm, not depending on the values of the standard deviations.
+ \see deriche(), vanvliet().
+ **/
+ CImg<T>& blur(const float sigma_x, const float sigma_y, const float sigma_z,
+ const bool boundary_conditions=true, const bool is_gaussian=false) {
+ if (is_empty()) return *this;
+ if (is_gaussian) {
+ if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions);
+ if (_height>1) vanvliet(sigma_y,0,'y',boundary_conditions);
+ if (_depth>1) vanvliet(sigma_z,0,'z',boundary_conditions);
+ } else {
+ if (_width>1) deriche(sigma_x,0,'x',boundary_conditions);
+ if (_height>1) deriche(sigma_y,0,'y',boundary_conditions);
+ if (_depth>1) deriche(sigma_z,0,'z',boundary_conditions);
+ }
+ return *this;
+ }
+
+ //! Blur image \newinstance.
+ CImg<Tfloat> get_blur(const float sigma_x, const float sigma_y, const float sigma_z,
+ const bool boundary_conditions=true, const bool is_gaussian=false) const {
+ return CImg<Tfloat>(*this,false).blur(sigma_x,sigma_y,sigma_z,boundary_conditions,is_gaussian);
+ }
+
+ //! Blur image isotropically.
+ /**
+ \param sigma Standard deviation of the blur.
+ \param boundary_conditions Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann }</tt>.a
+ \see deriche(), vanvliet().
+ **/
+ CImg<T>& blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) {
+ const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100;
+ return blur(nsigma,nsigma,nsigma,boundary_conditions,is_gaussian);
+ }
+
+ //! Blur image isotropically \newinstance.
+ CImg<Tfloat> get_blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) const {
+ return CImg<Tfloat>(*this,false).blur(sigma,boundary_conditions,is_gaussian);
+ }
+
+ //! Blur image anisotropically, directed by 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 diffusion process.
+ \param interpolation_type Interpolation scheme.
+ Can be <tt>{ 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }</tt>.
+ \param is_fast_approx Tells if a fast approximation of the gaussian function is used 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 is_fast_approx=1) {
+
+ // Check arguments and init variables
+ if (!is_sameXYZ(G) || (G._spectrum!=3 && G._spectrum!=6))
+ throw CImgArgumentException(_cimg_instance
+ "blur_anisotropic(): Invalid specified diffusion tensor field (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ G._width,G._height,G._depth,G._spectrum,G._data);
+
+ if (is_empty() || amplitude<=0 || dl<0) return *this;
+ const bool is_3d = (G._spectrum==6);
+ T val_min, val_max = max_min(val_min);
+ cimg_abort_init;
+
+ if (da<=0) { // Iterated oriented Laplacians
+ CImg<Tfloat> velocity(_width,_height,_depth,_spectrum);
+ for (unsigned int iteration = 0; iteration<(unsigned int)amplitude; ++iteration) {
+ Tfloat *ptrd = velocity._data, veloc_max = 0;
+ if (is_3d) // 3d version
+ cimg_forC(*this,c) {
+ cimg_abort_test();
+ CImg_3x3x3(I,Tfloat);
+ 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_forZC(*this,z,c) {
+ cimg_abort_test();
+ CImg_3x3(I,Tfloat);
+ 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 ulongT whd = (ulongT)_width*_height*_depth;
+ const float sqrt2amplitude = (float)std::sqrt(2*amplitude);
+ const int dx1 = width() - 1, dy1 = height() - 1, dz1 = depth() - 1;
+ CImg<Tfloat> res(_width,_height,_depth,_spectrum,0), W(_width,_height,_depth,is_3d?4:3), val(_spectrum,1,1,1,0);
+ int N = 0;
+ if (is_3d) { // 3d version
+ for (float phi = (180%(int)da)/2.0f; phi<=180; phi+=da) {
+ const float phir = (float)(phi*cimg::PI/180), datmp = (float)(da/std::cos(phir)),
+ da2 = datmp<1?360.0f:datmp;
+ for (float theta = 0; theta<360; (theta+=da2),++N) {
+ const float
+ thetar = (float)(theta*cimg::PI/180),
+ vx = (float)(std::cos(thetar)*std::cos(phir)),
+ vy = (float)(std::sin(thetar)*std::cos(phir)),
+ vz = (float)std::sin(phir);
+ const t
+ *pa = G.data(0,0,0,0), *pb = G.data(0,0,0,1), *pc = G.data(0,0,0,2),
+ *pd = G.data(0,0,0,3), *pe = G.data(0,0,0,4), *pf = G.data(0,0,0,5);
+ Tfloat *pd0 = W.data(0,0,0,0), *pd1 = W.data(0,0,0,1), *pd2 = W.data(0,0,0,2), *pd3 = W.data(0,0,0,3);
+ cimg_forXYZ(G,xg,yg,zg) {
+ const t a = *(pa++), b = *(pb++), c = *(pc++), d = *(pd++), e = *(pe++), f = *(pf++);
+ const float
+ u = (float)(a*vx + b*vy + c*vz),
+ v = (float)(b*vx + d*vy + e*vz),
+ w = (float)(c*vx + e*vy + f*vz),
+ n = 1e-5f + cimg::hypot(u,v,w),
+ dln = dl/n;
+ *(pd0++) = (Tfloat)(u*dln);
+ *(pd1++) = (Tfloat)(v*dln);
+ *(pd2++) = (Tfloat)(w*dln);
+ *(pd3++) = (Tfloat)n;
+ }
+
+ cimg_abort_test();
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=2)
+ firstprivate(val))
+ cimg_forYZ(*this,y,z) cimg_abort_try2 {
+ cimg_abort_test2();
+ cimg_forX(*this,x) {
+ 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 (is_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 (is_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 (is_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 = res.data(x,y,z);
+ 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; }
+ }
+ } cimg_abort_catch2()
+ }
+ }
+ } 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 = std::max(1e-5f,cimg::hypot(u,v)),
+ dln = dl/n;
+ *(pd0++) = (Tfloat)(u*dln);
+ *(pd1++) = (Tfloat)(v*dln);
+ *(pd2++) = (Tfloat)n;
+ }
+
+ cimg_abort_test();
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256 && _height>=2) firstprivate(val))
+ cimg_forY(*this,y) cimg_abort_try2 {
+ cimg_abort_test2();
+ cimg_forX(*this,x) {
+ 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 (is_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 (is_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 (is_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 = res.data(x,y);
+ 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; }
+ }
+ } cimg_abort_catch2()
+ }
+ }
+ const Tfloat *ptrs = res._data;
+ cimg_for(*this,ptrd,T) {
+ const Tfloat val = *(ptrs++)/N;
+ *ptrd = val<val_min?val_min:(val>val_max?val_max:(T)val);
+ }
+ }
+ cimg_abort_test();
+ return *this;
+ }
+
+ //! Blur image anisotropically, directed by a field of diffusion tensors \newinstance.
+ template<typename t>
+ CImg<Tfloat> 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 is_fast_approx=true) const {
+ return CImg<Tfloat>(*this,false).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx);
+ }
+
+ //! Blur image anisotropically, in an edge-preserving way.
+ /**
+ \param amplitude Amplitude of the smoothing.
+ \param sharpness Sharpness.
+ \param anisotropy Anisotropy.
+ \param alpha Standard deviation of the gradient blur.
+ \param sigma Standard deviation of the structure tensor blur.
+ \param dl Spatial discretization.
+ \param da Angular discretization.
+ \param gauss_prec Precision of the diffusion process.
+ \param interpolation_type Interpolation scheme.
+ Can be <tt>{ 0=nearest-neighbor | 1=linear | 2=Runge-Kutta }</tt>.
+ \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not.
+ **/
+ 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 is_fast_approx=true) {
+ return blur_anisotropic(get_diffusion_tensors(sharpness,anisotropy,alpha,sigma,interpolation_type!=3),
+ amplitude,dl,da,gauss_prec,interpolation_type,is_fast_approx);
+ }
+
+ //! Blur image anisotropically, in an edge-preserving way \newinstance.
+ CImg<Tfloat> 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 is_fast_approx=true) const {
+ return CImg<Tfloat>(*this,false).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,
+ interpolation_type,is_fast_approx);
+ }
+
+ //! Blur image, with the joint bilateral filter.
+ /**
+ \param guide Image used to model the smoothing weights.
+ \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 value axis.
+ \param sampling_x Amount of downsampling along the X-axis used for the approximation.
+ Defaults (0) to sigma_x.
+ \param sampling_y Amount of downsampling along the Y-axis used for the approximation.
+ Defaults (0) to sigma_y.
+ \param sampling_z Amount of downsampling along the Z-axis used for the approximation.
+ Defaults (0) to sigma_z.
+ \param sampling_r Amount of downsampling along the value axis used for the approximation.
+ Defaults (0) to sigma_r.
+ \note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006
+ (extended for 3d volumetric images).
+ It is based on the reference implementation http://people.csail.mit.edu/jiawen/software/bilateralFilter.m
+ **/
+ template<typename t>
+ CImg<T>& blur_bilateral(const CImg<t>& guide,
+ const float sigma_x, const float sigma_y,
+ const float sigma_z, const float sigma_r,
+ const float sampling_x, const float sampling_y,
+ const float sampling_z, const float sampling_r) {
+ if (!is_sameXYZ(guide))
+ throw CImgArgumentException(_cimg_instance
+ "blur_bilateral(): Invalid size for specified guide image (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ guide._width,guide._height,guide._depth,guide._spectrum,guide._data);
+ if (is_empty() || (!sigma_x && !sigma_y && !sigma_z)) return *this;
+ T edge_min, edge_max = guide.max_min(edge_min);
+ if (edge_min==edge_max) return blur(sigma_x,sigma_y,sigma_z);
+ const float
+ edge_delta = (float)(edge_max - edge_min),
+ _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,
+ _sigma_r = sigma_r>=0?sigma_r:-sigma_r*(edge_max - edge_min)/100,
+ _sampling_x = sampling_x?sampling_x:std::max(_sigma_x,1.0f),
+ _sampling_y = sampling_y?sampling_y:std::max(_sigma_y,1.0f),
+ _sampling_z = sampling_z?sampling_z:std::max(_sigma_z,1.0f),
+ _sampling_r = sampling_r?sampling_r:std::max(_sigma_r,edge_delta/256),
+ derived_sigma_x = _sigma_x / _sampling_x,
+ derived_sigma_y = _sigma_y / _sampling_y,
+ derived_sigma_z = _sigma_z / _sampling_z,
+ derived_sigma_r = _sigma_r / _sampling_r;
+ const int
+ padding_x = (int)(2*derived_sigma_x) + 1,
+ padding_y = (int)(2*derived_sigma_y) + 1,
+ padding_z = (int)(2*derived_sigma_z) + 1,
+ padding_r = (int)(2*derived_sigma_r) + 1;
+ const unsigned int
+ bx = (unsigned int)((_width - 1)/_sampling_x + 1 + 2*padding_x),
+ by = (unsigned int)((_height - 1)/_sampling_y + 1 + 2*padding_y),
+ bz = (unsigned int)((_depth - 1)/_sampling_z + 1 + 2*padding_z),
+ br = (unsigned int)(edge_delta/_sampling_r + 1 + 2*padding_r);
+ if (bx>0 || by>0 || bz>0 || br>0) {
+ const bool is_3d = (_depth>1);
+ if (is_3d) { // 3d version of the algorithm
+ CImg<floatT> bgrid(bx,by,bz,br), bgridw(bx,by,bz,br);
+ cimg_forC(*this,c) {
+ const CImg<t> _guide = guide.get_shared_channel(c%guide._spectrum);
+ bgrid.fill(0); bgridw.fill(0);
+ cimg_forXYZ(*this,x,y,z) {
+ const T val = (*this)(x,y,z,c);
+ const float edge = (float)_guide(x,y,z);
+ const int
+ X = (int)cimg::round(x/_sampling_x) + padding_x,
+ Y = (int)cimg::round(y/_sampling_y) + padding_y,
+ Z = (int)cimg::round(z/_sampling_z) + padding_z,
+ R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r;
+ bgrid(X,Y,Z,R)+=(float)val;
+ bgridw(X,Y,Z,R)+=1;
+ }
+ bgrid.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false);
+ bgridw.blur(derived_sigma_x,derived_sigma_y,derived_sigma_z,true).deriche(derived_sigma_r,0,'c',false);
+
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(size()>=4096))
+ cimg_forXYZ(*this,x,y,z) {
+ const float edge = (float)_guide(x,y,z);
+ const float
+ X = x/_sampling_x + padding_x,
+ Y = y/_sampling_y + padding_y,
+ Z = z/_sampling_z + padding_z,
+ R = (edge - edge_min)/_sampling_r + padding_r;
+ const float 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 { // 2d version of the algorithm
+ CImg<floatT> bgrid(bx,by,br,2);
+ cimg_forC(*this,c) {
+ const CImg<t> _guide = guide.get_shared_channel(c%guide._spectrum);
+ bgrid.fill(0);
+ cimg_forXY(*this,x,y) {
+ const T val = (*this)(x,y,c);
+ const float edge = (float)_guide(x,y);
+ const int
+ X = (int)cimg::round(x/_sampling_x) + padding_x,
+ Y = (int)cimg::round(y/_sampling_y) + padding_y,
+ R = (int)cimg::round((edge - edge_min)/_sampling_r) + padding_r;
+ bgrid(X,Y,R,0)+=(float)val;
+ bgrid(X,Y,R,1)+=1;
+ }
+ bgrid.blur(derived_sigma_x,derived_sigma_y,0,true).blur(0,0,derived_sigma_r,false);
+
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(size()>=4096))
+ cimg_forXY(*this,x,y) {
+ const float edge = (float)_guide(x,y);
+ const float
+ X = x/_sampling_x + padding_x,
+ Y = y/_sampling_y + padding_y,
+ R = (edge - edge_min)/_sampling_r + padding_r;
+ const float bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1);
+ (*this)(x,y,c) = (T)(bval0/bval1);
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Blur image, with the joint bilateral filter \newinstance.
+ template<typename t>
+ CImg<Tfloat> get_blur_bilateral(const CImg<t>& guide,
+ const float sigma_x, const float sigma_y,
+ const float sigma_z, const float sigma_r,
+ const float sampling_x, const float sampling_y,
+ const float sampling_z, const float sampling_r) const {
+ return CImg<Tfloat>(*this,false).blur_bilateral(guide,sigma_x,sigma_y,sigma_z,sigma_r,
+ sampling_x,sampling_y,sampling_z,sampling_r);
+ }
+
+ //! Blur image using the joint bilateral filter.
+ /**
+ \param guide Image used to model the smoothing weights.
+ \param sigma_s Amount of blur along the XYZ-axes.
+ \param sigma_r Amount of blur along the value axis.
+ \param sampling_s Amount of downsampling along the XYZ-axes used for the approximation. Defaults to sigma_s.
+ \param sampling_r Amount of downsampling along the value axis used for the approximation. Defaults to sigma_r.
+ **/
+ template<typename t>
+ CImg<T>& blur_bilateral(const CImg<t>& guide,
+ const float sigma_s, const float sigma_r,
+ const float sampling_s=0, const float sampling_r=0) {
+ const float _sigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100;
+ return blur_bilateral(guide,_sigma_s,_sigma_s,_sigma_s,sigma_r,sampling_s,sampling_s,sampling_s,sampling_r);
+ }
+
+ //! Blur image using the bilateral filter \newinstance.
+ template<typename t>
+ CImg<Tfloat> get_blur_bilateral(const CImg<t>& guide,
+ const float sigma_s, const float sigma_r,
+ const float sampling_s=0, const float sampling_r=0) const {
+ return CImg<Tfloat>(*this,false).blur_bilateral(guide,sigma_s,sigma_r,sampling_s,sampling_r);
+ }
+
+ // [internal] Apply a box filter (used by CImg<T>::boxfilter() and CImg<T>::blur_box()).
+ /*
+ \param ptr the pointer of the data
+ \param N size of the data
+ \param boxsize Size of the box filter (can be subpixel).
+ \param off the offset between two data point
+ \param order the order of the filter 0 (smoothing), 1st derivtive and 2nd derivative.
+ \param boundary_conditions Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann }</tt>.
+ */
+ static void _cimg_blur_box_apply(T *ptr, const float boxsize, const int N, const ulongT off,
+ const int order, const bool boundary_conditions,
+ const unsigned int nb_iter) {
+ // Smooth.
+ if (boxsize>1 && nb_iter) {
+ const int w2 = (int)(boxsize - 1)/2;
+ const unsigned int winsize = 2*w2 + 1U;
+ const double frac = (boxsize - winsize)/2.;
+ CImg<T> win(winsize);
+ for (unsigned int iter = 0; iter<nb_iter; ++iter) {
+ Tdouble sum = 0; // window sum
+ for (int x = -w2; x<=w2; ++x) {
+ win[x + w2] = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,x);
+ sum+=win[x + w2];
+ }
+ int ifirst = 0, ilast = 2*w2;
+ T
+ prev = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,-w2 - 1),
+ next = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,w2 + 1);
+ for (int x = 0; x < N - 1; ++x) {
+ const double sum2 = sum + frac * (prev + next);
+ ptr[x*off] = (T)(sum2/boxsize);
+ prev = win[ifirst];
+ sum-=prev;
+ ifirst = (int)((ifirst + 1)%winsize);
+ ilast = (int)((ilast + 1)%winsize);
+ win[ilast] = next;
+ sum+=next;
+ next = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,x + w2 + 2);
+ }
+ const double sum2 = sum + frac * (prev + next);
+ ptr[(N - 1)*off] = (T)(sum2/boxsize);
+ }
+ }
+
+ // Derive.
+ switch (order) {
+ case 0 :
+ break;
+ case 1 : {
+ Tfloat
+ p = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,-1),
+ c = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,0),
+ n = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,1);
+ for (int x = 0; x<N - 1; ++x) {
+ ptr[x*off] = (T)((n-p)/2.0);
+ p = c;
+ c = n;
+ n = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,x + 2);
+ }
+ ptr[(N - 1)*off] = (T)((n-p)/2.0);
+ } break;
+ case 2: {
+ Tfloat
+ p = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,-1),
+ c = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,0),
+ n = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,1);
+ for (int x = 0; x<N - 1; ++x) {
+ ptr[x*off] = (T)(n - 2*c + p);
+ p = c;
+ c = n;
+ n = __cimg_blur_box_apply(ptr,N,off,boundary_conditions,x + 2);
+ }
+ ptr[(N - 1)*off] = (T)(n - 2*c + p);
+ } break;
+ }
+ }
+
+ static T __cimg_blur_box_apply(T *ptr, const int N, const ulongT off,
+ const bool boundary_conditions, const int x) {
+ if (x<0) return boundary_conditions?ptr[0]:T();
+ if (x>=N) return boundary_conditions?ptr[(N - 1)*off]:T();
+ return ptr[x*off];
+ }
+
+ // Apply box filter of order 0,1,2.
+ /**
+ \param boxsize Size of the box window (can be subpixel)
+ \param order the order of the filter 0,1 or 2.
+ \param axis Axis along which the filter is computed. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param boundary_conditions Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann }</tt>.
+ **/
+ CImg<T>& boxfilter(const float boxsize, const int order, const char axis='x',
+ const bool boundary_conditions=true,
+ const unsigned int nb_iter=1) {
+ if (is_empty() || !boxsize || (boxsize<=1 && !order)) return *this;
+ const char naxis = cimg::lowercase(axis);
+ const float nboxsize = boxsize>=0?boxsize:-boxsize*
+ (naxis=='x'?_width:naxis=='y'?_height:naxis=='z'?_depth:_spectrum)/100;
+ switch (naxis) {
+ case 'x' : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forYZC(*this,y,z,c)
+ _cimg_blur_box_apply(data(0,y,z,c),nboxsize,_width,1U,order,boundary_conditions,nb_iter);
+ } break;
+ case 'y' : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXZC(*this,x,z,c)
+ _cimg_blur_box_apply(data(x,0,z,c),nboxsize,_height,(ulongT)_width,order,boundary_conditions,nb_iter);
+ } break;
+ case 'z' : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXYC(*this,x,y,c)
+ _cimg_blur_box_apply(data(x,y,0,c),nboxsize,_depth,(ulongT)_width*_height,order,boundary_conditions,nb_iter);
+ } break;
+ default : {
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16))
+ cimg_forXYZ(*this,x,y,z)
+ _cimg_blur_box_apply(data(x,y,z,0),nboxsize,_spectrum,(ulongT)_width*_height*_depth,
+ order,boundary_conditions,nb_iter);
+ }
+ }
+ return *this;
+ }
+
+ // Apply box filter of order 0,1 or 2 \newinstance.
+ CImg<Tfloat> get_boxfilter(const float boxsize, const int order, const char axis='x',
+ const bool boundary_conditions=true,
+ const unsigned int nb_iter=1) const {
+ return CImg<Tfloat>(*this,false).boxfilter(boxsize,order,axis,boundary_conditions,nb_iter);
+ }
+
+ //! Blur image with a box filter.
+ /**
+ \param boxsize_x Size of the box window, along the X-axis (can be subpixel).
+ \param boxsize_y Size of the box window, along the Y-axis (can be subpixel).
+ \param boxsize_z Size of the box window, along the Z-axis (can be subpixel).
+ \param boundary_conditions Boundary conditions. Can be <tt>{ false=dirichlet | true=neumann }</tt>.
+ \note
+ - This is a recursive algorithm, not depending on the values of the box kernel size.
+ \see blur().
+ **/
+ CImg<T>& blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z,
+ const bool boundary_conditions=true,
+ const unsigned int nb_iter=1) {
+ if (is_empty()) return *this;
+ if (_width>1) boxfilter(boxsize_x,0,'x',boundary_conditions,nb_iter);
+ if (_height>1) boxfilter(boxsize_y,0,'y',boundary_conditions,nb_iter);
+ if (_depth>1) boxfilter(boxsize_z,0,'z',boundary_conditions,nb_iter);
+ return *this;
+ }
+
+ //! Blur image with a box filter \newinstance.
+ CImg<Tfloat> get_blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z,
+ const bool boundary_conditions=true) const {
+ return CImg<Tfloat>(*this,false).blur_box(boxsize_x,boxsize_y,boxsize_z,boundary_conditions);
+ }
+
+ //! Blur image with a box filter.
+ /**
+ \param boxsize Size of the box window (can be subpixel).
+ \param boundary_conditions Boundary conditions. Can be <tt>{ 0=dirichlet | 1=neumann }</tt>.a
+ \see deriche(), vanvliet().
+ **/
+ CImg<T>& blur_box(const float boxsize, const bool boundary_conditions=true) {
+ const float nboxsize = boxsize>=0?boxsize:-boxsize*cimg::max(_width,_height,_depth)/100;
+ return blur_box(nboxsize,nboxsize,nboxsize,boundary_conditions);
+ }
+
+ //! Blur image with a box filter \newinstance.
+ CImg<Tfloat> get_blur_box(const float boxsize, const bool boundary_conditions=true) const {
+ return CImg<Tfloat>(*this,false).blur_box(boxsize,boundary_conditions);
+ }
+
+ //! Blur image, with the image guided filter.
+ /**
+ \param guide Image used to guide the smoothing process.
+ \param radius Spatial radius. If negative, it is expressed as a percentage of the largest image size.
+ \param regularization Regularization parameter.
+ If negative, it is expressed as a percentage of the guide value range.
+ \note This method implements the filtering algorithm described in:
+ He, Kaiming; Sun, Jian; Tang, Xiaoou, "Guided Image Filtering," Pattern Analysis and Machine Intelligence,
+ IEEE Transactions on , vol.35, no.6, pp.1397,1409, June 2013
+ **/
+ template<typename t>
+ CImg<T>& blur_guided(const CImg<t>& guide, const float radius, const float regularization) {
+ return get_blur_guided(guide,radius,regularization).move_to(*this);
+ }
+
+ //! Blur image, with the image guided filter \newinstance.
+ template<typename t>
+ CImg<Tfloat> get_blur_guided(const CImg<t>& guide, const float radius, const float regularization) const {
+ if (!is_sameXYZ(guide))
+ throw CImgArgumentException(_cimg_instance
+ "blur_guided(): Invalid size for specified guide image (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ guide._width,guide._height,guide._depth,guide._spectrum,guide._data);
+ if (is_empty() || !radius) return *this;
+ const int _radius = radius>=0?(int)radius:(int)(-radius*cimg::max(_width,_height,_depth)/100);
+ float _regularization = regularization;
+ if (regularization<0) {
+ T edge_min, edge_max = guide.max_min(edge_min);
+ if (edge_min==edge_max) return *this;
+ _regularization = -regularization*(edge_max - edge_min)/100;
+ }
+ _regularization = std::max(_regularization,0.01f);
+ const unsigned int psize = (unsigned int)(1 + 2*_radius);
+ const CImg<uintT> N = CImg<uintT>(_width,_height,_depth,1,1)._blur_guided(psize);
+ CImg<Tfloat>
+ mean_I = CImg<Tfloat>(guide,false)._blur_guided(psize).div(N),
+ mean_p = CImg<Tfloat>(*this,false)._blur_guided(psize).div(N),
+ cov_Ip = CImg<Tfloat>(*this,false).mul(guide)._blur_guided(psize).div(N)-=mean_p.get_mul(mean_I),
+ var_I = CImg<Tfloat>(guide,false).sqr()._blur_guided(psize).div(N)-=mean_I.get_sqr(),
+ &a = cov_Ip.div(var_I+=_regularization),
+ &b = mean_p-=a.get_mul(mean_I);
+ a._blur_guided(psize).div(N);
+ b._blur_guided(psize).div(N);
+ return a.mul(guide)+=b;
+ }
+
+ // [internal] Perform box filter with dirichlet boundary conditions.
+ CImg<T>& _blur_guided(const unsigned int psize) {
+ const int p1 = (int)psize/2, p2 = (int)psize - p1;
+ if (_depth!=1) {
+ CImg<floatT> cumul = get_cumulate('z'), cumul2 = cumul.get_shift(0,0,p2,0,1);
+ (cumul.shift(0,0,-p1,0,1)-=cumul2).move_to(*this);
+ }
+ if (_height!=1) {
+ CImg<floatT> cumul = get_cumulate('y'), cumul2 = cumul.get_shift(0,p2,0,0,1);
+ (cumul.shift(0,-p1,0,0,1)-=cumul2).move_to(*this);
+ }
+ if (_width!=1) {
+ CImg<floatT> cumul = get_cumulate('x'), cumul2 = cumul.get_shift(p2,0,0,0,1);
+ (cumul.shift(-p1,0,0,0,1)-=cumul2).move_to(*this);
+ }
+ return *this;
+ }
+
+ //! Blur image using patch-based space.
+ /**
+ \param sigma_s Amount of blur along the XYZ-axes.
+ \param sigma_p Amount of blur along the value axis.
+ \param patch_size Size of the patchs.
+ \param lookup_size Size of the window to search similar patchs.
+ \param smoothness Smoothness for the patch comparison.
+ \param is_fast_approx Tells if a fast approximation of the gaussian function is used or not.
+ **/
+ 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 is_fast_approx=true) {
+ if (is_empty() || !patch_size || !lookup_size) return *this;
+ return get_blur_patch(sigma_s,sigma_p,patch_size,lookup_size,smoothness,is_fast_approx).move_to(*this);
+ }
+
+ //! Blur image using patch-based space \newinstance.
+ CImg<Tfloat> 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 is_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._data; 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._data; 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._data; 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._data; cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(pQ++); distance2+=dI*dI; } \
+ distance2/=Pnorm; \
+ const float dx = (float)p - x, dy = (float)q - y, \
+ alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)std::exp(-alldist); \
+ if (weight>weight_max) weight_max = weight; \
+ sum_weights+=weight; \
+ cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \
+ } \
+ sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=weight_max*(*this)(x,y,c); \
+ if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \
+ else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \
+ }
+
+ if (is_empty() || !patch_size || !lookup_size) return +*this;
+ CImg<Tfloat> res(_width,_height,_depth,_spectrum,0);
+ const CImg<T> _img = smoothness>0?get_blur(smoothness):CImg<Tfloat>(),&img = smoothness>0?_img:*this;
+ CImg<T> P(patch_size*patch_size*_spectrum), Q(P);
+ const float
+ nsigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100,
+ sigma_s2 = nsigma_s*nsigma_s, sigma_p2 = sigma_p*sigma_p, sigma_p3 = 3*sigma_p,
+ Pnorm = P.size()*sigma_p2;
+ const int rsize2 = (int)lookup_size/2, rsize1 = (int)lookup_size - rsize2 - 1;
+ const unsigned int N2 = patch_size*patch_size, N3 = N2*patch_size;
+ cimg::unused(N2,N3);
+ if (_depth>1) switch (patch_size) { // 3d
+ case 2 : if (is_fast_approx) _cimg_blur_patch3d_fast(2) else _cimg_blur_patch3d(2) break;
+ case 3 : if (is_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 (is_fast_approx)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res._width>=32 && res._height*res._depth>=4)
+ private(P,Q))
+ 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_pragma_openmp(parallel for collapse(2)
+ if (res._width>=32 && res._height*res._depth>=4) firstprivate(P,Q))
+ 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 (is_fast_approx) _cimg_blur_patch2d_fast(2) else _cimg_blur_patch2d(2) break;
+ case 3 : if (is_fast_approx) _cimg_blur_patch2d_fast(3) else _cimg_blur_patch2d(3) break;
+ case 4 : if (is_fast_approx) _cimg_blur_patch2d_fast(4) else _cimg_blur_patch2d(4) break;
+ case 5 : if (is_fast_approx) _cimg_blur_patch2d_fast(5) else _cimg_blur_patch2d(5) break;
+ case 6 : if (is_fast_approx) _cimg_blur_patch2d_fast(6) else _cimg_blur_patch2d(6) break;
+ case 7 : if (is_fast_approx) _cimg_blur_patch2d_fast(7) else _cimg_blur_patch2d(7) break;
+ case 8 : if (is_fast_approx) _cimg_blur_patch2d_fast(8) else _cimg_blur_patch2d(8) break;
+ case 9 : if (is_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 (is_fast_approx)
+ cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=32 && res._height>=4) firstprivate(P,Q))
+ 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_pragma_openmp(parallel for cimg_openmp_if(res._width>=32 && res._height>=4) firstprivate(P,Q))
+ 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;
+ }
+
+ //! Blur image with the median filter.
+ /**
+ \param n Size of the median filter.
+ \param threshold Threshold used to discard pixels too far from the current pixel value in the median computation.
+ **/
+ CImg<T>& blur_median(const unsigned int n, const float threshold=0) {
+ if (!n) return *this;
+ return get_blur_median(n,threshold).move_to(*this);
+ }
+
+ //! Blur image with the median filter \newinstance.
+ CImg<T> get_blur_median(const unsigned int n, const float threshold=0) const {
+ if (is_empty() || n<=1) return +*this;
+ CImg<T> res(_width,_height,_depth,_spectrum);
+ T *ptrd = res._data;
+ cimg::unused(ptrd);
+ const int hr = (int)n/2, hl = n - hr - 1;
+ if (res._depth!=1) { // 3d
+ if (threshold>0)
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4))
+ cimg_forXYZC(*this,x,y,z,c) { // With threshold.
+ 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;
+ const float val0 = (float)(*this)(x,y,z,c);
+ CImg<T> values(n*n*n);
+ unsigned int nb_values = 0;
+ T *ptrd = values.data();
+ cimg_for_inXYZ(*this,nx0,ny0,nz0,nx1,ny1,nz1,p,q,r)
+ if (cimg::abs((float)(*this)(p,q,r,c)-val0)<=threshold) { *(ptrd++) = (*this)(p,q,r,c); ++nb_values; }
+ res(x,y,z,c) = values.get_shared_points(0,nb_values - 1).median();
+ }
+ else
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4))
+ cimg_forXYZC(*this,x,y,z,c) { // Without threshold.
+ 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;
+ res(x,y,z,c) = 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) { // 2d
+ if (threshold>0)
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=16 && _height*_spectrum>=4))
+ cimg_forXYC(*this,x,y,c) { // With threshold.
+ 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;
+ const float val0 = (float)(*this)(x,y,c);
+ CImg<T> values(n*n);
+ unsigned int nb_values = 0;
+ T *ptrd = values.data();
+ cimg_for_inXY(*this,nx0,ny0,nx1,ny1,p,q)
+ if (cimg::abs((float)(*this)(p,q,c)-val0)<=threshold) { *(ptrd++) = (*this)(p,q,c); ++nb_values; }
+ res(x,y,c) = values.get_shared_points(0,nb_values - 1).median();
+ }
+ else switch (n) { // Without threshold.
+ case 3 : {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2))
+ cimg_forC(*this,c) {
+ T I[9] = { (T)0 };
+ cimg_for3x3(*this,x,y,0,c,I,T)
+ res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6],I[7],I[8]);
+ }
+ } break;
+ case 5 : {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2))
+ cimg_forC(*this,c) {
+ T I[25] = { (T)0 };
+ cimg_for5x5(*this,x,y,0,c,I,T)
+ res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],
+ I[5],I[6],I[7],I[8],I[9],
+ I[10],I[11],I[12],I[13],I[14],
+ I[15],I[16],I[17],I[18],I[19],
+ I[20],I[21],I[22],I[23],I[24]);
+ }
+ } break;
+ case 7 : {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2))
+ cimg_forC(*this,c) {
+ T I[49] = { (T)0 };
+ cimg_for7x7(*this,x,y,0,c,I,T)
+ res(x,y,c) = cimg::median(I[0],I[1],I[2],I[3],I[4],I[5],I[6],
+ I[7],I[8],I[9],I[10],I[11],I[12],I[13],
+ I[14],I[15],I[16],I[17],I[18],I[19],I[20],
+ I[21],I[22],I[23],I[24],I[25],I[26],I[27],
+ I[28],I[29],I[30],I[31],I[32],I[33],I[34],
+ I[35],I[36],I[37],I[38],I[39],I[40],I[41],
+ I[42],I[43],I[44],I[45],I[46],I[47],I[48]);
+ }
+ } break;
+ default : {
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=16 && _height*_spectrum>=4))
+ 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;
+ res(x,y,c) = get_crop(nx0,ny0,0,c,nx1,ny1,0,c).median();
+ }
+ }
+ }
+ } else { // 1d
+
+ CImg<T> I;
+ if (threshold>0)
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=16 && _spectrum>=2))
+ cimg_forXC(*this,x,c) { // With threshold.
+ const int
+ x0 = x - hl, x1 = x + hr,
+ nx0 = x0<0?0:x0, nx1 = x1>=width()?width() - 1:x1;
+ const float val0 = (float)(*this)(x,c);
+ CImg<T> values(n);
+ unsigned int nb_values = 0;
+ T *ptrd = values.data();
+ cimg_for_inX(*this,nx0,nx1,p)
+ if (cimg::abs((float)(*this)(p,c)-val0)<=threshold) { *(ptrd++) = (*this)(p,c); ++nb_values; }
+ res(x,c) = values.get_shared_points(0,nb_values - 1).median();
+ }
+ else switch (n) { // Without threshold.
+ case 2 : {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2))
+ cimg_forC(*this,c) {
+ I.assign(4);
+ cimg_for2x2(*this,x,y,0,c,I,T) res(x,c) = (T)(0.5f*(I[0] + I[1]));
+ }
+ } break;
+ case 3 : {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2))
+ cimg_forC(*this,c) {
+ I.assign(9);
+ cimg_for3x3(*this,x,y,0,c,I,T)
+ res(x,c) = 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_pragma_openmp(parallel for cimg_openmp_if(_width>=16 && _spectrum>=2))
+ cimg_forXC(*this,x,c) {
+ const int
+ x0 = x - hl, x1 = x + hr,
+ nx0 = x0<0?0:x0, nx1 = x1>=width()?width() - 1:x1;
+ res(x,c) = get_crop(nx0,0,0,c,nx1,0,0,c).median();
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Sharpen image.
+ /**
+ \param amplitude Sharpening amplitude
+ \param sharpen_type Select sharpening method. Can be <tt>{ false=inverse diffusion | true=shock filters }</tt>.
+ \param edge Edge threshold (shock filters only).
+ \param alpha Gradient smoothness (shock filters only).
+ \param sigma Tensor smoothness (shock filters only).
+ **/
+ CImg<T>& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1,
+ const float alpha=0, const float sigma=0) {
+ if (is_empty()) return *this;
+ T val_min, val_max = max_min(val_min);
+ const float nedge = edge/2;
+ CImg<Tfloat> velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum);
+
+ if (_depth>1) { // 3d
+ if (sharpen_type) { // Shock filters.
+ CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
+ if (sigma>0) G.blur(sigma);
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=32 && _height*_depth>=16))
+ cimg_forYZ(G,y,z) {
+ Tfloat *ptrG0 = G.data(0,y,z,0), *ptrG1 = G.data(0,y,z,1),
+ *ptrG2 = G.data(0,y,z,2), *ptrG3 = G.data(0,y,z,3);
+ CImg<Tfloat> val, vec;
+ cimg_forX(G,x) {
+ G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
+ if (val[0]<0) val[0] = 0;
+ if (val[1]<0) val[1] = 0;
+ if (val[2]<0) val[2] = 0;
+ *(ptrG0++) = vec(0,0);
+ *(ptrG1++) = vec(0,1);
+ *(ptrG2++) = vec(0,2);
+ *(ptrG3++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1] + val[2],-(Tfloat)nedge);
+ }
+ }
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=512 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
+ CImg_3x3x3(I,Tfloat);
+ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+ const Tfloat
+ u = G(x,y,z,0),
+ v = G(x,y,z,1),
+ w = G(x,y,z,2),
+ amp = G(x,y,z,3),
+ ixx = Incc + Ipcc - 2*Iccc,
+ ixy = (Innc + Ippc - Inpc - Ipnc)/4,
+ ixz = (Incn + Ipcp - Incp - Ipcn)/4,
+ iyy = Icnc + Icpc - 2*Iccc,
+ iyz = (Icnn + Icpp - Icnp - Icpn)/4,
+ izz = Iccn + Iccp - 2*Iccc,
+ ixf = Incc - Iccc,
+ ixb = Iccc - Ipcc,
+ iyf = Icnc - Iccc,
+ iyb = Iccc - Icpc,
+ izf = Iccn - Iccc,
+ izb = Iccc - Iccp,
+ itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz,
+ it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb),
+ veloc = -amp*cimg::sign(itt)*cimg::abs(it);
+ *(ptrd++) = veloc;
+ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+ }
+ _veloc_max[c] = veloc_max;
+ }
+ } else // Inverse diffusion.
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
+ CImg_3x3x3(I,Tfloat);
+ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
+ const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc;
+ *(ptrd++) = veloc;
+ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+ }
+ _veloc_max[c] = veloc_max;
+ }
+ } else { // 2d.
+ if (sharpen_type) { // Shock filters.
+ CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
+ if (sigma>0) G.blur(sigma);
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=32 && _height>=16))
+ cimg_forY(G,y) {
+ CImg<Tfloat> val, vec;
+ Tfloat *ptrG0 = G.data(0,y,0,0), *ptrG1 = G.data(0,y,0,1), *ptrG2 = G.data(0,y,0,2);
+ cimg_forX(G,x) {
+ G.get_tensor_at(x,y).symmetric_eigen(val,vec);
+ if (val[0]<0) val[0] = 0;
+ if (val[1]<0) val[1] = 0;
+ *(ptrG0++) = vec(0,0);
+ *(ptrG1++) = vec(0,1);
+ *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge);
+ }
+ }
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=512 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
+ CImg_3x3(I,Tfloat);
+ cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+ const Tfloat
+ u = G(x,y,0),
+ v = G(x,y,1),
+ amp = G(x,y,2),
+ ixx = Inc + Ipc - 2*Icc,
+ ixy = (Inn + Ipp - Inp - Ipn)/4,
+ iyy = Icn + Icp - 2*Icc,
+ ixf = Inc - Icc,
+ ixb = Icc - Ipc,
+ iyf = Icn - Icc,
+ iyb = Icc - Icp,
+ itt = u*u*ixx + v*v*iyy + 2*u*v*ixy,
+ it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb),
+ veloc = -amp*cimg::sign(itt)*cimg::abs(it);
+ *(ptrd++) = veloc;
+ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+ }
+ _veloc_max[c] = veloc_max;
+ }
+ } else // Inverse diffusion.
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
+ CImg_3x3(I,Tfloat);
+ cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
+ const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc;
+ *(ptrd++) = veloc;
+ if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
+ }
+ _veloc_max[c] = veloc_max;
+ }
+ }
+ const Tfloat veloc_max = _veloc_max.max();
+ if (veloc_max<=0) return *this;
+ return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this);
+ }
+
+ //! Sharpen image \newinstance.
+ 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);
+ }
+
+ //! Return image gradient.
+ /**
+ \param axes Axes considered for the gradient computation, as a C-string (e.g "xy").
+ \param scheme = Numerical scheme used for the gradient computation:
+ - -1 = Backward finite differences
+ - 0 = Centered finite differences
+ - 1 = Forward finite differences
+ - 2 = Using Sobel kernels
+ - 3 = Using rotation invariant kernels
+ - 4 = Using Deriche recusrsive filter.
+ - 5 = Using Van Vliet recusrsive filter.
+ **/
+ CImgList<Tfloat> get_gradient(const char *const axes=0, const int scheme=3) const {
+ CImgList<Tfloat> grad(2,_width,_height,_depth,_spectrum);
+ bool is_3d = false;
+ if (axes) {
+ for (unsigned int a = 0; axes[a]; ++a) {
+ const char axis = cimg::lowercase(axes[a]);
+ switch (axis) {
+ case 'x' : case 'y' : break;
+ case 'z' : is_3d = true; break;
+ default :
+ throw CImgArgumentException(_cimg_instance
+ "get_gradient(): Invalid specified axis '%c'.",
+ cimg_instance,
+ axis);
+ }
+ }
+ } else is_3d = (_depth>1);
+ if (is_3d) {
+ CImg<Tfloat>(_width,_height,_depth,_spectrum).move_to(grad);
+ switch (scheme) { // 3d.
+ case -1 : { // Backward finite differences.
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off, *ptrd2 = grad[2]._data + off;
+ CImg_3x3x3(I,Tfloat);
+ 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_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off, *ptrd2 = grad[2]._data + off;
+ CImg_2x2x2(I,Tfloat);
+ cimg_for2x2x2(*this,x,y,z,c,I,Tfloat) {
+ *(ptrd0++) = Incc - Iccc;
+ *(ptrd1++) = Icnc - Iccc;
+ *(ptrd2++) = Iccn - Iccc;
+ }
+ }
+ } break;
+ case 4 : { // 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;
+ case 5 : { // Van Vliet filter with low standard variation.
+ grad[0] = get_vanvliet(0,1,'x');
+ grad[1] = get_vanvliet(0,1,'y');
+ grad[2] = get_vanvliet(0,1,'z');
+ } break;
+ default : { // Central finite differences.
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off, *ptrd2 = grad[2]._data + off;
+ CImg_3x3x3(I,Tfloat);
+ 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_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off;
+ CImg_3x3(I,Tfloat);
+ cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+ *(ptrd0++) = Icc - Ipc;
+ *(ptrd1++) = Icc - Icp;
+ }
+ }
+ } break;
+ case 1 : { // Forward finite differences.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off;
+ CImg_2x2(I,Tfloat);
+ cimg_for2x2(*this,x,y,z,c,I,Tfloat) {
+ *(ptrd0++) = Inc - Icc;
+ *(ptrd1++) = Icn - Icc;
+ }
+ }
+ } break;
+ case 2 : { // Sobel scheme.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off;
+ CImg_3x3(I,Tfloat);
+ cimg_for3x3(*this,x,y,z,c,I,Tfloat) {
+ *(ptrd0++) = -Ipp - 2*Ipc - Ipn + Inp + 2*Inc + Inn;
+ *(ptrd1++) = -Ipp - 2*Icp - Inp + Ipn + 2*Icn + Inn;
+ }
+ }
+ } break;
+ case 3 : { // Rotation invariant kernel.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off;
+ 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_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 : { // Van Vliet filter with low standard variation
+ grad[0] = get_deriche(0,1,'x');
+ grad[1] = get_deriche(0,1,'y');
+ } break;
+ case 5 : { // Deriche filter with low standard variation
+ grad[0] = get_vanvliet(0,1,'x');
+ grad[1] = get_vanvliet(0,1,'y');
+ } break;
+ default : { // Central finite differences
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height;
+ Tfloat *ptrd0 = grad[0]._data + off, *ptrd1 = grad[1]._data + off;
+ CImg_3x3(I,Tfloat);
+ 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::lowercase(axes[l]);
+ switch (axis) {
+ case 'x' : res.insert(grad[0]); break;
+ case 'y' : res.insert(grad[1]); break;
+ case 'z' : res.insert(grad[2]); break;
+ }
+ }
+ grad.assign();
+ return res;
+ }
+
+ //! Return image hessian.
+ /**
+ \param axes Axes considered for the hessian computation, as a C-string (e.g "xy").
+ **/
+ 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 = (unsigned int)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
+
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth;
+ Tfloat
+ *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off,
+ *ptrd3 = res[3]._data + off, *ptrd4 = res[4]._data + off, *ptrd5 = res[5]._data + off;
+ CImg_3x3x3(I,Tfloat);
+ 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
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ const ulongT off = (ulongT)c*_width*_height*_depth + z*_width*_height;
+ Tfloat *ptrd0 = res[0]._data + off, *ptrd1 = res[1]._data + off, *ptrd2 = res[2]._data + off;
+ CImg_3x3(I,Tfloat);
+ cimg_for3x3(*this,x,y,z,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;
+ if (axis1=='x' && axis2=='x') { // Ixx
+ valid_axis = true;
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ Tfloat *ptrd = res[l2].data(0,0,z,c);
+ CImg_3x3(I,Tfloat);
+ 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_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ Tfloat *ptrd = res[l2].data(0,0,z,c);
+ CImg_3x3(I,Tfloat);
+ 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_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = res[l2].data(0,0,0,c);
+ CImg_3x3x3(I,Tfloat);
+ 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_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forZC(*this,z,c) {
+ Tfloat *ptrd = res[l2].data(0,0,z,c);
+ CImg_3x3(I,Tfloat);
+ 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_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = res[l2].data(0,0,0,c);
+ 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_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = res[l2].data(0,0,0,c);
+ CImg_3x3x3(I,Tfloat);
+ 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 image laplacian.
+ CImg<T>& laplacian() {
+ return get_laplacian().move_to(*this);
+ }
+
+ //! Compute image laplacian \newinstance.
+ CImg<Tfloat> get_laplacian() const {
+ if (is_empty()) return CImg<Tfloat>();
+ CImg<Tfloat> res(_width,_height,_depth,_spectrum);
+ if (_depth>1) { // 3d
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = res.data(0,0,0,c);
+ CImg_3x3x3(I,Tfloat);
+ cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) *(ptrd++) = Incc + Ipcc + Icnc + Icpc + Iccn + Iccp - 6*Iccc;
+ }
+ } else if (_height>1) { // 2d
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = res.data(0,0,0,c);
+ CImg_3x3(I,Tfloat);
+ cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc + Icn + Icp - 4*Icc;
+ }
+ } else { // 1d
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=1048576 && _height*_depth*_spectrum>=2))
+ cimg_forC(*this,c) {
+ Tfloat *ptrd = res.data(0,0,0,c);
+ CImg_3x3(I,Tfloat);
+ cimg_for3x3(*this,x,y,0,c,I,Tfloat) *(ptrd++) = Inc + Ipc - 2*Icc;
+ }
+ }
+ return res;
+ }
+
+ //! Compute the structure tensor field of an image.
+ /**
+ \param is_fwbw_scheme scheme. Can be <tt>{ false=centered | true=forward-backward }</tt>
+ **/
+ CImg<T>& structure_tensors(const bool is_fwbw_scheme=false) {
+ return get_structure_tensors(is_fwbw_scheme).move_to(*this);
+ }
+
+ //! Compute the structure tensor field of an image \newinstance.
+ CImg<Tfloat> get_structure_tensors(const bool is_fwbw_scheme=false) const {
+ if (is_empty()) return *this;
+ CImg<Tfloat> res;
+ if (_depth>1) { // 3d
+ res.assign(_width,_height,_depth,6,0);
+ if (!is_fwbw_scheme) { // Classical central finite differences
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=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_3x3x3(I,Tfloat);
+ 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;
+ }
+ }
+ } else { // Forward/backward finite differences.
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=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_3x3x3(I,Tfloat);
+ 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;
+ }
+ }
+ }
+ } else { // 2d
+ res.assign(_width,_height,_depth,3,0);
+ if (!is_fwbw_scheme) { // Classical central finite differences
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=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_3x3(I,Tfloat);
+ 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;
+ }
+ }
+ } else { // Forward/backward finite differences (version 2).
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=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_3x3(I,Tfloat);
+ 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;
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Compute field of diffusion tensors for edge-preserving smoothing.
+ /**
+ \param sharpness Sharpness
+ \param anisotropy Anisotropy
+ \param alpha Standard deviation of the gradient blur.
+ \param sigma Standard deviation of the structure tensor blur.
+ \param is_sqrt Tells if the square root of the tensor field is computed instead.
+ **/
+ CImg<T>& diffusion_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 = std::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
+ get_structure_tensors().move_to(res).blur(sigma);
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=256))
+ cimg_forYZ(*this,y,z) {
+ Tfloat
+ *ptrd0 = res.data(0,y,z,0), *ptrd1 = res.data(0,y,z,1), *ptrd2 = res.data(0,y,z,2),
+ *ptrd3 = res.data(0,y,z,3), *ptrd4 = res.data(0,y,z,4), *ptrd5 = res.data(0,y,z,5);
+ CImg<floatT> val(3), vec(3,3);
+ cimg_forX(*this,x) {
+ 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
+ get_structure_tensors().move_to(res).blur(sigma);
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256 && _height>=256))
+ cimg_forY(*this,y) {
+ Tfloat *ptrd0 = res.data(0,y,0,0), *ptrd1 = res.data(0,y,0,1), *ptrd2 = res.data(0,y,0,2);
+ CImg<floatT> val(2), vec(2,2);
+ cimg_forX(*this,x) {
+ 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);
+ }
+
+ //! Compute field of diffusion tensors for edge-preserving smoothing \newinstance.
+ CImg<Tfloat> get_diffusion_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).diffusion_tensors(sharpness,anisotropy,alpha,sigma,is_sqrt);
+ }
+
+ //! Estimate displacement field between two images.
+ /**
+ \param source Reference image.
+ \param smoothness Smoothness of estimated displacement field.
+ \param precision Precision required for algorithm convergence.
+ \param nb_scales Number of scales used to estimate the displacement field.
+ \param iteration_max Maximum number of iterations allowed for one scale.
+ \param is_backward If false, match I2(X + U(X)) = I1(X), else match I2(X) = I1(X - U(X)).
+ \param guide Image used as the initial correspondence estimate for the algorithm.
+ 'guide' may have a last channel with boolean values (0=false | other=true) that
+ tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask).
+ **/
+ CImg<T>& displacement(const CImg<T>& source, const float smoothness=0.1f, const float precision=5.0f,
+ const unsigned int nb_scales=0, const unsigned int iteration_max=10000,
+ const bool is_backward=false,
+ const CImg<floatT>& guide=CImg<floatT>::const_empty()) {
+ return get_displacement(source,smoothness,precision,nb_scales,iteration_max,is_backward,guide).
+ move_to(*this);
+ }
+
+ //! Estimate displacement field between two images \newinstance.
+ CImg<floatT> get_displacement(const CImg<T>& source,
+ const float smoothness=0.1f, const float precision=5.0f,
+ const unsigned int nb_scales=0, const unsigned int iteration_max=10000,
+ const bool is_backward=false,
+ const CImg<floatT>& guide=CImg<floatT>::const_empty()) const {
+ if (is_empty() || !source) return +*this;
+ if (!is_sameXYZC(source))
+ throw CImgArgumentException(_cimg_instance
+ "displacement(): Instance and source image (%u,%u,%u,%u,%p) have "
+ "different dimensions.",
+ cimg_instance,
+ source._width,source._height,source._depth,source._spectrum,source._data);
+ if (precision<0)
+ throw CImgArgumentException(_cimg_instance
+ "displacement(): Invalid specified precision %g "
+ "(should be >=0)",
+ cimg_instance,
+ precision);
+
+ const bool is_3d = source._depth>1;
+ const unsigned int constraint = is_3d?3:2;
+
+ if (guide &&
+ (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<constraint))
+ throw CImgArgumentException(_cimg_instance
+ "displacement(): Specified guide (%u,%u,%u,%u,%p) "
+ "has invalid dimensions.",
+ cimg_instance,
+ guide._width,guide._height,guide._depth,guide._spectrum,guide._data);
+
+ const unsigned int
+ mins = is_3d?cimg::min(_width,_height,_depth):std::min(_width,_height),
+ _nb_scales = nb_scales>0?nb_scales:
+ (unsigned int)cimg::round(std::log(mins/8.0)/std::log(1.5),1,1);
+
+ const float _precision = (float)std::pow(10.0,-(double)precision);
+ float sm, sM = source.max_min(sm), tm, tM = max_min(tm);
+ const float sdelta = sm==sM?1:(sM - sm), tdelta = tm==tM?1:(tM - tm);
+
+ CImg<floatT> U, V;
+ floatT bound = 0;
+ for (int scale = (int)_nb_scales - 1; scale>=0; --scale) {
+ const float factor = (float)std::pow(1.5,(double)scale);
+ const unsigned int
+ _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1,
+ _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1,
+ _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1;
+ if (sw<5 && sh<5 && (!is_3d || sd<5)) continue; // skip too small scales.
+ const CImg<Tfloat>
+ I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta,
+ I2 = (get_resize(I1,2)-=tm)/=tdelta;
+ if (guide._spectrum>constraint) guide.get_resize(I2._width,I2._height,I2._depth,-100,1).move_to(V);
+ if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3);
+ else {
+ if (guide)
+ guide.get_shared_channels(0,is_3d?2:1).get_resize(I2._width,I2._height,I2._depth,-100,2).move_to(U);
+ else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0);
+ }
+
+ float dt = 2, energy = cimg::type<float>::max();
+ const CImgList<Tfloat> dI = is_backward?I1.get_gradient():I2.get_gradient();
+
+ for (unsigned int iteration = 0; iteration<iteration_max; ++iteration) {
+ cimg_abort_test();
+ float _energy = 0;
+
+ if (is_3d) { // 3d version.
+ if (smoothness>=0) // Isotropic regularization.
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_height*_depth>=8 && _width>=16)
+ reduction(+:_energy))
+ cimg_forYZ(U,y,z) {
+ const int
+ _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y,
+ _p1z = z?z - 1:0, _n1z = z<U.depth() - 1?z + 1:z;
+ cimg_for3X(U,x) {
+ const float
+ X = is_backward?x - U(x,y,z,0):x + U(x,y,z,0),
+ Y = is_backward?y - U(x,y,z,1):y + U(x,y,z,1),
+ Z = is_backward?z - U(x,y,z,2):z + U(x,y,z,2);
+ float delta_I = 0, _energy_regul = 0;
+ if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXYZ(X,Y,Z,c) - I2(x,y,z,c));
+ else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,z,c) - I2._linear_atXYZ(X,Y,Z,c));
+ cimg_forC(U,c) {
+ const float
+ Ux = 0.5f*(U(_n1x,y,z,c) - U(_p1x,y,z,c)),
+ Uy = 0.5f*(U(x,_n1y,z,c) - U(x,_p1y,z,c)),
+ Uz = 0.5f*(U(x,y,_n1z,c) - U(x,y,_p1z,c)),
+ Uxx = U(_n1x,y,z,c) + U(_p1x,y,z,c),
+ Uyy = U(x,_n1y,z,c) + U(x,_p1y,z,c),
+ Uzz = U(x,y,_n1z,c) + U(x,y,_p1z,c);
+ U(x,y,z,c) = (float)(U(x,y,z,c) + dt*(delta_I*dI[c]._linear_atXYZ(X,Y,Z) +
+ smoothness* ( Uxx + Uyy + Uzz)))/(1 + 6*smoothness*dt);
+ _energy_regul+=Ux*Ux + Uy*Uy + Uz*Uz;
+ }
+ if (is_backward) { // Constraint displacement vectors to stay in image.
+ if (U(x,y,z,0)>x) U(x,y,z,0) = (float)x;
+ if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y;
+ if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z;
+ bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound;
+ bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound;
+ bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound;
+ } else {
+ if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x;
+ if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y;
+ if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z;
+ bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound;
+ bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound;
+ bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound;
+ }
+ _energy+=delta_I*delta_I + smoothness*_energy_regul;
+ }
+ if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints.
+ U(x,y,z,0) = V(x,y,z,0)/factor;
+ U(x,y,z,1) = V(x,y,z,1)/factor;
+ U(x,y,z,2) = V(x,y,z,2)/factor;
+ }
+ } else { // Anisotropic regularization.
+ const float nsmoothness = -smoothness;
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_height*_depth>=8 && _width>=16)
+ reduction(+:_energy))
+ cimg_forYZ(U,y,z) {
+ const int
+ _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y,
+ _p1z = z?z - 1:0, _n1z = z<U.depth() - 1?z + 1:z;
+ cimg_for3X(U,x) {
+ const float
+ X = is_backward?x - U(x,y,z,0):x + U(x,y,z,0),
+ Y = is_backward?y - U(x,y,z,1):y + U(x,y,z,1),
+ Z = is_backward?z - U(x,y,z,2):z + U(x,y,z,2);
+ float delta_I = 0, _energy_regul = 0;
+ if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXYZ(X,Y,Z,c) - I2(x,y,z,c));
+ else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,z,c) - I2._linear_atXYZ(X,Y,Z,c));
+ cimg_forC(U,c) {
+ const float
+ Ux = 0.5f*(U(_n1x,y,z,c) - U(_p1x,y,z,c)),
+ Uy = 0.5f*(U(x,_n1y,z,c) - U(x,_p1y,z,c)),
+ Uz = 0.5f*(U(x,y,_n1z,c) - U(x,y,_p1z,c)),
+ N2 = Ux*Ux + Uy*Uy + Uz*Uz,
+ N = std::sqrt(N2),
+ N3 = 1e-5f + N2*N,
+ coef_a = (1 - Ux*Ux/N2)/N,
+ coef_b = -2*Ux*Uy/N3,
+ coef_c = -2*Ux*Uz/N3,
+ coef_d = (1 - Uy*Uy/N2)/N,
+ coef_e = -2*Uy*Uz/N3,
+ coef_f = (1 - Uz*Uz/N2)/N,
+ Uxx = U(_n1x,y,z,c) + U(_p1x,y,z,c),
+ Uyy = U(x,_n1y,z,c) + U(x,_p1y,z,c),
+ Uzz = U(x,y,_n1z,c) + U(x,y,_p1z,c),
+ Uxy = 0.25f*(U(_n1x,_n1y,z,c) + U(_p1x,_p1y,z,c) - U(_n1x,_p1y,z,c) - U(_n1x,_p1y,z,c)),
+ Uxz = 0.25f*(U(_n1x,y,_n1z,c) + U(_p1x,y,_p1z,c) - U(_n1x,y,_p1z,c) - U(_n1x,y,_p1z,c)),
+ Uyz = 0.25f*(U(x,_n1y,_n1z,c) + U(x,_p1y,_p1z,c) - U(x,_n1y,_p1z,c) - U(x,_n1y,_p1z,c));
+ U(x,y,z,c) = (float)(U(x,y,z,c) + dt*(delta_I*dI[c]._linear_atXYZ(X,Y,Z) +
+ nsmoothness* ( coef_a*Uxx + coef_b*Uxy +
+ coef_c*Uxz + coef_d*Uyy +
+ coef_e*Uyz + coef_f*Uzz ))
+ )/(1 + 2*(coef_a + coef_d + coef_f)*nsmoothness*dt);
+ _energy_regul+=N;
+ }
+ if (is_backward) { // Constraint displacement vectors to stay in image.
+ if (U(x,y,z,0)>x) U(x,y,z,0) = (float)x;
+ if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y;
+ if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z;
+ bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound;
+ bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound;
+ bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound;
+ } else {
+ if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x;
+ if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y;
+ if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z;
+ bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound;
+ bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound;
+ bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound;
+ }
+ _energy+=delta_I*delta_I + nsmoothness*_energy_regul;
+ }
+ if (V) cimg_forXYZ(V,x,y,z) if (V(x,y,z,3)) { // Apply constraints.
+ U(x,y,z,0) = V(x,y,z,0)/factor;
+ U(x,y,z,1) = V(x,y,z,1)/factor;
+ U(x,y,z,2) = V(x,y,z,2)/factor;
+ }
+ }
+ }
+ } else { // 2d version.
+ if (smoothness>=0) // Isotropic regularization.
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=8 && _width>=16) reduction(+:_energy))
+ cimg_forY(U,y) {
+ const int _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y;
+ cimg_for3X(U,x) {
+ const float
+ X = is_backward?x - U(x,y,0):x + U(x,y,0),
+ Y = is_backward?y - U(x,y,1):y + U(x,y,1);
+ float delta_I = 0, _energy_regul = 0;
+ if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXY(X,Y,c) - I2(x,y,c));
+ else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,c) - I2._linear_atXY(X,Y,c));
+ cimg_forC(U,c) {
+ const float
+ Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)),
+ Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)),
+ Uxx = U(_n1x,y,c) + U(_p1x,y,c),
+ Uyy = U(x,_n1y,c) + U(x,_p1y,c);
+ U(x,y,c) = (float)(U(x,y,c) + dt*(delta_I*dI[c]._linear_atXY(X,Y) +
+ smoothness*( Uxx + Uyy )))/(1 + 4*smoothness*dt);
+ _energy_regul+=Ux*Ux + Uy*Uy;
+ }
+ if (is_backward) { // Constraint displacement vectors to stay in image.
+ if (U(x,y,0)>x) U(x,y,0) = (float)x;
+ if (U(x,y,1)>y) U(x,y,1) = (float)y;
+ bound = (float)x - _width; if (U(x,y,0)<=bound) U(x,y,0) = bound;
+ bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound;
+ } else {
+ if (U(x,y,0)<-x) U(x,y,0) = -(float)x;
+ if (U(x,y,1)<-y) U(x,y,1) = -(float)y;
+ bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound;
+ bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound;
+ }
+ _energy+=delta_I*delta_I + smoothness*_energy_regul;
+ }
+ if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints.
+ U(x,y,0) = V(x,y,0)/factor;
+ U(x,y,1) = V(x,y,1)/factor;
+ }
+ } else { // Anisotropic regularization.
+ const float nsmoothness = -smoothness;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=8 && _width>=16) reduction(+:_energy))
+ cimg_forY(U,y) {
+ const int _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y;
+ cimg_for3X(U,x) {
+ const float
+ X = is_backward?x - U(x,y,0):x + U(x,y,0),
+ Y = is_backward?y - U(x,y,1):y + U(x,y,1);
+ float delta_I = 0, _energy_regul = 0;
+ if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXY(X,Y,c) - I2(x,y,c));
+ else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,c) - I2._linear_atXY(X,Y,c));
+ cimg_forC(U,c) {
+ const float
+ Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)),
+ Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)),
+ N2 = Ux*Ux + Uy*Uy,
+ N = std::sqrt(N2),
+ N3 = 1e-5f + N2*N,
+ coef_a = Uy*Uy/N3,
+ coef_b = -2*Ux*Uy/N3,
+ coef_c = Ux*Ux/N3,
+ Uxx = U(_n1x,y,c) + U(_p1x,y,c),
+ Uyy = U(x,_n1y,c) + U(x,_p1y,c),
+ Uxy = 0.25f*(U(_n1x,_n1y,c) + U(_p1x,_p1y,c) - U(_n1x,_p1y,c) - U(_n1x,_p1y,c));
+ U(x,y,c) = (float)(U(x,y,c) + dt*(delta_I*dI[c]._linear_atXY(X,Y) +
+ nsmoothness*( coef_a*Uxx + coef_b*Uxy + coef_c*Uyy )))/
+ (1 + 2*(coef_a + coef_c)*nsmoothness*dt);
+ _energy_regul+=N;
+ }
+ if (is_backward) { // Constraint displacement vectors to stay in image.
+ if (U(x,y,0)>x) U(x,y,0) = (float)x;
+ if (U(x,y,1)>y) U(x,y,1) = (float)y;
+ bound = (float)x - _width; if (U(x,y,0)<=bound) U(x,y,0) = bound;
+ bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound;
+ } else {
+ if (U(x,y,0)<-x) U(x,y,0) = -(float)x;
+ if (U(x,y,1)<-y) U(x,y,1) = -(float)y;
+ bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound;
+ bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound;
+ }
+ _energy+=delta_I*delta_I + nsmoothness*_energy_regul;
+ }
+ if (V) cimg_forX(V,x) if (V(x,y,2)) { // Apply constraints.
+ U(x,y,0) = V(x,y,0)/factor;
+ U(x,y,1) = V(x,y,1)/factor;
+ }
+ }
+ }
+ }
+ const float d_energy = (_energy - energy)/(sw*sh*sd);
+ if (d_energy<=0 && -d_energy<_precision) break;
+ if (d_energy>0) dt*=0.5f;
+ energy = _energy;
+ }
+ }
+ return U;
+ }
+
+ //! Compute correspondence map between two images, using the patch-match algorithm.
+ /**
+ \param patch_image The image containing the reference patches to match with the instance image.
+ \param patch_width Width of the patch used for matching.
+ \param patch_height Height of the patch used for matching.
+ \param patch_depth Depth of the patch used for matching.
+ \param nb_iterations Number of patch-match iterations.
+ \param nb_randoms Number of randomization attempts (per pixel).
+ \param guide Image used as the initial correspondence estimate for the algorithm.
+ 'guide' may have a last channel with boolean values (0=false | other=true) that
+ tells for each pixel if its correspondence vector is constrained to its initial value (constraint mask).
+ \param[out] matching_score Returned as the image of matching scores.
+ \note
+ The patch-match algorithm is described in this paper:
+ Connelly Barnes, Eli Shechtman, Adam Finkelstein, Dan B Goldman(2009),
+ PatchMatch: A Randomized Correspondence Algorithm for Structural Image Editing
+ **/
+ template<typename t1, typename t2>
+ CImg<T>& patchmatch(const CImg<T>& patch_image,
+ const unsigned int patch_width,
+ const unsigned int patch_height,
+ const unsigned int patch_depth,
+ const unsigned int nb_iterations,
+ const unsigned int nb_randoms,
+ const CImg<t1> &guide,
+ CImg<t2> &matching_score) {
+ return get_patchmatch(patch_image,patch_width,patch_height,patch_depth,
+ nb_iterations,nb_randoms,guide,matching_score).move_to(*this);
+ }
+
+ //! Compute correspondence map between two images, using the patch-match algorithm \newinstance.
+ template<typename t1, typename t2>
+ CImg<intT> get_patchmatch(const CImg<T>& patch_image,
+ const unsigned int patch_width,
+ const unsigned int patch_height,
+ const unsigned int patch_depth,
+ const unsigned int nb_iterations,
+ const unsigned int nb_randoms,
+ const CImg<t1> &guide,
+ CImg<t2> &matching_score) const {
+ return _patchmatch(patch_image,patch_width,patch_height,patch_depth,
+ nb_iterations,nb_randoms,
+ guide,true,matching_score);
+ }
+
+ //! Compute correspondence map between two images, using the patch-match algorithm \overloading.
+ template<typename t>
+ CImg<T>& patchmatch(const CImg<T>& patch_image,
+ const unsigned int patch_width,
+ const unsigned int patch_height,
+ const unsigned int patch_depth,
+ const unsigned int nb_iterations,
+ const unsigned int nb_randoms,
+ const CImg<t> &guide) {
+ return get_patchmatch(patch_image,patch_width,patch_height,patch_depth,
+ nb_iterations,nb_randoms,guide).move_to(*this);
+ }
+
+ //! Compute correspondence map between two images, using the patch-match algorithm \overloading.
+ template<typename t>
+ CImg<intT> get_patchmatch(const CImg<T>& patch_image,
+ const unsigned int patch_width,
+ const unsigned int patch_height,
+ const unsigned int patch_depth,
+ const unsigned int nb_iterations,
+ const unsigned int nb_randoms,
+ const CImg<t> &guide) const {
+ return _patchmatch(patch_image,patch_width,patch_height,patch_depth,
+ nb_iterations,nb_randoms,
+ guide,false,CImg<T>::empty());
+ }
+
+ //! Compute correspondence map between two images, using the patch-match algorithm \overloading.
+ CImg<T>& patchmatch(const CImg<T>& patch_image,
+ const unsigned int patch_width,
+ const unsigned int patch_height,
+ const unsigned int patch_depth=1,
+ const unsigned int nb_iterations=5,
+ const unsigned int nb_randoms=5) {
+ return get_patchmatch(patch_image,patch_width,patch_height,patch_depth,
+ nb_iterations,nb_randoms).move_to(*this);
+ }
+
+ //! Compute correspondence map between two images, using the patch-match algorithm \overloading.
+ CImg<intT> get_patchmatch(const CImg<T>& patch_image,
+ const unsigned int patch_width,
+ const unsigned int patch_height,
+ const unsigned int patch_depth=1,
+ const unsigned int nb_iterations=5,
+ const unsigned int nb_randoms=5) const {
+ return _patchmatch(patch_image,patch_width,patch_height,patch_depth,
+ nb_iterations,nb_randoms,
+ CImg<T>::const_empty(),
+ false,CImg<T>::empty());
+ }
+
+ template<typename t1, typename t2>
+ CImg<intT> _patchmatch(const CImg<T>& patch_image,
+ const unsigned int patch_width,
+ const unsigned int patch_height,
+ const unsigned int patch_depth,
+ const unsigned int nb_iterations,
+ const unsigned int nb_randoms,
+ const CImg<t1> &guide,
+ const bool is_matching_score,
+ CImg<t2> &matching_score) const {
+ if (is_empty()) return CImg<intT>::const_empty();
+ if (patch_image._spectrum!=_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "patchmatch(): Instance image and specified patch image (%u,%u,%u,%u,%p) "
+ "have different spectrums.",
+ cimg_instance,
+ patch_image._width,patch_image._height,patch_image._depth,patch_image._spectrum,
+ patch_image._data);
+ if (patch_width>_width || patch_height>_height || patch_depth>_depth)
+ throw CImgArgumentException(_cimg_instance
+ "patchmatch(): Specified patch size %ux%ux%u is bigger than the dimensions "
+ "of the instance image.",
+ cimg_instance,patch_width,patch_height,patch_depth);
+ if (patch_width>patch_image._width || patch_height>patch_image._height || patch_depth>patch_image._depth)
+ throw CImgArgumentException(_cimg_instance
+ "patchmatch(): Specified patch size %ux%ux%u is bigger than the dimensions "
+ "of the patch image image (%u,%u,%u,%u,%p).",
+ cimg_instance,patch_width,patch_height,patch_depth,
+ patch_image._width,patch_image._height,patch_image._depth,patch_image._spectrum,
+ patch_image._data);
+ const unsigned int
+ _constraint = patch_image._depth>1?3:2,
+ constraint = guide._spectrum>_constraint?_constraint:0;
+
+ if (guide &&
+ (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<_constraint))
+ throw CImgArgumentException(_cimg_instance
+ "patchmatch(): Specified guide (%u,%u,%u,%u,%p) has invalid dimensions "
+ "considering instance and patch image image (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ guide._width,guide._height,guide._depth,guide._spectrum,guide._data,
+ patch_image._width,patch_image._height,patch_image._depth,patch_image._spectrum,
+ patch_image._data);
+
+ CImg<intT> map(_width,_height,_depth,patch_image._depth>1?3:2);
+ CImg<floatT> score(_width,_height,_depth);
+ const int
+ psizew = (int)patch_width, psizew1 = psizew/2, psizew2 = psizew - psizew1 - 1,
+ psizeh = (int)patch_height, psizeh1 = psizeh/2, psizeh2 = psizeh - psizeh1 - 1,
+ psized = (int)patch_depth, psized1 = psized/2, psized2 = psized - psized1 - 1;
+
+ if (_depth>1 || patch_image._depth>1) { // 3d version.
+
+ // Initialize correspondence map.
+ if (guide) cimg_forXYZ(*this,x,y,z) { // User-defined initialization.
+ const int
+ cx1 = x<=psizew1?x:(x<width() - psizew2?psizew1:psizew + x - width()), cx2 = psizew - cx1 - 1,
+ cy1 = y<=psizeh1?y:(y<height() - psizeh2?psizeh1:psizeh + y - height()), cy2 = psizeh - cy1 - 1,
+ cz1 = z<=psized1?z:(z<depth() - psized2?psized1:psized + z - depth()), cz2 = psized - cz1 - 1,
+ u = std::min(std::max((int)guide(x,y,z,0),cx1),patch_image.width() - 1 - cx2),
+ v = std::min(std::max((int)guide(x,y,z,1),cy1),patch_image.height() - 1 - cy2),
+ w = std::min(std::max((int)guide(x,y,z,2),cz1),patch_image.depth() - 1 - cz2);
+ map(x,y,z,0) = u;
+ map(x,y,z,1) = v;
+ map(x,y,z,2) = w;
+ score(x,y,z) = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ x - cx1,y - cy1,z - cz1,
+ u - cx1,v - cy1,w - cz1,cimg::type<float>::inf());
+ } else cimg_forXYZ(*this,x,y,z) { // Random initialization.
+ const int
+ cx1 = x<=psizew1?x:(x<width() - psizew2?psizew1:psizew + x - width()), cx2 = psizew - cx1 - 1,
+ cy1 = y<=psizeh1?y:(y<height() - psizeh2?psizeh1:psizeh + y - height()), cy2 = psizeh - cy1 - 1,
+ cz1 = z<=psized1?z:(z<depth() - psized2?psized1:psized + z - depth()), cz2 = psized - cz1 - 1,
+ u = (int)cimg::round(cimg::rand(cx1,patch_image.width() - 1 - cx2)),
+ v = (int)cimg::round(cimg::rand(cy1,patch_image.height() - 1 - cy2)),
+ w = (int)cimg::round(cimg::rand(cz1,patch_image.depth() - 1 - cz2));
+ map(x,y,z,0) = u;
+ map(x,y,z,1) = v;
+ map(x,y,z,2) = w;
+ score(x,y,z) = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ x - cx1,y - cy1,z - cz1,
+ u - cx1,v - cy1,w - cz1,cimg::type<float>::inf());
+ }
+
+ // Start iteration loop.
+ for (unsigned int iter = 0; iter<nb_iterations; ++iter) {
+ cimg_abort_test();
+ const bool is_even = !(iter%2);
+
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>64 && iter<nb_iterations-2))
+ cimg_forXYZ(*this,X,Y,Z) {
+ const int
+ x = is_even?X:width() - 1 - X,
+ y = is_even?Y:height() - 1 - Y,
+ z = is_even?Z:depth() - 1 - Z;
+ if (score(x,y,z)<=1e-5 || (constraint && guide(x,y,z,constraint)!=0)) continue;
+ const int
+ cx1 = x<=psizew1?x:(x<width() - psizew2?psizew1:psizew + x - width()), cx2 = psizew - cx1 - 1,
+ cy1 = y<=psizeh1?y:(y<height() - psizeh2?psizeh1:psizeh + y - height()), cy2 = psizeh - cy1 - 1,
+ cz1 = z<=psized1?z:(z<depth() - psized2?psized1:psized + z - depth()), cz2 = psized - cz1 - 1,
+ xp = x - cx1,
+ yp = y - cy1,
+ zp = z - cz1;
+
+ // Propagation.
+ if (is_even) {
+ if (x>0) { // Compare with left neighbor.
+ const int u = map(x - 1,y,z,0), v = map(x - 1,y,z,1), w = map(x - 1,y,z,2);
+ if (u>=cx1 - 1 && u<patch_image.width() - 1 - cx2 &&
+ v>=cy1 && v<patch_image.height() - cy2 &&
+ w>=cz1 && w<patch_image.depth() - cz2) {
+ const float
+ current_score = score(x,y,z),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ xp,yp,zp,u + 1 - cx1,v - cy1,w - cz1,current_score);
+ if (D<current_score) { score(x,y,z) = D; map(x,y,z,0) = u + 1; map(x,y,z,1) = v; map(x,y,z,2) = w; }
+ }
+ }
+ if (y>0) { // Compare with up neighbor.
+ const int u = map(x,y - 1,z,0), v = map(x,y - 1,z,1), w = map(x,y - 1,z,2);
+ if (u>=cx1 && u<patch_image.width() - cx2 &&
+ v>=cy1 - 1 && v<patch_image.height() - 1 - cy2 &&
+ w>=cz1 && w<patch_image.depth() - cx2) {
+ const float
+ current_score = score(x,y,z),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ xp,yp,zp,u - cx1,v + 1 - cy1,w - cz1,current_score);
+ if (D<current_score) { score(x,y,z) = D; map(x,y,z,0) = u; map(x,y,z,1) = v + 1; map(x,y,z,2) = w; }
+ }
+ }
+ if (z>0) { // Compare with backward neighbor.
+ const int u = map(x,y,z - 1,0), v = map(x,y,z - 1,1), w = map(x,y,z - 1,2);
+ if (u>=cx1 && u<patch_image.width() - cx2 &&
+ v>=cy1 && v<patch_image.height() - cy2 &&
+ w>=cz1 - 1 && w<patch_image.depth() - 1 - cz2) {
+ const float
+ current_score = score(x,y,z),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ xp,yp,zp,u - cx1,v - cy1,w + 1 - cz1,current_score);
+ if (D<current_score) { score(x,y,z) = D; map(x,y,z,0) = u; map(x,y,z,1) = v; map(x,y,z,2) = w + 1; }
+ }
+ }
+ } else {
+ if (x<width() - 1) { // Compare with right neighbor.
+ const int u = map(x + 1,y,z,0), v = map(x + 1,y,z,1), w = map(x + 1,y,z,2);
+ if (u>=cx1 + 1 && u<patch_image.width() + 1 - cx2 &&
+ v>=cy1 && v<patch_image.height() - cy2 &&
+ w>=cz1 && w<patch_image.depth() - cz2) {
+ const float
+ current_score = score(x,y,z),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ xp,yp,zp,u - 1 - cx1,v - cy1,w - cz1,current_score);
+ if (D<current_score) { score(x,y,z) = D; map(x,y,z,0) = u - 1; map(x,y,z,1) = v; map(x,y,z,2) = w; }
+ }
+ }
+ if (y<height() - 1) { // Compare with bottom neighbor.
+ const int u = map(x,y + 1,z,0), v = map(x,y + 1,z,1), w = map(x,y + 1,z,2);
+ if (u>=cx1 && u<patch_image.width() - cx2 &&
+ v>=cy1 + 1 && v<patch_image.height() + 1 - cy2 &&
+ w>=cz1 && w<patch_image.depth() - cz2) {
+ const float
+ current_score = score(x,y,z),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ xp,yp,zp,u - cx1,v - 1 - cy1,w - cz1,current_score);
+ if (D<current_score) { score(x,y,z) = D; map(x,y,z,0) = u; map(x,y,z,1) = v - 1; map(x,y,z,2) = w; }
+ }
+ }
+ if (z<depth() - 1) { // Compare with forward neighbor.
+ const int u = map(x,y,z + 1,0), v = map(x,y,z + 1,1), w = map(x,y,z + 1,2);
+ if (u>=cx1 && u<patch_image.width() - cx2 &&
+ v>=cy1 && v<patch_image.height() - cy2 &&
+ w>=cz1 + 1 && w<patch_image.depth() + 1 - cz2) {
+ const float
+ current_score = score(x,y,z),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ xp,yp,zp,u - cx1,v - cy1,w - 1 - cz1,current_score);
+ if (D<current_score) { score(x,y,z) = D; map(x,y,z,0) = u; map(x,y,z,1) = v; map(x,y,z,2) = w - 1; }
+ }
+ }
+ }
+
+ // Randomization.
+ const int u = map(x,y,z,0), v = map(x,y,z,1), w = map(x,y,z,2);
+ float dw = (float)patch_image.width(), dh = (float)patch_image.height(), dd = (float)patch_image.depth();
+ for (unsigned int i = 0; i<nb_randoms; ++i) {
+ const int
+ ui = (int)cimg::round(cimg::rand(std::max((float)cx1,u - dw),
+ std::min(patch_image.width() - 1.0f - cx2,u + dw))),
+ vi = (int)cimg::round(cimg::rand(std::max((float)cy1,v - dh),
+ std::min(patch_image.height() - 1.0f - cy2,v + dh))),
+ wi = (int)cimg::round(cimg::rand(std::max((float)cz1,w - dd),
+ std::min(patch_image.depth() - 1.0f - cz2,w + dd)));
+ if (ui!=u || vi!=v || wi!=w) {
+ const float
+ current_score = score(x,y,z),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,patch_depth,
+ xp,yp,zp,ui - cx1,vi - cy1,wi - cz1,current_score);
+ if (D<current_score) { score(x,y,z) = D; map(x,y,z,0) = ui; map(x,y,z,1) = vi; map(x,y,z,2) = wi; }
+ dw = std::max(5.0f,dw*0.5f); dh = std::max(5.0f,dh*0.5f); dd = std::max(5.0f,dd*0.5f);
+ }
+ }
+ }
+ }
+
+ } else { // 2d version.
+
+ // Initialize correspondence map.
+ if (guide) cimg_forXY(*this,x,y) { // Random initialization.
+ const int
+ cx1 = x<=psizew1?x:(x<width() - psizew2?psizew1:psizew + x - width()), cx2 = psizew - cx1 - 1,
+ cy1 = y<=psizeh1?y:(y<height() - psizeh2?psizeh1:psizeh + y - height()) , cy2 = psizeh - cy1 - 1,
+ u = std::min(std::max((int)guide(x,y,0),cx1),patch_image.width() - 1 - cx2),
+ v = std::min(std::max((int)guide(x,y,1),cy1),patch_image.height() - 1 - cy2);
+ map(x,y,0) = u;
+ map(x,y,1) = v;
+ score(x,y) = _patchmatch(*this,patch_image,patch_width,patch_height,
+ x - cx1,y - cy1,u - cx1,v - cy1,cimg::type<float>::inf());
+ } else cimg_forXY(*this,x,y) { // Random initialization.
+ const int
+ cx1 = x<=psizew1?x:(x<width() - psizew2?psizew1:psizew + x - width()), cx2 = psizew - cx1 - 1,
+ cy1 = y<=psizeh1?y:(y<height() - psizeh2?psizeh1:psizeh + y - height()) , cy2 = psizeh - cy1 - 1,
+ u = (int)cimg::round(cimg::rand(cx1,patch_image.width() - 1 - cx2)),
+ v = (int)cimg::round(cimg::rand(cy1,patch_image.height() - 1 - cy2));
+ map(x,y,0) = u;
+ map(x,y,1) = v;
+ score(x,y) = _patchmatch(*this,patch_image,patch_width,patch_height,
+ x - cx1,y - cy1,u - cx1,v - cy1,cimg::type<float>::inf());
+ }
+
+ // Start iteration loop.
+ for (unsigned int iter = 0; iter<nb_iterations; ++iter) {
+ const bool is_even = !(iter%2);
+
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_width>64 && iter<nb_iterations-2))
+ cimg_forXY(*this,X,Y) {
+ const int
+ x = is_even?X:width() - 1 - X,
+ y = is_even?Y:height() - 1 - Y;
+ if (score(x,y)<=1e-5 || (constraint && guide(x,y,constraint)!=0)) continue;
+ const int
+ cx1 = x<=psizew1?x:(x<width() - psizew2?psizew1:psizew + x - width()), cx2 = psizew - cx1 - 1,
+ cy1 = y<=psizeh1?y:(y<height() - psizeh2?psizeh1:psizeh + y - height()) , cy2 = psizeh - cy1 - 1,
+ xp = x - cx1,
+ yp = y - cy1;
+
+ // Propagation.
+ if (is_even) {
+ if (x>0) { // Compare with left neighbor.
+ const int u = map(x - 1,y,0), v = map(x - 1,y,1);
+ if (u>=cx1 - 1 && u<patch_image.width() - 1 - cx2 &&
+ v>=cy1 && v<patch_image.height() - cy2) {
+ const float
+ current_score = score(x,y),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,
+ xp,yp,u + 1 - cx1,v - cy1,current_score);
+ if (D<current_score) { score(x,y) = D; map(x,y,0) = u + 1; map(x,y,1) = v; }
+ }
+ }
+ if (y>0) { // Compare with up neighbor.
+ const int u = map(x,y - 1,0), v = map(x,y - 1,1);
+ if (u>=cx1 && u<patch_image.width() - cx2 &&
+ v>=cy1 - 1 && v<patch_image.height() - 1 - cy2) {
+ const float
+ current_score = score(x,y),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,
+ xp,yp,u - cx1,v + 1 - cy1,current_score);
+ if (D<current_score) { score(x,y) = D; map(x,y,0) = u; map(x,y,1) = v + 1; }
+ }
+ }
+ } else {
+ if (x<width() - 1) { // Compare with right neighbor.
+ const int u = map(x + 1,y,0), v = map(x + 1,y,1);
+ if (u>=cx1 + 1 && u<patch_image.width() + 1 - cx2 &&
+ v>=cy1 && v<patch_image.height() - cy2) {
+ const float
+ current_score = score(x,y),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,
+ xp,yp,u - 1 - cx1,v - cy1,current_score);
+ if (D<current_score) { score(x,y) = D; map(x,y,0) = u - 1; map(x,y,1) = v; }
+ }
+ }
+ if (y<height() - 1) { // Compare with bottom neighbor.
+ const int u = map(x,y + 1,0), v = map(x,y + 1,1);
+ if (u>=cx1 && u<patch_image.width() - cx2 &&
+ v>=cy1 + 1 && v<patch_image.height() + 1 - cy2) {
+ const float
+ current_score = score(x,y),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,
+ xp,yp,u - cx1,v - 1 - cy1,current_score);
+ if (D<current_score) { score(x,y) = D; map(x,y,0) = u; map(x,y,1) = v - 1; }
+ }
+ }
+ }
+
+ // Randomization.
+ const int u = map(x,y,0), v = map(x,y,1);
+ float dw = (float)patch_image.width(), dh = (float)patch_image.height();
+ for (unsigned int i = 0; i<nb_randoms; ++i) {
+ const int
+ ui = (int)cimg::round(cimg::rand(std::max((float)cx1,u - dw),
+ std::min(patch_image.width() - 1.0f - cx2,u + dw))),
+ vi = (int)cimg::round(cimg::rand(std::max((float)cy1,v - dh),
+ std::min(patch_image.height() - 1.0f - cy2,v + dh)));
+ if (ui!=u || vi!=v) {
+ const float
+ current_score = score(x,y),
+ D = _patchmatch(*this,patch_image,patch_width,patch_height,
+ xp,yp,ui - cx1,vi - cy1,current_score);
+ if (D<current_score) { score(x,y) = D; map(x,y,0) = ui; map(x,y,1) = vi; }
+ dw = std::max(5.0f,dw*0.5f); dh = std::max(5.0f,dh*0.5f);
+ }
+ }
+ }
+ }
+ }
+ if (is_matching_score) score.move_to(matching_score);
+ return map;
+ }
+
+ // Compute SSD between two patches in different images.
+ static float _patchmatch(const CImg<T>& img1, const CImg<T>& img2,
+ const unsigned int psizew, const unsigned int psizeh,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const float max_ssd) { // 2d version.
+ const T *p1 = img1.data(x1,y1), *p2 = img2.data(x2,y2);
+ const ulongT
+ offx1 = (ulongT)img1._width - psizew,
+ offx2 = (ulongT)img2._width - psizew,
+ offy1 = (ulongT)img1._width*img1._height - psizeh*img1._width,
+ offy2 = (ulongT)img2._width*img2._height - psizeh*img2._width;
+ float ssd = 0;
+ cimg_forC(img1,c) {
+ for (unsigned int j = 0; j<psizeh; ++j) {
+ for (unsigned int i = 0; i<psizew; ++i)
+ ssd += cimg::sqr(*(p1++) - *(p2++));
+ if (ssd>max_ssd) return max_ssd;
+ p1+=offx1; p2+=offx2;
+ }
+ p1+=offy1; p2+=offy2;
+ }
+ return ssd;
+ }
+
+ static float _patchmatch(const CImg<T>& img1, const CImg<T>& img2,
+ const unsigned int psizew, const unsigned int psizeh, const unsigned int psized,
+ const int x1, const int y1, const int z1,
+ const int x2, const int y2, const int z2,
+ const float max_ssd) { // 3d version.
+ const T *p1 = img1.data(x1,y1,z1), *p2 = img2.data(x2,y2,z2);
+ const ulongT
+ offx1 = (ulongT)img1._width - psizew,
+ offx2 = (ulongT)img2._width - psizew,
+ offy1 = (ulongT)img1._width*img1._height - psizeh*img1._width - psizew,
+ offy2 = (ulongT)img2._width*img2._height - psizeh*img2._width - psizew,
+ offz1 = (ulongT)img1._width*img1._height*img1._depth - psized*img1._width*img1._height -
+ psizeh*img1._width - psizew,
+ offz2 = (ulongT)img2._width*img2._height*img2._depth - psized*img2._width*img2._height -
+ psizeh*img2._width - psizew;
+ float ssd = 0;
+ cimg_forC(img1,c) {
+ for (unsigned int k = 0; k<psized; ++k) {
+ for (unsigned int j = 0; j<psizeh; ++j) {
+ for (unsigned int i = 0; i<psizew; ++i)
+ ssd += cimg::sqr(*(p1++) - *(p2++));
+ if (ssd>max_ssd) return max_ssd;
+ p1+=offx1; p2+=offx2;
+ }
+ p1+=offy1; p2+=offy2;
+ }
+ p1+=offz1; p2+=offz2;
+ }
+ return ssd;
+ }
+
+ //! Compute Euclidean distance function to a specified value.
+ /**
+ \param value Reference value.
+ \param metric Type of metric. Can be <tt>{ 0=Chebyshev | 1=Manhattan | 2=Euclidean | 3=Squared-euclidean }</tt>.
+ \note
+ The distance transform implementation has been submitted by A. Meijster, and implements
+ the article 'W.H. Hesselink, A. Meijster, J.B.T.M. Roerdink,
+ "A general algorithm for computing distance transforms in linear time.",
+ In: Mathematical Morphology and its Applications to Image and Signal Processing,
+ J. Goutsias, L. Vincent, and D.S. Bloomberg (eds.), Kluwer, 2000, pp. 331-340.'
+ The submitted code has then been modified to fit CImg coding style and constraints.
+ **/
+ CImg<T>& distance(const T& value, const unsigned int metric=2) {
+ if (is_empty()) return *this;
+ if (cimg::type<Tint>::string()!=cimg::type<T>::string()) // For datatype < int.
+ return CImg<Tint>(*this,false).distance((Tint)value,metric).
+ cut((Tint)cimg::type<T>::min(),(Tint)cimg::type<T>::max()).move_to(*this);
+ bool is_value = false;
+ cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,(T)0:(T)std::max(0,99999999); // (avoid VC++ warning)
+ if (!is_value) return fill(cimg::type<T>::max());
+ switch (metric) {
+ case 0 : return _distance_core(_distance_sep_cdt,_distance_dist_cdt); // Chebyshev.
+ case 1 : return _distance_core(_distance_sep_mdt,_distance_dist_mdt); // Manhattan.
+ case 3 : return _distance_core(_distance_sep_edt,_distance_dist_edt); // Squared Euclidean.
+ default : return _distance_core(_distance_sep_edt,_distance_dist_edt).sqrt(); // Euclidean.
+ }
+ return *this;
+ }
+
+ //! Compute distance to a specified value \newinstance.
+ CImg<Tfloat> get_distance(const T& value, const unsigned int metric=2) const {
+ return CImg<Tfloat>(*this,false).distance((Tfloat)value,metric);
+ }
+
+ static longT _distance_sep_edt(const longT i, const longT u, const longT *const g) {
+ return (u*u - i*i + g[u] - g[i])/(2*(u - i));
+ }
+
+ static longT _distance_dist_edt(const longT x, const longT i, const longT *const g) {
+ return (x - i)*(x - i) + g[i];
+ }
+
+ static longT _distance_sep_mdt(const longT i, const longT u, const longT *const g) {
+ return (u - i<=g[u] - g[i]?999999999:(g[u] - g[i] + u + i)/2);
+ }
+
+ static longT _distance_dist_mdt(const longT x, const longT i, const longT *const g) {
+ return (x<i?i - x:x - i) + g[i];
+ }
+
+ static longT _distance_sep_cdt(const longT i, const longT u, const longT *const g) {
+ const longT h = (i + u)/2;
+ if (g[i]<=g[u]) { return h<i + g[u]?i + g[u]:h; }
+ return h<u - g[i]?h:u - g[i];
+ }
+
+ static longT _distance_dist_cdt(const longT x, const longT i, const longT *const g) {
+ const longT d = x<i?i - x:x - i;
+ return d<g[i]?g[i]:d;
+ }
+
+ static void _distance_scan(const unsigned int len,
+ const longT *const g,
+ longT (*const sep)(const longT, const longT, const longT *const),
+ longT (*const f)(const longT, const longT, const longT *const),
+ longT *const s,
+ longT *const t,
+ longT *const dt) {
+ longT q = s[0] = t[0] = 0;
+ for (int u = 1; u<(int)len; ++u) { // Forward scan.
+ while ((q>=0) && f(t[q],s[q],g)>f(t[q],u,g)) { --q; }
+ if (q<0) { q = 0; s[0] = u; }
+ else { const longT w = 1 + sep(s[q], u, g); if (w<(longT)len) { ++q; s[q] = u; t[q] = w; }}
+ }
+ for (int u = (int)len - 1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan.
+ }
+
+ CImg<T>& _distance_core(longT (*const sep)(const longT, const longT, const longT *const),
+ longT (*const f)(const longT, const longT, const longT *const)) {
+ // Check for g++ 4.9.X, as OpenMP seems to crash for this particular function. I have no clues why.
+#define cimg_is_gcc49x (__GNUC__==4 && __GNUC_MINOR__==9)
+
+ const ulongT wh = (ulongT)_width*_height;
+#if defined(cimg_use_openmp) && !cimg_is_gcc49x
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2))
+#endif
+ cimg_forC(*this,c) {
+ CImg<longT> g(_width), dt(_width), s(_width), t(_width);
+ CImg<T> img = get_shared_channel(c);
+#if defined(cimg_use_openmp) && !cimg_is_gcc49x
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)
+ firstprivate(g,dt,s,t))
+#endif
+ cimg_forYZ(*this,y,z) { // Over X-direction.
+ cimg_forX(*this,x) g[x] = (longT)img(x,y,z,0,wh);
+ _distance_scan(_width,g,sep,f,s,t,dt);
+ cimg_forX(*this,x) img(x,y,z,0,wh) = (T)dt[x];
+ }
+ if (_height>1) {
+ g.assign(_height); dt.assign(_height); s.assign(_height); t.assign(_height);
+#if defined(cimg_use_openmp) && !cimg_is_gcc49x
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_height>=512 && _width*_depth>=16)
+ firstprivate(g,dt,s,t))
+#endif
+ cimg_forXZ(*this,x,z) { // Over Y-direction.
+ cimg_forY(*this,y) g[y] = (longT)img(x,y,z,0,wh);
+ _distance_scan(_height,g,sep,f,s,t,dt);
+ cimg_forY(*this,y) img(x,y,z,0,wh) = (T)dt[y];
+ }
+ }
+ if (_depth>1) {
+ g.assign(_depth); dt.assign(_depth); s.assign(_depth); t.assign(_depth);
+#if defined(cimg_use_openmp) && !cimg_is_gcc49x
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_depth>=512 && _width*_height>=16)
+ firstprivate(g,dt,s,t))
+#endif
+ cimg_forXY(*this,x,y) { // Over Z-direction.
+ cimg_forZ(*this,z) g[z] = (longT)img(x,y,z,0,wh);
+ _distance_scan(_depth,g,sep,f,s,t,dt);
+ cimg_forZ(*this,z) img(x,y,z,0,wh) = (T)dt[z];
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Compute chamfer distance to a specified value, with a custom metric.
+ /**
+ \param value Reference value.
+ \param metric_mask Metric mask.
+ \note The algorithm code has been initially proposed by A. Meijster, and modified by D. Tschumperlé.
+ **/
+ template<typename t>
+ CImg<T>& distance(const T& value, const CImg<t>& metric_mask) {
+ if (is_empty()) return *this;
+ bool is_value = false;
+ cimg_for(*this,ptr,T) *ptr = *ptr==value?is_value=true,0:(T)999999999;
+ if (!is_value) return fill(cimg::type<T>::max());
+ const ulongT wh = (ulongT)_width*_height;
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2))
+ cimg_forC(*this,c) {
+ CImg<T> img = get_shared_channel(c);
+ cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=1024))
+ cimg_forXYZ(metric_mask,dx,dy,dz) {
+ const t weight = metric_mask(dx,dy,dz);
+ if (weight) {
+ for (int z = dz, nz = 0; z<depth(); ++z,++nz) { // Forward scan.
+ for (int y = dy , ny = 0; y<height(); ++y,++ny) {
+ for (int x = dx, nx = 0; x<width(); ++x,++nx) {
+ const T dd = img(nx,ny,nz,0,wh) + weight;
+ if (dd<img(x,y,z,0,wh)) img(x,y,z,0,wh) = dd;
+ }
+ }
+ }
+ for (int z = depth() - 1 - dz, nz = depth() - 1; z>=0; --z,--nz) { // Backward scan.
+ for (int y = height() - 1 - dy, ny = height() - 1; y>=0; --y,--ny) {
+ for (int x = width() - 1 - dx, nx = width() - 1; x>=0; --x,--nx) {
+ const T dd = img(nx,ny,nz,0,wh) + weight;
+ if (dd<img(x,y,z,0,wh)) img(x,y,z,0,wh) = dd;
+ }
+ }
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Compute chamfer distance to a specified value, with a custom metric \newinstance.
+ template<typename t>
+ CImg<Tfloat> get_distance(const T& value, const CImg<t>& metric_mask) const {
+ return CImg<Tfloat>(*this,false).distance(value,metric_mask);
+ }
+
+ //! Compute distance to a specified value, according to a custom metric (use dijkstra algorithm).
+ /**
+ \param value Reference value.
+ \param metric Field of distance potentials.
+ \param is_high_connectivity Tells if the algorithm uses low or high connectivity.
+ **/
+ template<typename t, typename to>
+ CImg<T>& distance_dijkstra(const T& value, const CImg<t>& metric, const bool is_high_connectivity,
+ CImg<to>& return_path) {
+ return get_distance_dijkstra(value,metric,is_high_connectivity,return_path).move_to(*this);
+ }
+
+ //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm) \newinstance.
+ template<typename t, typename to>
+ CImg<typename cimg::superset<t,long>::type>
+ get_distance_dijkstra(const T& value, const CImg<t>& metric, const bool is_high_connectivity,
+ CImg<to>& return_path) const {
+ if (is_empty()) return return_path.assign();
+ if (!is_sameXYZ(metric))
+ throw CImgArgumentException(_cimg_instance
+ "distance_dijkstra(): image instance and metric map (%u,%u,%u,%u) "
+ "have incompatible dimensions.",
+ cimg_instance,
+ metric._width,metric._height,metric._depth,metric._spectrum);
+ typedef typename cimg::superset<t,long>::type td; // Type used for computing cumulative distances.
+ CImg<td> result(_width,_height,_depth,_spectrum), Q;
+ CImg<boolT> is_queued(_width,_height,_depth,1);
+ if (return_path) return_path.assign(_width,_height,_depth,_spectrum);
+
+ cimg_forC(*this,c) {
+ const CImg<T> img = get_shared_channel(c);
+ const CImg<t> met = metric.get_shared_channel(c%metric._spectrum);
+ CImg<td> res = result.get_shared_channel(c);
+ CImg<to> path = return_path?return_path.get_shared_channel(c):CImg<to>();
+ unsigned int sizeQ = 0;
+
+ // Detect initial seeds.
+ is_queued.fill(0);
+ cimg_forXYZ(img,x,y,z) if (img(x,y,z)==value) {
+ Q._priority_queue_insert(is_queued,sizeQ,0,x,y,z);
+ res(x,y,z) = 0;
+ if (path) path(x,y,z) = (to)0;
+ }
+
+ // Start distance propagation.
+ while (sizeQ) {
+
+ // Get and remove point with minimal potential from the queue.
+ const int x = (int)Q(0,1), y = (int)Q(0,2), z = (int)Q(0,3);
+ const td P = (td)-Q(0,0);
+ Q._priority_queue_remove(sizeQ);
+
+ // Update neighbors.
+ td npot = 0;
+ if (x - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x - 1,y,z) + P),x - 1,y,z)) {
+ res(x - 1,y,z) = npot; if (path) path(x - 1,y,z) = (to)2;
+ }
+ if (x + 1<width() && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x + 1,y,z) + P),x + 1,y,z)) {
+ res(x + 1,y,z) = npot; if (path) path(x + 1,y,z) = (to)1;
+ }
+ if (y - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y - 1,z) + P),x,y - 1,z)) {
+ res(x,y - 1,z) = npot; if (path) path(x,y - 1,z) = (to)8;
+ }
+ if (y + 1<height() && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y + 1,z) + P),x,y + 1,z)) {
+ res(x,y + 1,z) = npot; if (path) path(x,y + 1,z) = (to)4;
+ }
+ if (z - 1>=0 && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y,z - 1) + P),x,y,z - 1)) {
+ res(x,y,z - 1) = npot; if (path) path(x,y,z - 1) = (to)32;
+ }
+ if (z + 1<depth() && Q._priority_queue_insert(is_queued,sizeQ,-(npot=met(x,y,z + 1) + P),x,y,z + 1)) {
+ res(x,y,z + 1) = npot; if (path) path(x,y,z + 1) = (to)16;
+ }
+
+ if (is_high_connectivity) {
+ const float sqrt2 = std::sqrt(2.0f), sqrt3 = std::sqrt(3.0f);
+
+ // Diagonal neighbors on slice z.
+ if (x - 1>=0 && y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y - 1,z) + P)),x - 1,y - 1,z)) {
+ res(x - 1,y - 1,z) = npot; if (path) path(x - 1,y - 1,z) = (to)10;
+ }
+ if (x + 1<width() && y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y - 1,z) + P)),x + 1,y - 1,z)) {
+ res(x + 1,y - 1,z) = npot; if (path) path(x + 1,y - 1,z) = (to)9;
+ }
+ if (x - 1>=0 && y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y + 1,z) + P)),x - 1,y + 1,z)) {
+ res(x - 1,y + 1,z) = npot; if (path) path(x - 1,y + 1,z) = (to)6;
+ }
+ if (x + 1<width() && y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y + 1,z) + P)),x + 1,y + 1,z)) {
+ res(x + 1,y + 1,z) = npot; if (path) path(x + 1,y + 1,z) = (to)5;
+ }
+
+ if (z - 1>=0) { // Diagonal neighbors on slice z - 1.
+ if (x - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z - 1) + P)),x - 1,y,z - 1)) {
+ res(x - 1,y,z - 1) = npot; if (path) path(x - 1,y,z - 1) = (to)34;
+ }
+ if (x + 1<width() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y,z - 1) + P)),x + 1,y,z - 1)) {
+ res(x + 1,y,z - 1) = npot; if (path) path(x + 1,y,z - 1) = (to)33;
+ }
+ if (y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z - 1) + P)),x,y - 1,z - 1)) {
+ res(x,y - 1,z - 1) = npot; if (path) path(x,y - 1,z - 1) = (to)40;
+ }
+ if (y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y + 1,z - 1) + P)),x,y + 1,z - 1)) {
+ res(x,y + 1,z - 1) = npot; if (path) path(x,y + 1,z - 1) = (to)36;
+ }
+ if (x - 1>=0 && y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z - 1) + P)),
+ x - 1,y - 1,z - 1)) {
+ res(x - 1,y - 1,z - 1) = npot; if (path) path(x - 1,y - 1,z - 1) = (to)42;
+ }
+ if (x + 1<width() && y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z - 1) + P)),
+ x + 1,y - 1,z - 1)) {
+ res(x + 1,y - 1,z - 1) = npot; if (path) path(x + 1,y - 1,z - 1) = (to)41;
+ }
+ if (x - 1>=0 && y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y + 1,z - 1) + P)),
+ x - 1,y + 1,z - 1)) {
+ res(x - 1,y + 1,z - 1) = npot; if (path) path(x - 1,y + 1,z - 1) = (to)38;
+ }
+ if (x + 1<width() && y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y + 1,z - 1) + P)),
+ x + 1,y + 1,z - 1)) {
+ res(x + 1,y + 1,z - 1) = npot; if (path) path(x + 1,y + 1,z - 1) = (to)37;
+ }
+ }
+
+ if (z + 1<depth()) { // Diagonal neighbors on slice z + 1.
+ if (x - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x - 1,y,z + 1) + P)),x - 1,y,z + 1)) {
+ res(x - 1,y,z + 1) = npot; if (path) path(x - 1,y,z + 1) = (to)18;
+ }
+ if (x + 1<width() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x + 1,y,z + 1) + P)),x + 1,y,z + 1)) {
+ res(x + 1,y,z + 1) = npot; if (path) path(x + 1,y,z + 1) = (to)17;
+ }
+ if (y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y - 1,z + 1) + P)),x,y - 1,z + 1)) {
+ res(x,y - 1,z + 1) = npot; if (path) path(x,y - 1,z + 1) = (to)24;
+ }
+ if (y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt2*met(x,y + 1,z + 1) + P)),x,y + 1,z + 1)) {
+ res(x,y + 1,z + 1) = npot; if (path) path(x,y + 1,z + 1) = (to)20;
+ }
+ if (x - 1>=0 && y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y - 1,z + 1) + P)),
+ x - 1,y - 1,z + 1)) {
+ res(x - 1,y - 1,z + 1) = npot; if (path) path(x - 1,y - 1,z + 1) = (to)26;
+ }
+ if (x + 1<width() && y - 1>=0 &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y - 1,z + 1) + P)),
+ x + 1,y - 1,z + 1)) {
+ res(x + 1,y - 1,z + 1) = npot; if (path) path(x + 1,y - 1,z + 1) = (to)25;
+ }
+ if (x - 1>=0 && y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x - 1,y + 1,z + 1) + P)),
+ x - 1,y + 1,z + 1)) {
+ res(x - 1,y + 1,z + 1) = npot; if (path) path(x - 1,y + 1,z + 1) = (to)22;
+ }
+ if (x + 1<width() && y + 1<height() &&
+ Q._priority_queue_insert(is_queued,sizeQ,-(npot=(td)(sqrt3*met(x + 1,y + 1,z + 1) + P)),
+ x + 1,y + 1,z + 1)) {
+ res(x + 1,y + 1,z + 1) = npot; if (path) path(x + 1,y + 1,z + 1) = (to)21;
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm). \overloading.
+ template<typename t>
+ CImg<T>& distance_dijkstra(const T& value, const CImg<t>& metric,
+ const bool is_high_connectivity=false) {
+ return get_distance_dijkstra(value,metric,is_high_connectivity).move_to(*this);
+ }
+
+ //! Compute distance map to a specified value, according to a custom metric (use dijkstra algorithm). \newinstance.
+ template<typename t>
+ CImg<Tfloat> get_distance_dijkstra(const T& value, const CImg<t>& metric,
+ const bool is_high_connectivity=false) const {
+ CImg<T> return_path;
+ return get_distance_dijkstra(value,metric,is_high_connectivity,return_path);
+ }
+
+ //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm).
+ /**
+ \param value Reference value.
+ \param metric Field of distance potentials.
+ **/
+ template<typename t>
+ CImg<T>& distance_eikonal(const T& value, const CImg<t>& metric) {
+ return get_distance_eikonal(value,metric).move_to(*this);
+ }
+
+ //! Compute distance map to one source point, according to a custom metric (use fast marching algorithm).
+ template<typename t>
+ CImg<Tfloat> get_distance_eikonal(const T& value, const CImg<t>& metric) const {
+ if (is_empty()) return *this;
+ if (!is_sameXYZ(metric))
+ throw CImgArgumentException(_cimg_instance
+ "distance_eikonal(): image instance and metric map (%u,%u,%u,%u) have "
+ "incompatible dimensions.",
+ cimg_instance,
+ metric._width,metric._height,metric._depth,metric._spectrum);
+ CImg<Tfloat> result(_width,_height,_depth,_spectrum,cimg::type<Tfloat>::max()), Q;
+ CImg<charT> state(_width,_height,_depth); // -1=far away, 0=narrow, 1=frozen.
+
+ cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2) firstprivate(Q,state))
+ cimg_forC(*this,c) {
+ const CImg<T> img = get_shared_channel(c);
+ const CImg<t> met = metric.get_shared_channel(c%metric._spectrum);
+ CImg<Tfloat> res = result.get_shared_channel(c);
+ unsigned int sizeQ = 0;
+ state.fill(-1);
+
+ // Detect initial seeds.
+ Tfloat *ptr1 = res._data; char *ptr2 = state._data;
+ cimg_for(img,ptr0,T) { if (*ptr0==value) { *ptr1 = 0; *ptr2 = 1; } ++ptr1; ++ptr2; }
+
+ // Initialize seeds neighbors.
+ ptr2 = state._data;
+ cimg_forXYZ(img,x,y,z) if (*(ptr2++)==1) {
+ if (x - 1>=0 && state(x - 1,y,z)==-1) {
+ const Tfloat dist = res(x - 1,y,z) = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z);
+ Q._eik_priority_queue_insert(state,sizeQ,-dist,x - 1,y,z);
+ }
+ if (x + 1<width() && state(x + 1,y,z)==-1) {
+ const Tfloat dist = res(x + 1,y,z) = __distance_eikonal(res,met(x + 1,y,z),x + 1,y,z);
+ Q._eik_priority_queue_insert(state,sizeQ,-dist,x + 1,y,z);
+ }
+ if (y - 1>=0 && state(x,y - 1,z)==-1) {
+ const Tfloat dist = res(x,y - 1,z) = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z);
+ Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y - 1,z);
+ }
+ if (y + 1<height() && state(x,y + 1,z)==-1) {
+ const Tfloat dist = res(x,y + 1,z) = __distance_eikonal(res,met(x,y + 1,z),x,y + 1,z);
+ Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y + 1,z);
+ }
+ if (z - 1>=0 && state(x,y,z - 1)==-1) {
+ const Tfloat dist = res(x,y,z - 1) = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1);
+ Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z - 1);
+ }
+ if (z + 1<depth() && state(x,y,z + 1)==-1) {
+ const Tfloat dist = res(x,y,z + 1) = __distance_eikonal(res,met(x,y,z + 1),x,y,z + 1);
+ Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z + 1);
+ }
+ }
+
+ // Propagate front.
+ while (sizeQ) {
+ int x = -1, y = -1, z = -1;
+ while (sizeQ && x<0) {
+ x = (int)Q(0,1); y = (int)Q(0,2); z = (int)Q(0,3);
+ Q._priority_queue_remove(sizeQ);
+ if (state(x,y,z)==1) x = -1; else state(x,y,z) = 1;
+ }
+ if (x>=0) {
+ if (x - 1>=0 && state(x - 1,y,z)!=1) {
+ const Tfloat dist = __distance_eikonal(res,met(x - 1,y,z),x - 1,y,z);
+ if (dist<res(x - 1,y,z)) {
+ res(x - 1,y,z) = dist; Q._eik_priority_queue_insert(state,sizeQ,-dist,x - 1,y,z);
+ }
+ }
+ if (x + 1<width() && state(x + 1,y,z)!=1) {
+ const Tfloat dist = __distance_eikonal(res,met(x + 1,y,z),x + 1,y,z);
+ if (dist<res(x + 1,y,z)) {
+ res(x + 1,y,z) = dist; Q._eik_priority_queue_insert(state,sizeQ,-dist,x + 1,y,z);
+ }
+ }
+ if (y - 1>=0 && state(x,y - 1,z)!=1) {
+ const Tfloat dist = __distance_eikonal(res,met(x,y - 1,z),x,y - 1,z);
+ if (dist<res(x,y - 1,z)) {
+ res(x,y - 1,z) = dist; Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y - 1,z);
+ }
+ }
+ if (y + 1<height() && state(x,y + 1,z)!=1) {
+ const Tfloat dist = __distance_eikonal(res,met(x,y + 1,z),x,y + 1,z);
+ if (dist<res(x,y + 1,z)) {
+ res(x,y + 1,z) = dist; Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y + 1,z);
+ }
+ }
+ if (z - 1>=0 && state(x,y,z - 1)!=1) {
+ const Tfloat dist = __distance_eikonal(res,met(x,y,z - 1),x,y,z - 1);
+ if (dist<res(x,y,z - 1)) {
+ res(x,y,z - 1) = dist; Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z - 1);
+ }
+ }
+ if (z + 1<depth() && state(x,y,z + 1)!=1) {
+ const Tfloat dist = __distance_eikonal(res,met(x,y,z + 1),x,y,z + 1);
+ if (dist<res(x,y,z + 1)) {
+ res(x,y,z + 1) = dist; Q._eik_priority_queue_insert(state,sizeQ,-dist,x,y,z + 1);
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ // Locally solve eikonal equation.
+ Tfloat __distance_eikonal(const CImg<Tfloat>& res, const Tfloat P,
+ const int x=0, const int y=0, const int z=0) const {
+ const Tfloat M = (Tfloat)cimg::type<T>::max();
+ T T1 = (T)std::min(x - 1>=0?res(x - 1,y,z):M,x + 1<width()?res(x + 1,y,z):M);
+ Tfloat root = 0;
+ if (_depth>1) { // 3d.
+ T
+ T2 = (T)std::min(y - 1>=0?res(x,y - 1,z):M,y + 1<height()?res(x,y + 1,z):M),
+ T3 = (T)std::min(z - 1>=0?res(x,y,z - 1):M,z + 1<depth()?res(x,y,z + 1):M);
+ if (T1>T2) cimg::swap(T1,T2);
+ if (T2>T3) cimg::swap(T2,T3);
+ if (T1>T2) cimg::swap(T1,T2);
+ if (P<=0) return (Tfloat)T1;
+ if (T3<M && ___distance_eikonal(3,-2*(T1 + T2 + T3),T1*T1 + T2*T2 + T3*T3 - P*P,root))
+ return std::max((Tfloat)T3,root);
+ if (T2<M && ___distance_eikonal(2,-2*(T1 + T2),T1*T1 + T2*T2 - P*P,root))
+ return std::max((Tfloat)T2,root);
+ return P + T1;
+ } else if (_height>1) { // 2d.
+ T T2 = (T)std::min(y - 1>=0?res(x,y - 1,z):M,y + 1<height()?res(x,y + 1,z):M);
+ if (T1>T2) cimg::swap(T1,T2);
+ if (P<=0) return (Tfloat)T1;
+ if (T2<M && ___distance_eikonal(2,-2*(T1 + T2),T1*T1 + T2*T2 - P*P,root))
+ return std::max((Tfloat)T2,root);
+ return P + T1;
+ } else { // 1d.
+ if (P<=0) return (Tfloat)T1;
+ return P + T1;
+ }
+ return 0;
+ }
+
+ // Find max root of a 2nd-order polynomial.
+ static bool ___distance_eikonal(const Tfloat a, const Tfloat b, const Tfloat c, Tfloat &root) {
+ const Tfloat delta = b*b - 4*a*c;
+ if (delta<0) return false;
+ root = 0.5f*(-b + std::sqrt(delta))/a;
+ return true;
+ }
+
+ // Insert new point in heap.
+ template<typename t>
+ void _eik_priority_queue_insert(CImg<charT>& state, unsigned int& siz, const t value,
+ const unsigned int x, const unsigned int y, const unsigned int z) {
+ if (state(x,y,z)>0) return;
+ state(x,y,z) = 0;
+ 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));
+ }
+ }
+
+ //! Compute distance function to 0-valued isophotes, using the Eikonal PDE.
+ /**
+ \param nb_iterations Number of PDE iterations.
+ \param band_size Size of the narrow band.
+ \param time_step Time step of the PDE iterations.
+ **/
+ 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 = 1e-5f + cimg::hypot(gx,gy,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 = std::max((Tfloat)1e-5,cimg::hypot(gx,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;
+ }
+
+ //! Compute distance function to 0-valued isophotes, using the Eikonal PDE \newinstance.
+ 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 Haar multiscale wavelet transform.
+ /**
+ \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);
+ }
+
+ //! Compute Haar multiscale wavelet transform \newinstance.
+ 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;
+ const Tfloat sqrt2 = std::sqrt(2.0f);
+ if (nb_scales==1) {
+ switch (cimg::lowercase(axis)) { // Single scale transform
+ case 'x' : {
+ const unsigned int w = _width/2;
+ if (w) {
+ if ((w%2) && w!=1)
+ throw CImgInstanceException(_cimg_instance
+ "haar(): Sub-image width %u is not even.",
+ 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)/sqrt2;
+ res(x2++,y,z,c) = (val0 + val1)/sqrt2;
+ }
+ } 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)/sqrt2;
+ res(xw,y,z,c) = (val1 - val0)/sqrt2;
+ }
+ }
+ } else return *this;
+ } break;
+ case 'y' : {
+ const unsigned int h = _height/2;
+ if (h) {
+ if ((h%2) && h!=1)
+ throw CImgInstanceException(_cimg_instance
+ "haar(): Sub-image height %u is not even.",
+ 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)/sqrt2;
+ res(x,y2++,z,c) = (val0 + val1)/sqrt2;
+ }
+ } 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)/sqrt2;
+ res(x,yh,z,c) = (val1 - val0)/sqrt2;
+ }
+ }
+ } else return *this;
+ } break;
+ case 'z' : {
+ const unsigned int d = _depth/2;
+ if (d) {
+ if ((d%2) && d!=1)
+ throw CImgInstanceException(_cimg_instance
+ "haar(): Sub-image depth %u is not even.",
+ 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)/sqrt2;
+ res(x,y,z2++,c) = (val0 + val1)/sqrt2;
+ }
+ } 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)/sqrt2;
+ res(x,y,zd,c) = (val1 - val0)/sqrt2;
+ }
+ }
+ } 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::lowercase(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::lowercase(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 Haar multiscale wavelet transform \overloading.
+ /**
+ \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);
+ }
+
+ //! Compute Haar multiscale wavelet transform \newinstance.
+ 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 1d Fast Fourier Transform, along a specified axis.
+ /**
+ \param axis Axis along which the FFT is computed.
+ \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed.
+ **/
+ CImgList<Tfloat> get_FFT(const char axis, const bool is_invert=false) const {
+ CImgList<Tfloat> res(*this,CImg<Tfloat>());
+ CImg<Tfloat>::FFT(res[0],res[1],axis,is_invert);
+ return res;
+ }
+
+ //! Compute n-d Fast Fourier Transform.
+ /*
+ \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed.
+ **/
+ CImgList<Tfloat> get_FFT(const bool is_invert=false) const {
+ CImgList<Tfloat> res(*this,CImg<Tfloat>());
+ CImg<Tfloat>::FFT(res[0],res[1],is_invert);
+ return res;
+ }
+
+ //! Compute 1d Fast Fourier Transform, along a specified axis.
+ /**
+ \param[in,out] real Real part of the pixel values.
+ \param[in,out] imag Imaginary part of the pixel values.
+ \param axis Axis along which the FFT is computed.
+ \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed.
+ **/
+ static void FFT(CImg<T>& real, CImg<T>& imag, const char axis, const bool is_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,(T)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
+ cimg::mutex(12);
+ fftw_complex *data_in;
+ fftw_plan data_plan;
+
+ switch (cimg::lowercase(axis)) {
+ case 'x' : { // Fourier along X, using FFTW library.
+ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width);
+ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) "
+ "for computing FFT of image (%u,%u,%u,%u) along the X-axis.",
+ pixel_type(),
+ cimg::strbuffersize(sizeof(fftw_complex)*real._width),
+ real._width,real._height,real._depth,real._spectrum);
+
+ data_plan = fftw_plan_dft_1d(real._width,data_in,data_in,is_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 (is_invert) cimg_forX(real,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); }
+ else cimg_forX(real,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }
+ }
+ } break;
+ case 'y' : { // Fourier along Y, using FFTW library.
+ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._height);
+ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) "
+ "for computing FFT of image (%u,%u,%u,%u) along the Y-axis.",
+ pixel_type(),
+ cimg::strbuffersize(sizeof(fftw_complex)*real._height),
+ real._width,real._height,real._depth,real._spectrum);
+
+ data_plan = fftw_plan_dft_1d(real._height,data_in,data_in,is_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 (is_invert)
+ cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }
+ else cimg_forY(real,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }
+ }
+ } break;
+ case 'z' : { // Fourier along Z, using FFTW library.
+ data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * real._depth);
+ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) "
+ "for computing FFT of image (%u,%u,%u,%u) along the Z-axis.",
+ pixel_type(),
+ cimg::strbuffersize(sizeof(fftw_complex)*real._depth),
+ real._width,real._height,real._depth,real._spectrum);
+
+ data_plan = fftw_plan_dft_1d(real._depth,data_in,data_in,is_invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
+ const ulongT off = (ulongT)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 (is_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 :
+ 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);
+ }
+ fftw_destroy_plan(data_plan);
+ fftw_free(data_in);
+ cimg::mutex(12,0);
+#else
+ switch (cimg::lowercase(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)((is_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 (is_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)((is_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 (is_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)((is_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 (is_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 n-d Fast Fourier Transform.
+ /**
+ \param[in,out] real Real part of the pixel values.
+ \param[in,out] imag Imaginary part of the pixel values.
+ \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed.
+ \param nb_threads Number of parallel threads used for the computation.
+ Use \c 0 to set this to the number of available cpus.
+ **/
+ static void FFT(CImg<T>& real, CImg<T>& imag, const bool is_invert=false, const unsigned int nb_threads=0) {
+ 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,(T)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
+ cimg::mutex(12);
+#ifndef cimg_use_fftw3_singlethread
+ const unsigned int _nb_threads = nb_threads?nb_threads:cimg::nb_cpus();
+ static int fftw_st = fftw_init_threads();
+ cimg::unused(fftw_st);
+ fftw_plan_with_nthreads(_nb_threads);
+#else
+ cimg::unused(nb_threads);
+#endif
+ fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*real._width*real._height*real._depth);
+ if (!data_in) throw CImgInstanceException("CImgList<%s>::FFT(): Failed to allocate memory (%s) "
+ "for computing FFT of image (%u,%u,%u,%u).",
+ pixel_type(),
+ cimg::strbuffersize(sizeof(fftw_complex)*real._width*
+ real._height*real._depth*real._spectrum),
+ real._width,real._height,real._depth,real._spectrum);
+
+ fftw_plan data_plan;
+ const ulongT w = (ulongT)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,
+ is_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 (!is_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);
+#ifndef cimg_use_fftw3_singlethread
+ fftw_cleanup_threads();
+#endif
+ cimg::mutex(12,0);
+#else
+ cimg::unused(nb_threads);
+ if (real._depth>1) FFT(real,imag,'z',is_invert);
+ if (real._height>1) FFT(real,imag,'y',is_invert);
+ if (real._width>1) FFT(real,imag,'x',is_invert);
+#endif
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name 3d Objects Management
+ //@{
+ //-------------------------------------
+
+ //! Shift 3d object's vertices.
+ /**
+ \param tx X-coordinate of the 3d displacement vector.
+ \param ty Y-coordinate of the 3d displacement vector.
+ \param tz Z-coordinate of the 3d displacement vector.
+ **/
+ 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_row(0)+=tx; get_shared_row(1)+=ty; get_shared_row(2)+=tz;
+ return *this;
+ }
+
+ //! Shift 3d object's vertices \newinstance.
+ 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 3d object's vertices, so that it becomes centered.
+ /**
+ \note The object center is computed as its barycenter.
+ **/
+ 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_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2);
+ float
+ xm, xM = (float)xcoords.max_min(xm),
+ ym, yM = (float)ycoords.max_min(ym),
+ zm, zM = (float)zcoords.max_min(zm);
+ xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2;
+ return *this;
+ }
+
+ //! Shift 3d object's vertices, so that it becomes centered \newinstance.
+ CImg<Tfloat> get_shift_object3d() const {
+ return CImg<Tfloat>(*this,false).shift_object3d();
+ }
+
+ //! Resize 3d object.
+ /**
+ \param sx Width of the 3d object's bounding box.
+ \param sy Height of the 3d object's bounding box.
+ \param sz Depth of the 3d object's bounding box.
+ **/
+ CImg<T>& resize_object3d(const float sx, const float sy=-100, const float sz=-100) {
+ if (_height!=3 || _depth>1 || _spectrum>1)
+ throw CImgInstanceException(_cimg_instance
+ "resize_object3d(): Instance is not a set of 3d vertices.",
+ cimg_instance);
+
+ CImg<T> xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2);
+ float
+ xm, xM = (float)xcoords.max_min(xm),
+ ym, yM = (float)ycoords.max_min(ym),
+ zm, zM = (float)zcoords.max_min(zm);
+ if (xm<xM) { if (sx>0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; }
+ if (ym<yM) { if (sy>0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; }
+ if (zm<zM) { if (sz>0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; }
+ return *this;
+ }
+
+ //! Resize 3d object \newinstance.
+ 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 3d object to unit size.
+ CImg<T> resize_object3d() {
+ if (_height!=3 || _depth>1 || _spectrum>1)
+ throw CImgInstanceException(_cimg_instance
+ "resize_object3d(): Instance is not a set of 3d vertices.",
+ cimg_instance);
+
+ CImg<T> xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2);
+ float
+ xm, xM = (float)xcoords.max_min(xm),
+ ym, yM = (float)ycoords.max_min(ym),
+ zm, zM = (float)zcoords.max_min(zm);
+ 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;
+ }
+
+ //! Resize 3d object to unit size \newinstance.
+ CImg<Tfloat> get_resize_object3d() const {
+ return CImg<Tfloat>(*this,false).resize_object3d();
+ }
+
+ //! Merge two 3d objects together.
+ /**
+ \param[in,out] primitives Primitives data of the current 3d object.
+ \param obj_vertices Vertices data of the additional 3d object.
+ \param obj_primitives Primitives data of the additional 3d object.
+ **/
+ template<typename tf, typename tp, typename tff>
+ CImg<T>& append_object3d(CImgList<tf>& primitives, const CImg<tp>& obj_vertices,
+ const CImgList<tff>& obj_primitives) {
+ if (!obj_vertices || !obj_primitives) return *this;
+ if (obj_vertices._height!=3 || obj_vertices._depth>1 || obj_vertices._spectrum>1)
+ throw CImgInstanceException(_cimg_instance
+ "append_object3d(): Specified vertice image (%u,%u,%u,%u,%p) is not a "
+ "set of 3d vertices.",
+ cimg_instance,
+ obj_vertices._width,obj_vertices._height,
+ obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data);
+
+ if (is_empty()) { primitives.assign(obj_primitives); return assign(obj_vertices); }
+ if (_height!=3 || _depth>1 || _spectrum>1)
+ throw CImgInstanceException(_cimg_instance
+ "append_object3d(): Instance is not a set of 3d vertices.",
+ cimg_instance);
+
+ const unsigned int P = _width;
+ append(obj_vertices,'x');
+ const unsigned int N = primitives._width;
+ primitives.insert(obj_primitives);
+ for (unsigned int i = N; i<primitives._width; ++i) {
+ CImg<tf> &p = primitives[i];
+ switch (p.size()) {
+ case 1 : p[0]+=P; break; // Point.
+ case 5 : p[0]+=P; p[1]+=P; break; // Sphere.
+ case 2 : case 6 : p[0]+=P; p[1]+=P; break; // Segment.
+ case 3 : case 9 : p[0]+=P; p[1]+=P; p[2]+=P; break; // Triangle.
+ case 4 : case 12 : p[0]+=P; p[1]+=P; p[2]+=P; p[3]+=P; break; // Rectangle.
+ }
+ }
+ return *this;
+ }
+
+ //! Texturize primitives of a 3d object.
+ /**
+ \param[in,out] primitives Primitives data of the 3d object.
+ \param[in,out] colors Colors data of the 3d object.
+ \param texture Texture image to map to 3d object.
+ \param coords Texture-mapping coordinates.
+ **/
+ template<typename tp, typename tc, typename tt, typename tx>
+ const CImg<T>& texturize_object3d(CImgList<tp>& primitives, CImgList<tc>& colors,
+ const CImg<tt>& texture, const CImg<tx>& coords=CImg<tx>::const_empty()) const {
+ if (is_empty()) return *this;
+ if (_height!=3)
+ throw CImgInstanceException(_cimg_instance
+ "texturize_object3d(): image instance is not a set of 3d points.",
+ cimg_instance);
+ if (coords && (coords._width!=_width || coords._height!=2))
+ throw CImgArgumentException(_cimg_instance
+ "texturize_object3d(): Invalid specified texture coordinates (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ coords._width,coords._height,coords._depth,coords._spectrum,coords._data);
+ CImg<intT> _coords;
+ if (!coords) { // If no texture coordinates specified, do a default XY-projection.
+ _coords.assign(_width,2);
+ float
+ xmin, xmax = (float)get_shared_row(0).max_min(xmin),
+ ymin, ymax = (float)get_shared_row(1).max_min(ymin),
+ dx = xmax>xmin?xmax-xmin:1,
+ dy = ymax>ymin?ymax-ymin:1;
+ cimg_forX(*this,p) {
+ _coords(p,0) = (int)(((*this)(p,0) - xmin)*texture._width/dx);
+ _coords(p,1) = (int)(((*this)(p,1) - ymin)*texture._height/dy);
+ }
+ } else _coords = coords;
+
+ int texture_ind = -1;
+ cimglist_for(primitives,l) {
+ CImg<tp> &p = primitives[l];
+ const unsigned int siz = p.size();
+ switch (siz) {
+ case 1 : { // Point.
+ const unsigned int i0 = (unsigned int)p[0];
+ const int x0 = _coords(i0,0), y0 = _coords(i0,1);
+ texture.get_vector_at(x0<=0?0:x0>=texture.width()?texture.width() - 1:x0,
+ y0<=0?0:y0>=texture.height()?texture.height() - 1:y0).move_to(colors[l]);
+ } break;
+ case 2 : case 6 : { // Line.
+ const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1];
+ const int
+ x0 = _coords(i0,0), y0 = _coords(i0,1),
+ x1 = _coords(i1,0), y1 = _coords(i1,1);
+ if (texture_ind<0) colors[texture_ind=l].assign(texture,false);
+ else colors[l].assign(colors[texture_ind],true);
+ CImg<tp>::vector(i0,i1,x0,y0,x1,y1).move_to(p);
+ } break;
+ case 3 : case 9 : { // Triangle.
+ const unsigned int i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2];
+ const int
+ x0 = _coords(i0,0), y0 = _coords(i0,1),
+ x1 = _coords(i1,0), y1 = _coords(i1,1),
+ x2 = _coords(i2,0), y2 = _coords(i2,1);
+ if (texture_ind<0) colors[texture_ind=l].assign(texture,false);
+ else colors[l].assign(colors[texture_ind],true);
+ CImg<tp>::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p);
+ } break;
+ case 4 : case 12 : { // Quadrangle.
+ const unsigned int
+ i0 = (unsigned int)p[0], i1 = (unsigned int)p[1], i2 = (unsigned int)p[2], i3 = (unsigned int)p[3];
+ const int
+ x0 = _coords(i0,0), y0 = _coords(i0,1),
+ x1 = _coords(i1,0), y1 = _coords(i1,1),
+ x2 = _coords(i2,0), y2 = _coords(i2,1),
+ x3 = _coords(i3,0), y3 = _coords(i3,1);
+ if (texture_ind<0) colors[texture_ind=l].assign(texture,false);
+ else colors[l].assign(colors[texture_ind],true);
+ CImg<tp>::vector(i0,i1,i2,i3,x0,y0,x1,y1,x2,y2,x3,y3).move_to(p);
+ } break;
+ }
+ }
+ return *this;
+ }
+
+ //! Generate a 3d elevation of the image instance.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param[out] colors The returned list of the 3d object colors.
+ \param elevation The input elevation map.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg");
+ CImgList<unsigned int> faces3d;
+ CImgList<unsigned char> colors3d;
+ const CImg<float> points3d = img.get_elevation3d(faces3d,colors3d,img.get_norm()*0.2);
+ CImg<unsigned char>().display_object3d("Elevation3d",points3d,faces3d,colors3d);
+ \endcode
+ \image html ref_elevation3d.jpg
+ **/
+ template<typename tf, typename tc, typename te>
+ CImg<floatT> get_elevation3d(CImgList<tf>& primitives, CImgList<tc>& colors, const CImg<te>& elevation) const {
+ if (!is_sameXY(elevation) || elevation._depth>1 || elevation._spectrum>1)
+ throw CImgArgumentException(_cimg_instance
+ "get_elevation3d(): Instance and specified elevation (%u,%u,%u,%u,%p) "
+ "have incompatible dimensions.",
+ cimg_instance,
+ elevation._width,elevation._height,elevation._depth,
+ elevation._spectrum,elevation._data);
+ if (is_empty()) return *this;
+ float m, M = (float)max_min(m);
+ if (M==m) ++M;
+ colors.assign();
+ const unsigned int size_x1 = _width - 1, size_y1 = _height - 1;
+ for (unsigned int y = 0; y<size_y1; ++y)
+ for (unsigned int x = 0; x<size_x1; ++x) {
+ const unsigned char
+ r = (unsigned char)(((*this)(x,y,0) - m)*255/(M-m)),
+ g = (unsigned char)(_spectrum>1?((*this)(x,y,1) - m)*255/(M-m):r),
+ b = (unsigned char)(_spectrum>2?((*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);
+ }
+
+ //! Generate the 3d projection planes of the image instance.
+ /**
+ \param[out] primitives Primitives data of the returned 3d object.
+ \param[out] colors Colors data of the returned 3d object.
+ \param x0 X-coordinate of the projection point.
+ \param y0 Y-coordinate of the projection point.
+ \param z0 Z-coordinate of the projection point.
+ \param normalize_colors Tells if the created textures have normalized colors.
+ **/
+ template<typename tf, typename tc>
+ CImg<floatT> get_projections3d(CImgList<tf>& primitives, CImgList<tc>& colors,
+ const unsigned int x0, const unsigned int y0, const unsigned int z0,
+ const bool normalize_colors=false) const {
+ float m = 0, M = 0, delta = 1;
+ if (normalize_colors) { m = (float)min_max(M); delta = 255/(m==M?1:M-m); }
+ const unsigned int
+ _x0 = (x0>=_width)?_width - 1:x0,
+ _y0 = (y0>=_height)?_height - 1:y0,
+ _z0 = (z0>=_depth)?_depth - 1:z0;
+ CImg<tc> img_xy, img_xz, img_yz;
+ if (normalize_colors) {
+ ((get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1)-=m)*=delta).move_to(img_xy);
+ ((get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_width,_depth,1,-100,-1).
+ move_to(img_xz);
+ ((get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1)-=m)*=delta).resize(_height,_depth,1,-100,-1).
+ move_to(img_yz);
+ } else {
+ get_crop(0,0,_z0,0,_width - 1,_height - 1,_z0,_spectrum - 1).move_to(img_xy);
+ get_crop(0,_y0,0,0,_width - 1,_y0,_depth - 1,_spectrum - 1).resize(_width,_depth,1,-100,-1).move_to(img_xz);
+ get_crop(_x0,0,0,0,_x0,_height - 1,_depth - 1,_spectrum - 1).resize(_height,_depth,1,-100,-1).move_to(img_yz);
+ }
+ CImg<floatT> points(12,3,1,1,
+ 0,_width - 1,_width - 1,0, 0,_width - 1,_width - 1,0, _x0,_x0,_x0,_x0,
+ 0,0,_height - 1,_height - 1, _y0,_y0,_y0,_y0, 0,_height - 1,_height - 1,0,
+ _z0,_z0,_z0,_z0, 0,0,_depth - 1,_depth - 1, 0,0,_depth - 1,_depth - 1);
+ primitives.assign();
+ CImg<tf>::vector(0,1,2,3,0,0,img_xy._width - 1,0,img_xy._width - 1,img_xy._height - 1,0,img_xy._height - 1).
+ move_to(primitives);
+ CImg<tf>::vector(4,5,6,7,0,0,img_xz._width - 1,0,img_xz._width - 1,img_xz._height - 1,0,img_xz._height - 1).
+ move_to(primitives);
+ CImg<tf>::vector(8,9,10,11,0,0,img_yz._width - 1,0,img_yz._width - 1,img_yz._height - 1,0,img_yz._height - 1).
+ move_to(primitives);
+ colors.assign();
+ img_xy.move_to(colors);
+ img_xz.move_to(colors);
+ img_yz.move_to(colors);
+ return points;
+ }
+
+ //! Generate a isoline of the image instance as a 3d object.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param isovalue The returned list of the 3d object colors.
+ \param size_x The number of subdivisions along the X-axis.
+ \param size_y The number of subdisivions along the Y-axis.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ const CImg<float> img("reference.jpg");
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = img.get_isoline3d(faces3d,100);
+ CImg<unsigned char>().display_object3d("Isoline3d",points3d,faces3d,colors3d);
+ \endcode
+ \image html ref_isoline3d.jpg
+ **/
+ template<typename tf>
+ CImg<floatT> get_isoline3d(CImgList<tf>& primitives, const float isovalue,
+ const int size_x=-100, const int size_y=-100) const {
+ if (_spectrum>1)
+ throw CImgInstanceException(_cimg_instance
+ "get_isoline3d(): Instance is not a scalar image.",
+ cimg_instance);
+ if (_depth>1)
+ throw CImgInstanceException(_cimg_instance
+ "get_isoline3d(): Instance is not a 2d image.",
+ cimg_instance);
+ primitives.assign();
+ if (is_empty()) return *this;
+ CImg<floatT> vertices;
+ if ((size_x==-100 && size_y==-100) || (size_x==width() && size_y==height())) {
+ const _functor2d_int func(*this);
+ vertices = isoline3d(primitives,func,isovalue,0,0,width() - 1.0f,height() - 1.0f,width(),height());
+ } else {
+ const _functor2d_float func(*this);
+ vertices = isoline3d(primitives,func,isovalue,0,0,width() - 1.0f,height() - 1.0f,size_x,size_y);
+ }
+ return vertices;
+ }
+
+ //! Generate an isosurface of the image instance as a 3d object.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param isovalue The returned list of the 3d object colors.
+ \param size_x Number of subdivisions along the X-axis.
+ \param size_y Number of subdisivions along the Y-axis.
+ \param size_z Number of subdisivions along the Z-axis.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ const CImg<float> img = CImg<unsigned char>("reference.jpg").resize(-100,-100,20);
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = img.get_isosurface3d(faces3d,100);
+ CImg<unsigned char>().display_object3d("Isosurface3d",points3d,faces3d,colors3d);
+ \endcode
+ \image html ref_isosurface3d.jpg
+ **/
+ template<typename tf>
+ CImg<floatT> get_isosurface3d(CImgList<tf>& primitives, const float isovalue,
+ const int size_x=-100, const int size_y=-100, const int size_z=-100) const {
+ if (_spectrum>1)
+ throw CImgInstanceException(_cimg_instance
+ "get_isosurface3d(): Instance is not a scalar image.",
+ cimg_instance);
+ primitives.assign();
+ if (is_empty()) return *this;
+ CImg<floatT> vertices;
+ if ((size_x==-100 && size_y==-100 && size_z==-100) || (size_x==width() && size_y==height() && size_z==depth())) {
+ const _functor3d_int func(*this);
+ vertices = isosurface3d(primitives,func,isovalue,0,0,0,width() - 1.0f,height() - 1.0f,depth() - 1.0f,
+ width(),height(),depth());
+ } else {
+ const _functor3d_float func(*this);
+ vertices = isosurface3d(primitives,func,isovalue,0,0,0,width() - 1.0f,height() - 1.0f,depth() - 1.0f,
+ size_x,size_y,size_z);
+ }
+ return vertices;
+ }
+
+ //! Compute 3d elevation of a function as a 3d object.
+ /**
+ \param[out] primitives Primitives data of the resulting 3d object.
+ \param func Elevation function. Is of type <tt>float (*func)(const float x,const float y)</tt>.
+ \param x0 X-coordinate of the starting point.
+ \param y0 Y-coordinate of the starting point.
+ \param x1 X-coordinate of the ending point.
+ \param y1 Y-coordinate of the ending point.
+ \param size_x Resolution of the function along the X-axis.
+ \param size_y Resolution of the function along the Y-axis.
+ **/
+ 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;
+ }
+
+ //! Compute 3d elevation of a function, as a 3d object \overloading.
+ 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 size_x=256, const int size_y=256) {
+ const _functor2d_expr func(expression);
+ return elevation3d(primitives,func,x0,y0,x1,y1,size_x,size_y);
+ }
+
+ //! Compute 0-isolines of a function, as a 3d object.
+ /**
+ \param[out] primitives Primitives data of the resulting 3d object.
+ \param func Elevation function. Is of type <tt>float (*func)(const float x,const float y)</tt>.
+ \param isovalue Isovalue to extract from function.
+ \param x0 X-coordinate of the starting point.
+ \param y0 Y-coordinate of the starting point.
+ \param x1 X-coordinate of the ending point.
+ \param y1 Y-coordinate of the ending point.
+ \param size_x Resolution of the function along the X-axis.
+ \param size_y Resolution of the function along the Y-axis.
+ \note Use the marching squares algorithm for extracting the isolines.
+ **/
+ 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 size_x=256, const int size_y=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)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)),
+ _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)),
+ 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?1U:0U) | (val1<isovalue?2U:0U) |
+ (val2<isovalue?4U:0U) | (val3<isovalue?8U:0U),
+ 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 = (unsigned int)*(segment++), p1 = (unsigned int)*(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';
+ }
+
+ //! Compute isolines of a function, as a 3d object \overloading.
+ 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 size_x=256, const int size_y=256) {
+ const _functor2d_expr func(expression);
+ return isoline3d(primitives,func,isovalue,x0,y0,x1,y1,size_x,size_y);
+ }
+
+ 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;
+ }
+
+ //! Compute isosurface of a function, as a 3d object.
+ /**
+ \param[out] primitives Primitives data of the resulting 3d object.
+ \param func Implicit function. Is of type <tt>float (*func)(const float x, const float y, const float z)</tt>.
+ \param isovalue Isovalue to extract.
+ \param x0 X-coordinate of the starting point.
+ \param y0 Y-coordinate of the starting point.
+ \param z0 Z-coordinate of the starting point.
+ \param x1 X-coordinate of the ending point.
+ \param y1 Y-coordinate of the ending point.
+ \param z1 Z-coordinate of the ending point.
+ \param size_x Resolution of the elevation function along the X-axis.
+ \param size_y Resolution of the elevation function along the Y-axis.
+ \param size_z Resolution of the elevation function along the Z-axis.
+ \note Use the marching cubes algorithm for extracting the isosurface.
+ **/
+ 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 const 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 const int triangles[256][16] = {
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
+ { 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 },
+ { 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 },
+ { 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 },
+ { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 },
+ { 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 },
+ { 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 },
+ { 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 },
+ { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 },
+ { 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 },
+ { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
+ { 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 },
+ { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 },
+ { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 },
+ { 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 },
+ { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 },
+ { 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 },
+ { 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
+ { 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 },
+ { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
+ { 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 },
+ { 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 },
+ { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 },
+ { 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
+ { 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 },
+ { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 },
+ { 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
+ { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 },
+ { 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 },
+ { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 },
+ { 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
+ { 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 },
+ { 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 },
+ { 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
+ { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 },
+ { 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 },
+ { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 },
+ { 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 },
+ { 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 },
+ { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 },
+ { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 },
+ { 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 },
+ { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 },
+ { 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 },
+ { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 },
+ { 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 },
+ { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 },
+ { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 },
+ { 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 },
+ { 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 },
+ { 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 },
+ { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
+ { 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 },
+ { 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 },
+ { 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 },
+ { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 },
+ { 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 },
+ { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 },
+ { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 },
+ { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 },
+ { 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 },
+ { 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 },
+ { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 },
+ { 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 },
+ { 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 },
+ { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 },
+ { 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 },
+ { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 },
+ { 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 },
+ { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 },
+ { 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 },
+ { 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 },
+ { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 },
+ { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 },
+ { 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 },
+ { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 },
+ { 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 },
+ { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 },
+ { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 },
+ { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 },
+ { 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 },
+ { 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 },
+ { 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 },
+ { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 },
+ { 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 },
+ { 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 },
+ { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 },
+ { 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 },
+ { 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 },
+ { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 },
+ { 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 },
+ { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 },
+ { 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 },
+ { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 },
+ { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 },
+ { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 },
+ { 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 },
+ { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 },
+ { 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 },
+ { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 },
+ { 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 },
+ { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 },
+ { 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 },
+ { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 },
+ { 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 },
+ { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 },
+ { 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 },
+ { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 },
+ { 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 },
+ { 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 },
+ { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 },
+ { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
+ { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }
+ };
+
+ const unsigned int
+ _nx = (unsigned int)(size_x>=0?size_x:cimg::round((x1-x0)*-size_x/100 + 1)),
+ _ny = (unsigned int)(size_y>=0?size_y:cimg::round((y1-y0)*-size_y/100 + 1)),
+ _nz = (unsigned int)(size_z>=0?size_z:cimg::round((z1-z0)*-size_z/100 + 1)),
+ nx = _nx?_nx:1,
+ ny = _ny?_ny:1,
+ nz = _nz?_nz:1,
+ nxm1 = nx - 1,
+ nym1 = ny - 1,
+ nzm1 = nz - 1;
+ primitives.assign();
+ if (!nxm1 || !nym1 || !nzm1) return CImg<floatT>();
+ const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1, dz = (z1 - z0)/nzm1;
+ CImgList<floatT> vertices;
+ CImg<intT> indices1(nx,ny,1,3,-1), indices2(indices1);
+ CImg<floatT> values1(nx,ny), values2(nx,ny);
+ float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0;
+
+ // Fill the first plane with function values
+ Y = y0;
+ cimg_forY(values1,y) {
+ X = x0;
+ cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=dx; }
+ Y+=dy;
+ }
+
+ // Run Marching Cubes algorithm
+ Z = z0; nZ = Z + dz;
+ for (unsigned int zi = 0; zi<nzm1; ++zi, Z = nZ, nZ+=dz) {
+ Y = y0; nY = Y + dy;
+ indices2.fill(-1);
+ for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y = nY, nY+=dy) {
+ X = x0; nX = X + dx;
+ for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X = nX, nX+=dx) {
+
+ // Determine cube configuration
+ const float
+ val0 = values1(xi,yi),
+ val1 = values1(nxi,yi),
+ val2 = values1(nxi,nyi),
+ val3 = values1(xi,nyi),
+ val4 = values2(xi,yi) = (float)func(X,Y,nZ),
+ val5 = values2(nxi,yi) = (float)func(nX,Y,nZ),
+ val6 = values2(nxi,nyi) = (float)func(nX,nY,nZ),
+ val7 = values2(xi,nyi) = (float)func(X,nY,nZ);
+
+ const unsigned int configuration =
+ (val0<isovalue?1U:0U) | (val1<isovalue?2U:0U) | (val2<isovalue?4U:0U) | (val3<isovalue?8U:0U) |
+ (val4<isovalue?16U:0U) | (val5<isovalue?32U:0U) | (val6<isovalue?64U:0U) | (val7<isovalue?128U:0U),
+ 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 = (unsigned int)*(triangle++),
+ p1 = (unsigned int)*(triangle++),
+ p2 = (unsigned int)*(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';
+ }
+
+ //! Compute isosurface of a function, as a 3d object \overloading.
+ 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() { mp->end(); delete mp; }
+ _functor2d_expr(const char *const expr):mp(0) {
+ mp = new _cimg_math_parser(expr,0,CImg<T>::const_empty(),0);
+ }
+ float operator()(const float x, const float y) const {
+ return (float)(*mp)(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() { mp->end(); delete mp; }
+ _functor3d_expr(const char *const expr):mp(0) {
+ mp = new _cimg_math_parser(expr,0,CImg<T>::const_empty(),0);
+ }
+ float operator()(const float x, const float y, const float z) const {
+ return (float)(*mp)(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);
+ }
+ };
+
+ //! Generate a 3d box object.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param size_x The width of the box (dimension along the X-axis).
+ \param size_y The height of the box (dimension along the Y-axis).
+ \param size_z The depth of the box (dimension along the Z-axis).
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = CImg<float>::box3d(faces3d,10,20,30);
+ CImg<unsigned char>().display_object3d("Box3d",points3d,faces3d);
+ \endcode
+ \image html ref_box3d.jpg
+ **/
+ template<typename tf>
+ static CImg<floatT> box3d(CImgList<tf>& primitives,
+ const float size_x=200, const float size_y=100, const float size_z=100) {
+ primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
+ return CImg<floatT>(8,3,1,1,
+ 0.,size_x,size_x, 0., 0.,size_x,size_x, 0.,
+ 0., 0.,size_y,size_y, 0., 0.,size_y,size_y,
+ 0., 0., 0., 0.,size_z,size_z,size_z,size_z);
+ }
+
+ //! Generate a 3d cone.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param radius The radius of the cone basis.
+ \param size_z The cone's height.
+ \param subdivisions The number of basis angular subdivisions.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = CImg<float>::cone3d(faces3d,50);
+ CImg<unsigned char>().display_object3d("Cone3d",points3d,faces3d);
+ \endcode
+ \image html ref_cone3d.jpg
+ **/
+ template<typename tf>
+ static CImg<floatT> cone3d(CImgList<tf>& primitives,
+ const float radius=50, const float size_z=100, const unsigned int subdivisions=24) {
+ primitives.assign();
+ if (!subdivisions) return CImg<floatT>();
+ CImgList<floatT> vertices(2,1,3,1,1,
+ 0.,0.,size_z,
+ 0.,0.,0.);
+ for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) {
+ const float a = (float)(angle*cimg::PI/180);
+ CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0).move_to(vertices);
+ }
+ const unsigned int nbr = vertices._width - 2;
+ for (unsigned int p = 0; p<nbr; ++p) {
+ const unsigned int curr = 2 + p, next = 2 + ((p + 1)%nbr);
+ CImg<tf>::vector(1,next,curr).move_to(primitives);
+ CImg<tf>::vector(0,curr,next).move_to(primitives);
+ }
+ return vertices>'x';
+ }
+
+ //! Generate a 3d cylinder.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param radius The radius of the cylinder basis.
+ \param size_z The cylinder's height.
+ \param subdivisions The number of basis angular subdivisions.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = CImg<float>::cylinder3d(faces3d,50);
+ CImg<unsigned char>().display_object3d("Cylinder3d",points3d,faces3d);
+ \endcode
+ \image html ref_cylinder3d.jpg
+ **/
+ template<typename tf>
+ static CImg<floatT> cylinder3d(CImgList<tf>& primitives,
+ const float radius=50, const float size_z=100, const unsigned int subdivisions=24) {
+ primitives.assign();
+ if (!subdivisions) return CImg<floatT>();
+ CImgList<floatT> vertices(2,1,3,1,1,
+ 0.,0.,0.,
+ 0.,0.,size_z);
+ for (float delta = 360.0f/subdivisions, angle = 0; angle<360; angle+=delta) {
+ const float a = (float)(angle*cimg::PI/180);
+ CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0.0f).move_to(vertices);
+ CImg<floatT>::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),size_z).move_to(vertices);
+ }
+ const unsigned int nbr = (vertices._width - 2)/2;
+ for (unsigned int p = 0; p<nbr; ++p) {
+ const unsigned int curr = 2 + 2*p, next = 2 + (2*((p + 1)%nbr));
+ CImg<tf>::vector(0,next,curr).move_to(primitives);
+ CImg<tf>::vector(1,curr + 1,next + 1).move_to(primitives);
+ CImg<tf>::vector(curr,next,next + 1,curr + 1).move_to(primitives);
+ }
+ return vertices>'x';
+ }
+
+ //! Generate a 3d torus.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param radius1 The large radius.
+ \param radius2 The small radius.
+ \param subdivisions1 The number of angular subdivisions for the large radius.
+ \param subdivisions2 The number of angular subdivisions for the small radius.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = CImg<float>::torus3d(faces3d,20,4);
+ CImg<unsigned char>().display_object3d("Torus3d",points3d,faces3d);
+ \endcode
+ \image html ref_torus3d.jpg
+ **/
+ template<typename tf>
+ static CImg<floatT> torus3d(CImgList<tf>& primitives,
+ const float radius1=100, const float radius2=30,
+ const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) {
+ primitives.assign();
+ if (!subdivisions1 || !subdivisions2) return CImg<floatT>();
+ CImgList<floatT> vertices;
+ for (unsigned int v = 0; v<subdivisions1; ++v) {
+ const float
+ beta = (float)(v*2*cimg::PI/subdivisions1),
+ xc = radius1*(float)std::cos(beta),
+ yc = radius1*(float)std::sin(beta);
+ for (unsigned int u = 0; u<subdivisions2; ++u) {
+ const float
+ alpha = (float)(u*2*cimg::PI/subdivisions2),
+ x = xc + radius2*(float)(std::cos(alpha)*std::cos(beta)),
+ y = yc + radius2*(float)(std::cos(alpha)*std::sin(beta)),
+ z = radius2*(float)std::sin(alpha);
+ CImg<floatT>::vector(x,y,z).move_to(vertices);
+ }
+ }
+ for (unsigned int vv = 0; vv<subdivisions1; ++vv) {
+ const unsigned int nv = (vv + 1)%subdivisions1;
+ for (unsigned int uu = 0; uu<subdivisions2; ++uu) {
+ const unsigned int nu = (uu + 1)%subdivisions2, svv = subdivisions2*vv, snv = subdivisions2*nv;
+ CImg<tf>::vector(svv + nu,svv + uu,snv + uu,snv + nu).move_to(primitives);
+ }
+ }
+ return vertices>'x';
+ }
+
+ //! Generate a 3d XY-plane.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param size_x The width of the plane (dimension along the X-axis).
+ \param size_y The height of the plane (dimensions along the Y-axis).
+ \param subdivisions_x The number of planar subdivisions along the X-axis.
+ \param subdivisions_y The number of planar subdivisions along the Y-axis.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = CImg<float>::plane3d(faces3d,100,50);
+ CImg<unsigned char>().display_object3d("Plane3d",points3d,faces3d);
+ \endcode
+ \image html ref_plane3d.jpg
+ **/
+ template<typename tf>
+ static CImg<floatT> plane3d(CImgList<tf>& primitives,
+ const float size_x=100, const float size_y=100,
+ const unsigned int subdivisions_x=10, const unsigned int subdivisions_y=10) {
+ primitives.assign();
+ if (!subdivisions_x || !subdivisions_y) return CImg<floatT>();
+ CImgList<floatT> vertices;
+ const unsigned int w = subdivisions_x + 1, h = subdivisions_y + 1;
+ const float fx = (float)size_x/w, fy = (float)size_y/h;
+ for (unsigned int y = 0; y<h; ++y) for (unsigned int x = 0; x<w; ++x)
+ CImg<floatT>::vector(fx*x,fy*y,0).move_to(vertices);
+ for (unsigned int y = 0; y<subdivisions_y; ++y) for (unsigned int x = 0; x<subdivisions_x; ++x) {
+ const int off1 = x + y*w, off2 = x + 1 + y*w, off3 = x + 1 + (y + 1)*w, off4 = x + (y + 1)*w;
+ CImg<tf>::vector(off1,off4,off3,off2).move_to(primitives);
+ }
+ return vertices>'x';
+ }
+
+ //! Generate a 3d sphere.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param radius The radius of the sphere (dimension along the X-axis).
+ \param subdivisions The number of recursive subdivisions from an initial icosahedron.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ CImgList<unsigned int> faces3d;
+ const CImg<float> points3d = CImg<float>::sphere3d(faces3d,100,4);
+ CImg<unsigned char>().display_object3d("Sphere3d",points3d,faces3d);
+ \endcode
+ \image html ref_sphere3d.jpg
+ **/
+ template<typename tf>
+ static CImg<floatT> sphere3d(CImgList<tf>& primitives,
+ const float radius=50, const unsigned int subdivisions=3) {
+
+ // Create initial icosahedron
+ primitives.assign();
+ const double tmp = (1 + std::sqrt(5.0f))/2, a = 1.0/std::sqrt(1 + tmp*tmp), b = tmp*a;
+ CImgList<floatT> vertices(12,1,3,1,1, b,a,0.0, -b,a,0.0, -b,-a,0.0, b,-a,0.0, a,0.0,b, a,0.0,-b,
+ -a,0.0,-b, -a,0.0,b, 0.0,b,a, 0.0,-b,a, 0.0,-b,-a, 0.0,b,-a);
+ primitives.assign(20,1,3,1,1, 4,8,7, 4,7,9, 5,6,11, 5,10,6, 0,4,3, 0,3,5, 2,7,1, 2,1,6,
+ 8,0,11, 8,11,1, 9,10,3, 9,2,10, 8,4,0, 11,0,5, 4,9,3,
+ 5,3,10, 7,8,1, 6,1,11, 7,2,9, 6,10,2);
+ // edge - length/2
+ float he = (float)a;
+
+ // Recurse subdivisions
+ for (unsigned int i = 0; i<subdivisions; ++i) {
+ const unsigned int L = primitives._width;
+ he/=2;
+ const float he2 = he*he;
+ for (unsigned int l = 0; l<L; ++l) {
+ const unsigned int
+ p0 = (unsigned int)primitives(0,0), p1 = (unsigned int)primitives(0,1), p2 = (unsigned int)primitives(0,2);
+ const float
+ x0 = vertices(p0,0), y0 = vertices(p0,1), z0 = vertices(p0,2),
+ x1 = vertices(p1,0), y1 = vertices(p1,1), z1 = vertices(p1,2),
+ x2 = vertices(p2,0), y2 = vertices(p2,1), z2 = vertices(p2,2),
+ tnx0 = (x0 + x1)/2, tny0 = (y0 + y1)/2, tnz0 = (z0 + z1)/2,
+ nn0 = cimg::hypot(tnx0,tny0,tnz0),
+ tnx1 = (x0 + x2)/2, tny1 = (y0 + y2)/2, tnz1 = (z0 + z2)/2,
+ nn1 = cimg::hypot(tnx1,tny1,tnz1),
+ tnx2 = (x1 + x2)/2, tny2 = (y1 + y2)/2, tnz2 = (z1 + z2)/2,
+ nn2 = cimg::hypot(tnx2,tny2,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;
+ }
+
+ //! Generate a 3d ellipsoid.
+ /**
+ \param[out] primitives The returned list of the 3d object primitives
+ (template type \e tf should be at least \e unsigned \e int).
+ \param tensor The tensor which gives the shape and size of the ellipsoid.
+ \param subdivisions The number of recursive subdivisions from an initial stretched icosahedron.
+ \return The N vertices (xi,yi,zi) of the 3d object as a Nx3 CImg<float> image (0<=i<=N - 1).
+ \par Example
+ \code
+ CImgList<unsigned int> faces3d;
+ const CImg<float> tensor = CImg<float>::diagonal(10,7,3),
+ points3d = CImg<float>::ellipsoid3d(faces3d,tensor,4);
+ CImg<unsigned char>().display_object3d("Ellipsoid3d",points3d,faces3d);
+ \endcode
+ \image html ref_ellipsoid3d.jpg
+ **/
+ template<typename tf, typename t>
+ static CImg<floatT> ellipsoid3d(CImgList<tf>& primitives,
+ const CImg<t>& tensor, const unsigned int subdivisions=3) {
+ primitives.assign();
+ if (!subdivisions) return CImg<floatT>();
+ CImg<floatT> S, V;
+ tensor.symmetric_eigen(S,V);
+ const float orient =
+ (V(0,1)*V(1,2) - V(0,2)*V(1,1))*V(2,0) +
+ (V(0,2)*V(1,0) - V(0,0)*V(1,2))*V(2,1) +
+ (V(0,0)*V(1,1) - V(0,1)*V(1,0))*V(2,2);
+ if (orient<0) { V(2,0) = -V(2,0); V(2,1) = -V(2,1); V(2,2) = -V(2,2); }
+ const float l0 = S[0], l1 = S[1], l2 = S[2];
+ CImg<floatT> vertices = sphere3d(primitives,1.0,subdivisions);
+ vertices.get_shared_row(0)*=l0;
+ vertices.get_shared_row(1)*=l1;
+ vertices.get_shared_row(2)*=l2;
+ return V*vertices;
+ }
+
+ //! Convert 3d object into a CImg3d representation.
+ /**
+ \param primitives Primitives data of the 3d object.
+ \param colors Colors data of the 3d object.
+ \param opacities Opacities data of the 3d object.
+ \param full_check Tells if full checking of the 3d object must be performed.
+ **/
+ template<typename tp, typename tc, typename to>
+ CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives,
+ const CImgList<tc>& colors,
+ const to& opacities,
+ const bool full_check=true) {
+ return get_object3dtoCImg3d(primitives,colors,opacities,full_check).move_to(*this);
+ }
+
+ //! Convert 3d object into a CImg3d representation \overloading.
+ template<typename tp, typename tc>
+ CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives,
+ const CImgList<tc>& colors,
+ const bool full_check=true) {
+ return get_object3dtoCImg3d(primitives,colors,full_check).move_to(*this);
+ }
+
+ //! Convert 3d object into a CImg3d representation \overloading.
+ template<typename tp>
+ CImg<T>& object3dtoCImg3d(const CImgList<tp>& primitives,
+ const bool full_check=true) {
+ return get_object3dtoCImg3d(primitives,full_check).move_to(*this);
+ }
+
+ //! Convert 3d object into a CImg3d representation \overloading.
+ CImg<T>& object3dtoCImg3d(const bool full_check=true) {
+ return get_object3dtoCImg3d(full_check).move_to(*this);
+ }
+
+ //! Convert 3d object into a CImg3d representation \newinstance.
+ template<typename tp, typename tc, typename to>
+ CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives,
+ const CImgList<tc>& colors,
+ const to& opacities,
+ const bool full_check=true) const {
+ CImg<charT> error_message(1024);
+ if (!is_object3d(primitives,colors,opacities,full_check,error_message))
+ throw CImgInstanceException(_cimg_instance
+ "object3dtoCImg3d(): Invalid specified 3d object (%u,%u) (%s).",
+ cimg_instance,_width,primitives._width,error_message.data());
+ CImg<floatT> res(1,_size_object3dtoCImg3d(primitives,colors,opacities));
+ float *ptrd = res._data;
+
+ // Put magick number.
+ *(ptrd++) = 'C' + 0.5f; *(ptrd++) = 'I' + 0.5f; *(ptrd++) = 'm' + 0.5f;
+ *(ptrd++) = 'g' + 0.5f; *(ptrd++) = '3' + 0.5f; *(ptrd++) = 'd' + 0.5f;
+
+ // Put number of vertices and primitives.
+ *(ptrd++) = cimg::uint2float(_width);
+ *(ptrd++) = cimg::uint2float(primitives._width);
+
+ // Put vertex data.
+ if (is_empty() || !primitives) return res;
+ const T *ptrx = data(0,0), *ptry = data(0,1), *ptrz = data(0,2);
+ cimg_forX(*this,p) {
+ *(ptrd++) = (float)*(ptrx++);
+ *(ptrd++) = (float)*(ptry++);
+ *(ptrd++) = (float)*(ptrz++);
+ }
+
+ // Put primitive data.
+ cimglist_for(primitives,p) {
+ *(ptrd++) = (float)primitives[p].size();
+ const tp *ptrp = primitives[p]._data;
+ cimg_foroff(primitives[p],i) *(ptrd++) = cimg::uint2float((unsigned int)*(ptrp++));
+ }
+
+ // Put color/texture data.
+ const unsigned int csiz = std::min(colors._width,primitives._width);
+ for (int c = 0; c<(int)csiz; ++c) {
+ const CImg<tc>& color = colors[c];
+ const tc *ptrc = color._data;
+ if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; }
+ else {
+ *(ptrd++) = -128.0f;
+ int shared_ind = -1;
+ if (color.is_shared()) for (int i = 0; i<c; ++i) if (ptrc==colors[i]._data) { shared_ind = i; break; }
+ if (shared_ind<0) {
+ *(ptrd++) = (float)color._width;
+ *(ptrd++) = (float)color._height;
+ *(ptrd++) = (float)color._spectrum;
+ cimg_foroff(color,l) *(ptrd++) = (float)*(ptrc++);
+ } else {
+ *(ptrd++) = (float)shared_ind;
+ *(ptrd++) = 0;
+ *(ptrd++) = 0;
+ }
+ }
+ }
+ const int csiz2 = primitives.width() - colors.width();
+ for (int c = 0; c<csiz2; ++c) { *(ptrd++) = 200.0f; *(ptrd++) = 200.0f; *(ptrd++) = 200.0f; }
+
+ // Put opacity data.
+ ptrd = _object3dtoCImg3d(opacities,ptrd);
+ const float *ptre = res.end();
+ while (ptrd<ptre) *(ptrd++) = 1.0f;
+ return res;
+ }
+
+ template<typename to>
+ float* _object3dtoCImg3d(const CImgList<to>& opacities, float *ptrd) const {
+ cimglist_for(opacities,o) {
+ const CImg<to>& opacity = opacities[o];
+ const to *ptro = opacity._data;
+ if (opacity.size()==1) *(ptrd++) = (float)*ptro;
+ else {
+ *(ptrd++) = -128.0f;
+ int shared_ind = -1;
+ if (opacity.is_shared()) for (int i = 0; i<o; ++i) if (ptro==opacities[i]._data) { shared_ind = i; break; }
+ if (shared_ind<0) {
+ *(ptrd++) = (float)opacity._width;
+ *(ptrd++) = (float)opacity._height;
+ *(ptrd++) = (float)opacity._spectrum;
+ cimg_foroff(opacity,l) *(ptrd++) = (float)*(ptro++);
+ } else {
+ *(ptrd++) = (float)shared_ind;
+ *(ptrd++) = 0;
+ *(ptrd++) = 0;
+ }
+ }
+ }
+ return ptrd;
+ }
+
+ template<typename to>
+ float* _object3dtoCImg3d(const CImg<to>& opacities, float *ptrd) const {
+ const to *ptro = opacities._data;
+ cimg_foroff(opacities,o) *(ptrd++) = (float)*(ptro++);
+ return ptrd;
+ }
+
+ template<typename tp, typename tc, typename to>
+ unsigned int _size_object3dtoCImg3d(const CImgList<tp>& primitives,
+ const CImgList<tc>& colors,
+ const CImgList<to>& opacities) const {
+ unsigned int siz = 8U + 3*_width;
+ cimglist_for(primitives,p) siz+=primitives[p].size() + 1;
+ for (int c = std::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 = 8U + 3*_width;
+ cimglist_for(primitives,p) siz+=primitives[p].size() + 1;
+ for (int c = std::min(primitives.width(),colors.width()) - 1; c>=0; --c) {
+ const unsigned int csiz = colors[c].size(); siz+=(csiz!=3)?4 + csiz:3;
+ }
+ if (colors._width<primitives._width) siz+=3*(primitives._width - colors._width);
+ siz+=primitives.size();
+ cimg::unused(opacities);
+ return siz;
+ }
+
+ //! Convert 3d object into a CImg3d representation \overloading.
+ template<typename tp, typename tc>
+ CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives,
+ const CImgList<tc>& colors,
+ const bool full_check=true) const {
+ CImgList<T> opacities;
+ return get_object3dtoCImg3d(primitives,colors,opacities,full_check);
+ }
+
+ //! Convert 3d object into a CImg3d representation \overloading.
+ template<typename tp>
+ CImg<floatT> get_object3dtoCImg3d(const CImgList<tp>& primitives,
+ const bool full_check=true) const {
+ CImgList<T> colors, opacities;
+ return get_object3dtoCImg3d(primitives,colors,opacities,full_check);
+ }
+
+ //! Convert 3d object into a CImg3d representation \overloading.
+ CImg<floatT> get_object3dtoCImg3d(const bool full_check=true) 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,full_check);
+ }
+
+ //! Convert CImg3d representation into a 3d object.
+ /**
+ \param[out] primitives Primitives data of the 3d object.
+ \param[out] colors Colors data of the 3d object.
+ \param[out] opacities Opacities data of the 3d object.
+ \param full_check Tells if full checking of the 3d object must be performed.
+ **/
+ template<typename tp, typename tc, typename to>
+ CImg<T>& CImg3dtoobject3d(CImgList<tp>& primitives,
+ CImgList<tc>& colors,
+ CImgList<to>& opacities,
+ const bool full_check=true) {
+ return get_CImg3dtoobject3d(primitives,colors,opacities,full_check).move_to(*this);
+ }
+
+ //! Convert CImg3d representation into a 3d object \newinstance.
+ template<typename tp, typename tc, typename to>
+ CImg<T> get_CImg3dtoobject3d(CImgList<tp>& primitives,
+ CImgList<tc>& colors,
+ CImgList<to>& opacities,
+ const bool full_check=true) const {
+ CImg<charT> error_message(1024);
+ if (!is_CImg3d(full_check,error_message))
+ throw CImgInstanceException(_cimg_instance
+ "CImg3dtoobject3d(): image instance is not a CImg3d (%s).",
+ cimg_instance,error_message.data());
+ const T *ptrs = _data + 6;
+ const unsigned int
+ nb_points = cimg::float2uint((float)*(ptrs++)),
+ nb_primitives = cimg::float2uint((float)*(ptrs++));
+ const CImg<T> points = CImg<T>(ptrs,3,nb_points,1,1,true).get_transpose();
+ ptrs+=3*nb_points;
+ primitives.assign(nb_primitives);
+ cimglist_for(primitives,p) {
+ const unsigned int nb_inds = (unsigned int)*(ptrs++);
+ primitives[p].assign(1,nb_inds);
+ tp *ptrp = primitives[p]._data;
+ for (unsigned int i = 0; i<nb_inds; ++i) *(ptrp++) = (tp)cimg::float2uint((float)*(ptrs++));
+ }
+ colors.assign(nb_primitives);
+ cimglist_for(colors,c) {
+ if (*ptrs==(T)-128) {
+ ++ptrs;
+ const unsigned int w = (unsigned int)*(ptrs++), h = (unsigned int)*(ptrs++), s = (unsigned int)*(ptrs++);
+ if (!h && !s) colors[c].assign(colors[w],true);
+ else { colors[c].assign(ptrs,w,h,1,s,false); ptrs+=w*h*s; }
+ } else { colors[c].assign(ptrs,1,1,1,3,false); ptrs+=3; }
+ }
+ opacities.assign(nb_primitives);
+ cimglist_for(opacities,o) {
+ if (*ptrs==(T)-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;
+ }
+
+ //@}
+ //---------------------------
+ //
+ //! \name Drawing Functions
+ //@{
+ //---------------------------
+
+#define cimg_init_scanline(color,opacity) \
+ const float _sc_nopacity = cimg::abs((float)opacity), _sc_copacity = 1 - std::max((float)opacity,0.0f); \
+ const ulongT _sc_whd = (ulongT)_width*_height*_depth
+
+#define cimg_draw_scanline(x0,x1,y,color,opacity,brightness) \
+ _draw_scanline(x0,x1,y,color,opacity,brightness,_sc_nopacity,_sc_copacity,_sc_whd)
+
+ // [internal] 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,
+ const float brightness,
+ const float nopacity, const float copacity, const ulongT whd) {
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const int nx0 = x0>0?x0:0, nx1 = x1<width()?x1:width() - 1, dx = nx1 - nx0;
+ if (dx>=0) {
+ const tc *col = color;
+ const ulongT 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 Tfloat val = *(col++)*nopacity;
+ for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; }
+ ptrd+=off;
+ }
+ } else if (brightness<=1) { // Brightness<1
+ cimg_forC(*this,c) {
+ const Tfloat val = *(col++)*brightness*nopacity;
+ for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; }
+ ptrd+=off;
+ }
+ } else { // Brightness>1
+ cimg_forC(*this,c) {
+ const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*maxval)*nopacity;
+ for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; }
+ ptrd+=off;
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 3d point.
+ /**
+ \param x0 X-coordinate of the point.
+ \param y0 Y-coordinate of the point.
+ \param z0 Z-coordinate of the point.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \note
+ - 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 int z0,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_point(): Specified color is (null).",
+ cimg_instance);
+ if (x0>=0 && y0>=0 && z0>=0 && x0<width() && y0<height() && z0<depth()) {
+ const ulongT whd = (ulongT)_width*_height*_depth;
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ 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 2d point \simplification.
+ 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 points cloud.
+ /**
+ \param points Image of vertices coordinates.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ 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 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.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch Tells if a reinitialization of the hash state must be done.
+ \note
+ - Line routine uses Bresenham's algorithm.
+ - Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern.
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0);
+ const unsigned char color[] = { 255,128,64 };
+ img.draw_line(40,40,80,70,color);
+ \endcode
+ **/
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0,
+ const int x1, const int y1,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Specified color is (null).",
+ cimg_instance);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ if (xright<0 || xleft>=width()) return *this;
+ if (xleft<0) { yleft-=(int)((float)xleft*((float)yright - yleft)/((float)xright - xleft)); xleft = 0; }
+ if (xright>=width()) {
+ yright-=(int)(((float)xright - width())*((float)yright - yleft)/((float)xright - xleft));
+ xright = width() - 1;
+ }
+ if (ydown<0 || yup>=height()) return *this;
+ if (yup<0) { xup-=(int)((float)yup*((float)xdown - xup)/((float)ydown - yup)); yup = 0; }
+ if (ydown>=height()) {
+ xdown-=(int)(((float)ydown - height())*((float)xdown - xup)/((float)ydown - yup));
+ ydown = height() - 1;
+ }
+ T *ptrd0 = data(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const longT
+ offx = (longT)(nx0<nx1?1:-1)*(steep?width():1),
+ offy = (longT)(ny0<ny1?1:-1)*(steep?1:width());
+ const ulongT wh = (ulongT)_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 - std::max(opacity,0.0f);
+ 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 line, with z-buffering.
+ /**
+ \param zbuffer Zbuffer image.
+ \param x0 X-coordinate of the starting point.
+ \param y0 Y-coordinate of the starting point.
+ \param z0 Z-coordinate of the starting point
+ \param x1 X-coordinate of the ending point.
+ \param y1 Y-coordinate of the ending point.
+ \param z1 Z-coordinate of the ending point.
+ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch Tells if a reinitialization of the hash state must be done.
+ **/
+ template<typename tz,typename tc>
+ CImg<T>& draw_line(CImg<tz>& zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Specified color is (null).",
+ cimg_instance);
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have "
+ "different dimensions.",
+ cimg_instance,
+ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ tzfloat
+ Z0 = 1/(tzfloat)z0, Z1 = 1/(tzfloat)z1, nz0 = Z0, nz1 = Z1, dz = Z1 - Z0,
+ &zleft = xdir?nz0:nz1,
+ &zright = xdir?nz1:nz0,
+ &zup = ydir?nz0:nz1,
+ &zdown = ydir?nz1:nz0;
+ if (xright<0 || xleft>=width()) return *this;
+ if (xleft<0) {
+ const float D = (float)xright - xleft;
+ yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+ zleft-=(tzfloat)xleft*(zright - zleft)/D;
+ xleft = 0;
+ }
+ if (xright>=width()) {
+ const float d = (float)xright - width(), D = (float)xright - xleft;
+ yright-=(int)(d*((float)yright - yleft)/D);
+ zright-=(tzfloat)d*(zright - zleft)/D;
+ xright = width() - 1;
+ }
+ if (ydown<0 || yup>=height()) return *this;
+ if (yup<0) {
+ const float D = (float)ydown - yup;
+ xup-=(int)((float)yup*((float)xdown - xup)/D);
+ zup-=(tzfloat)yup*(zdown - zup)/D;
+ yup = 0;
+ }
+ if (ydown>=height()) {
+ const float d = (float)ydown - height(), D = (float)ydown - yup;
+ xdown-=(int)(d*((float)xdown - xup)/D);
+ zdown-=(tzfloat)d*(zdown - zup)/D;
+ ydown = height() - 1;
+ }
+ T *ptrd0 = data(nx0,ny0);
+ tz *ptrz = zbuffer.data(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const longT
+ offx = (longT)(nx0<nx1?1:-1)*(steep?width():1),
+ offy = (longT)(ny0<ny1?1:-1)*(steep?1:width());
+ const ulongT
+ wh = (ulongT)_width*_height,
+ ndx = (ulongT)(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 - std::max(opacity,0.0f);
+ 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 line.
+ /**
+ \param x0 X-coordinate of the starting point.
+ \param y0 Y-coordinate of the starting point.
+ \param z0 Z-coordinate of the starting point
+ \param x1 X-coordinate of the ending point.
+ \param y1 Y-coordinate of the ending point.
+ \param z1 Z-coordinate of the ending point.
+ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch Tells if a reinitialization of the hash state must be done.
+ **/
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Specified color is (null).",
+ cimg_instance);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ int nx0 = x0, ny0 = y0, nz0 = z0, nx1 = x1, ny1 = y1, nz1 = z1;
+ if (nx0>nx1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+ if (nx1<0 || nx0>=width()) return *this;
+ if (nx0<0) {
+ const float D = 1.0f + nx1 - nx0;
+ ny0-=(int)((float)nx0*(1.0f + ny1 - ny0)/D);
+ nz0-=(int)((float)nx0*(1.0f + nz1 - nz0)/D);
+ nx0 = 0;
+ }
+ if (nx1>=width()) {
+ const float d = (float)nx1 - width(), D = 1.0f + nx1 - nx0;
+ ny1+=(int)(d*(1.0f + ny0 - ny1)/D);
+ nz1+=(int)(d*(1.0f + nz0 - nz1)/D);
+ nx1 = width() - 1;
+ }
+ if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+ if (ny1<0 || ny0>=height()) return *this;
+ if (ny0<0) {
+ const float D = 1.0f + ny1 - ny0;
+ nx0-=(int)((float)ny0*(1.0f + nx1 - nx0)/D);
+ nz0-=(int)((float)ny0*(1.0f + nz1 - nz0)/D);
+ ny0 = 0;
+ }
+ if (ny1>=height()) {
+ const float d = (float)ny1 - height(), D = 1.0f + ny1 - ny0;
+ nx1+=(int)(d*(1.0f + nx0 - nx1)/D);
+ nz1+=(int)(d*(1.0f + nz0 - nz1)/D);
+ ny1 = height() - 1;
+ }
+ if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
+ if (nz1<0 || nz0>=depth()) return *this;
+ if (nz0<0) {
+ const float D = 1.0f + nz1 - nz0;
+ nx0-=(int)((float)nz0*(1.0f + nx1 - nx0)/D);
+ ny0-=(int)((float)nz0*(1.0f + ny1 - ny0)/D);
+ nz0 = 0;
+ }
+ if (nz1>=depth()) {
+ const float d = (float)nz1 - depth(), D = 1.0f + nz1 - nz0;
+ nx1+=(int)(d*(1.0f + nx0 - nx1)/D);
+ ny1+=(int)(d*(1.0f + ny0 - ny1)/D);
+ nz1 = depth() - 1;
+ }
+ const unsigned int dmax = (unsigned int)cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0);
+ const ulongT whd = (ulongT)_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 - std::max(opacity,0.0f);
+ 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 textured 2d 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.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch Tells if the hash variable must be reinitialized.
+ \note
+ - Line routine uses the well known Bresenham's algorithm.
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0), texture("texture256x256.ppm");
+ const unsigned char color[] = { 255,128,64 };
+ img.draw_line(40,40,80,70,texture,0,0,255,255);
+ \endcode
+ **/
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0,
+ const int x1, const int y1,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ dtx = tx1-tx0, dty = ty1-ty0,
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ tnx0 = tx0, tnx1 = tx1, tny0 = ty0, tny1 = ty1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1, &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1, &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0,
+ &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+ if (xright<0 || xleft>=width()) return *this;
+ if (xleft<0) {
+ const float D = (float)xright - xleft;
+ yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+ txleft-=(int)((float)xleft*((float)txright - txleft)/D);
+ tyleft-=(int)((float)xleft*((float)tyright - tyleft)/D);
+ xleft = 0;
+ }
+ if (xright>=width()) {
+ const float d = (float)xright - width(), D = (float)xright - xleft;
+ yright-=(int)(d*((float)yright - yleft)/D);
+ txright-=(int)(d*((float)txright - txleft)/D);
+ tyright-=(int)(d*((float)tyright - tyleft)/D);
+ xright = width() - 1;
+ }
+ if (ydown<0 || yup>=height()) return *this;
+ if (yup<0) {
+ const float D = (float)ydown - yup;
+ xup-=(int)((float)yup*((float)xdown - xup)/D);
+ txup-=(int)((float)yup*((float)txdown - txup)/D);
+ tyup-=(int)((float)yup*((float)tydown - tyup)/D);
+ yup = 0;
+ }
+ if (ydown>=height()) {
+ const float d = (float)ydown - height(), D = (float)ydown - yup;
+ xdown-=(int)(d*((float)xdown - xup)/D);
+ txdown-=(int)(d*((float)txdown - txup)/D);
+ tydown-=(int)(d*((float)tydown - tyup)/D);
+ ydown = height() - 1;
+ }
+ T *ptrd0 = data(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const longT
+ offx = (longT)(nx0<nx1?1:-1)*(steep?width():1),
+ offy = (longT)(ny0<ny1?1:-1)*(steep?1:width()),
+ ndx = (longT)(dx>0?dx:1);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height;
+
+ 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;
+ const tc *col = &texture._atXY(tx,ty);
+ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
+ }
+ 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;
+ const tc *col = &texture._atXY(tx,ty);
+ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
+ T *ptrd = ptrd0;
+ if (pattern&hatch) {
+ const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
+ const tc *col = &texture._atXY(tx,ty);
+ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
+ }
+ 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;
+ const tc *col = &texture._atXY(tx,ty);
+ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a textured 2d line, with perspective correction.
+ /**
+ \param x0 X-coordinate of the starting point.
+ \param y0 Y-coordinate of the starting point.
+ \param z0 Z-coordinate of the starting point
+ \param x1 X-coordinate of the ending point.
+ \param y1 Y-coordinate of the ending point.
+ \param z1 Z-coordinate of the ending 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.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch Tells if the hash variable must be reinitialized.
+ **/
+ template<typename tc>
+ CImg<T>& draw_line(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ if (is_empty() && z0<=0 && z1<=0) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (is_overlapped(texture))
+ return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ float
+ Tx0 = tx0/z0, Tx1 = tx1/z1,
+ Ty0 = ty0/z0, Ty1 = ty1/z1,
+ Z0 = 1/z0, Z1 = 1/z1,
+ dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
+ tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
+ &zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
+ &zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+ &zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
+ &zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+ if (xright<0 || xleft>=width()) return *this;
+ if (xleft<0) {
+ const float D = (float)xright - xleft;
+ yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+ zleft-=(float)xleft*(zright - zleft)/D;
+ txleft-=(float)xleft*(txright - txleft)/D;
+ tyleft-=(float)xleft*(tyright - tyleft)/D;
+ xleft = 0;
+ }
+ if (xright>=width()) {
+ const float d = (float)xright - width(), D = (float)xright - xleft;
+ yright-=(int)(d*((float)yright - yleft)/D);
+ zright-=d*(zright - zleft)/D;
+ txright-=d*(txright - txleft)/D;
+ tyright-=d*(tyright - tyleft)/D;
+ xright = width() - 1;
+ }
+ if (ydown<0 || yup>=height()) return *this;
+ if (yup<0) {
+ const float D = (float)ydown - yup;
+ xup-=(int)((float)yup*((float)xdown - xup)/D);
+ zup-=(float)yup*(zdown - zup)/D;
+ txup-=(float)yup*(txdown - txup)/D;
+ tyup-=(float)yup*(tydown - tyup)/D;
+ yup = 0;
+ }
+ if (ydown>=height()) {
+ const float d = (float)ydown - height(), D = (float)ydown - yup;
+ xdown-=(int)(d*((float)xdown - xup)/D);
+ zdown-=d*(zdown - zup)/D;
+ txdown-=d*(txdown - txup)/D;
+ tydown-=d*(tydown - tyup)/D;
+ ydown = height() - 1;
+ }
+ T *ptrd0 = data(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const longT
+ offx = (longT)(nx0<nx1?1:-1)*(steep?width():1),
+ offy = (longT)(ny0<ny1?1:-1)*(steep?1:width()),
+ ndx = (longT)(dx>0?dx:1);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height;
+
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
+ }
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
+ }
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
+ ptrd0+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a textured 2d line, with perspective correction and z-buffering.
+ /**
+ \param zbuffer Z-buffer image.
+ \param x0 X-coordinate of the starting point.
+ \param y0 Y-coordinate of the starting point.
+ \param z0 Z-coordinate of the starting point
+ \param x1 X-coordinate of the ending point.
+ \param y1 Y-coordinate of the ending point.
+ \param z1 Z-coordinate of the ending 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.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch Tells if the hash variable must be reinitialized.
+ **/
+ template<typename tz, typename tc>
+ CImg<T>& draw_line(CImg<tz>& zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const float opacity=1,
+ const unsigned int pattern=~0U, const bool init_hatch=true) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0) return *this;
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have "
+ "different dimensions.",
+ cimg_instance,
+ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (is_overlapped(texture))
+ return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
+ static unsigned int hatch = ~0U - (~0U>>1);
+ if (init_hatch) hatch = ~0U - (~0U>>1);
+ const bool xdir = x0<x1, ydir = y0<y1;
+ int
+ nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
+ &xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
+ &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
+ &xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
+ &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
+ float
+ Tx0 = tx0/z0, Tx1 = tx1/z1,
+ Ty0 = ty0/z0, Ty1 = ty1/z1,
+ dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
+ tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1,
+ &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
+ &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
+ &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
+ &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
+ tzfloat
+ Z0 = 1/(tzfloat)z0, Z1 = 1/(tzfloat)z1,
+ dz = Z1 - Z0, nz0 = Z0, nz1 = Z1,
+ &zleft = xdir?nz0:nz1,
+ &zright = xdir?nz1:nz0,
+ &zup = ydir?nz0:nz1,
+ &zdown = ydir?nz1:nz0;
+ if (xright<0 || xleft>=width()) return *this;
+ if (xleft<0) {
+ const float D = (float)xright - xleft;
+ yleft-=(int)((float)xleft*((float)yright - yleft)/D);
+ zleft-=(float)xleft*(zright - zleft)/D;
+ txleft-=(float)xleft*(txright - txleft)/D;
+ tyleft-=(float)xleft*(tyright - tyleft)/D;
+ xleft = 0;
+ }
+ if (xright>=width()) {
+ const float d = (float)xright - width(), D = (float)xright - xleft;
+ yright-=(int)(d*((float)yright - yleft)/D);
+ zright-=d*(zright - zleft)/D;
+ txright-=d*(txright - txleft)/D;
+ tyright-=d*(tyright - tyleft)/D;
+ xright = width() - 1;
+ }
+ if (ydown<0 || yup>=height()) return *this;
+ if (yup<0) {
+ const float D = (float)ydown - yup;
+ xup-=(int)((float)yup*((float)xdown - xup)/D);
+ zup-=yup*(zdown - zup)/D;
+ txup-=yup*(txdown - txup)/D;
+ tyup-=yup*(tydown - tyup)/D;
+ yup = 0;
+ }
+ if (ydown>=height()) {
+ const float d = (float)ydown - height(), D = (float)ydown - yup;
+ xdown-=(int)(d*((float)xdown - xup)/D);
+ zdown-=d*(zdown - zup)/D;
+ txdown-=d*(txdown - txup)/D;
+ tydown-=d*(tydown - tyup)/D;
+ ydown = height() - 1;
+ }
+ T *ptrd0 = data(nx0,ny0);
+ tz *ptrz = zbuffer.data(nx0,ny0);
+ int dx = xright - xleft, dy = ydown - yup;
+ const bool steep = dy>dx;
+ if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
+ const longT
+ offx = (longT)(nx0<nx1?1:-1)*(steep?width():1),
+ offy = (longT)(ny0<ny1?1:-1)*(steep?1:width()),
+ ndx = (longT)(dx>0?dx:1);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height;
+
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
+ }
+ }
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)*col; ptrd+=whd; col+=twh; }
+ }
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ }
+ } else {
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
+ }
+ }
+ 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;
+ const tc *col = &texture._atXY((int)(tx/z),(int)(ty/z));
+ T *ptrd = ptrd0;
+ cimg_forC(*this,c) { *ptrd = (T)(nopacity**col + *ptrd*copacity); ptrd+=whd; col+=twh; }
+ }
+ ptrd0+=offx; ptrz+=offx;
+ if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a set of consecutive lines.
+ /**
+ \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.
+ \param pattern An integer whose bits describe the line pattern.
+ \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.
+ **/
+ 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 2d arrow.
+ /**
+ \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.
+ \param length Length of the arrow head. If negative, describes a percentage of the arrow length.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the line pattern.
+ **/
+ 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 2d spline.
+ /**
+ \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.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch If \c true, init hatch motif.
+ \note
+ - The curve is a 2d cubic Bezier spline, from the set of specified starting/ending points
+ and corresponding velocity vectors.
+ - The spline is drawn as a serie of connected segments. The \p precision parameter sets the
+ average number of pixels in each drawn segment.
+ - A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya),
+ (\p xb,\p yb), (\p x1,\p y1) } where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point
+ and (\p xa,\p ya), (\p xb,\p yb) are two
+ \e control points.
+ The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from
+ the control points as
+ \p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb).
+ \par Example:
+ \code
+ CImg<unsigned char> img(100,100,1,3,0);
+ const unsigned char color[] = { 255,255,255 };
+ img.draw_spline(30,30,0,100,90,40,0,-100,color);
+ \endcode
+ **/
+ template<typename tc>
+ CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
+ const int x1, const int y1, const float u1, const float v1,
+ const tc *const color, const float opacity=1,
+ const float precision=0.25, const unsigned int pattern=~0U,
+ const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_spline(): Specified color is (null).",
+ cimg_instance);
+ if (x0==x1 && y0==y1) return draw_point(x0,y0,color,opacity);
+ bool ninit_hatch = init_hatch;
+ const float
+ ax = u0 + u1 + 2*(x0 - x1),
+ bx = 3*(x1 - x0) - 2*u0 - u1,
+ ay = v0 + v1 + 2*(y0 - y1),
+ by = 3*(y1 - y0) - 2*v0 - v1,
+ _precision = 1/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1));
+ int ox = x0, oy = y0;
+ for (float t = 0; t<1; t+=_precision) {
+ const float t2 = t*t, t3 = t2*t;
+ const int
+ nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
+ ny = (int)(ay*t3 + by*t2 + v0*t + y0);
+ draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = nx; oy = ny;
+ }
+ return draw_line(ox,oy,x1,y1,color,opacity,pattern,false);
+ }
+
+ //! Draw a 3d spline \overloading.
+ /**
+ \note
+ - Similar to CImg::draw_spline() for a 3d spline in a volumetric image.
+ **/
+ template<typename tc>
+ CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
+ const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
+ const tc *const color, const float opacity=1,
+ const float precision=4, const unsigned int pattern=~0U,
+ const bool init_hatch=true) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_spline(): Specified color is (null).",
+ cimg_instance);
+ if (x0==x1 && y0==y1 && z0==z1) return draw_point(x0,y0,z0,color,opacity);
+ bool ninit_hatch = init_hatch;
+ const float
+ ax = u0 + u1 + 2*(x0 - x1),
+ bx = 3*(x1 - x0) - 2*u0 - u1,
+ ay = v0 + v1 + 2*(y0 - y1),
+ by = 3*(y1 - y0) - 2*v0 - v1,
+ az = w0 + w1 + 2*(z0 - z1),
+ bz = 3*(z1 - z0) - 2*w0 - w1,
+ _precision = 1/(cimg::hypot((float)x0 - x1,(float)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 textured 2d spline.
+ /**
+ \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.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the line pattern.
+ \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_spline(): 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<=0?0:x0>=texture.width()?texture.width() - 1:x0,
+ y0<=0?0:y0>=texture.height()?texture.height() - 1: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/(cimg::hypot((float)x0 - x1,(float)y0 - y1)*(precision>0?precision:1));
+ int ox = x0, oy = y0, otx = tx0, oty = ty0;
+ for (float t1 = 0; t1<1; t1+=_precision) {
+ const float t2 = t1*t1, t3 = t2*t1;
+ const int
+ nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0),
+ ny = (int)(ay*t3 + by*t2 + v0*t1 + y0),
+ ntx = tx0 + (int)((tx1 - tx0)*t1),
+ nty = ty0 + (int)((ty1 - ty0)*t1);
+ draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch);
+ ninit_hatch = false;
+ ox = nx; oy = ny; otx = ntx; oty = nty;
+ }
+ return draw_line(ox,oy,x1,y1,texture,otx,oty,tx1,ty1,opacity,pattern,false);
+ }
+
+ //! Draw a set of consecutive splines.
+ /**
+ \param points Vertices data.
+ \param tangents Tangents data.
+ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param is_closed_set Tells if the drawn spline set is closed.
+ \param precision Precision of the drawing.
+ \param pattern An integer whose bits describe the line pattern.
+ \param init_hatch If \c true, init hatch motif.
+ **/
+ 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 is_closed_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 (is_closed_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 (is_closed_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 splines \overloading.
+ /**
+ Similar to previous function, with the point tangents automatically estimated from the given points set.
+ **/
+ template<typename tp, typename tc>
+ CImg<T>& draw_spline(const CImg<tp>& points,
+ const tc *const color, const float opacity=1,
+ const bool is_closed_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 = is_closed_set?(p + points._width - 1)%points._width:(p?p - 1:0),
+ p1 = is_closed_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 + cimg::hypot(u0,v0),
+ u1 = x1 - x,
+ v1 = y1 - y,
+ n1 = 1e-8f + cimg::hypot(u1,v1),
+ u = u0/n0 + u1/n1,
+ v = v0/n0 + v1/n1,
+ n = 1e-8f + cimg::hypot(u,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 = is_closed_set?(p + points._width - 1)%points._width:(p?p - 1:0),
+ p1 = is_closed_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 + cimg::hypot(u0,v0,w0),
+ u1 = x1 - x,
+ v1 = y1 - y,
+ w1 = z1 - z,
+ n1 = 1e-8f + cimg::hypot(u1,v1,w1),
+ u = u0/n0 + u1/n1,
+ v = v0/n0 + v1/n1,
+ w = w0/n0 + w1/n1,
+ n = 1e-8f + cimg::hypot(u,v,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,is_closed_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, \
+ std::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, \
+ std::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, \
+ std::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, \
+ std::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, \
+ std::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))
+
+ // [internal] Draw a filled 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 float brightness) {
+ cimg_init_scanline(color,opacity);
+ const float nbrightness = cimg::cut(brightness,0,2);
+ 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)
+ cimg_draw_scanline(xl,xr,y,color,opacity,nbrightness);
+ else
+ _cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2)
+ cimg_draw_scanline(xr,xl,y,color,opacity,nbrightness);
+ }
+ return *this;
+ }
+
+ //! Draw a filled 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex.
+ \param y0 Y-coordinate of the first vertex.
+ \param x1 X-coordinate of the second vertex.
+ \param y1 Y-coordinate of the second vertex.
+ \param x2 X-coordinate of the third vertex.
+ \param y2 Y-coordinate of the third vertex.
+ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Specified color is (null).",
+ cimg_instance);
+ _draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1);
+ return *this;
+ }
+
+ //! Draw a outlined 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex.
+ \param y0 Y-coordinate of the first vertex.
+ \param x1 X-coordinate of the second vertex.
+ \param y1 Y-coordinate of the second vertex.
+ \param x2 X-coordinate of the third vertex.
+ \param y2 Y-coordinate of the third vertex.
+ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the outline pattern.
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Specified color is (null).",
+ cimg_instance);
+ draw_line(x0,y0,x1,y1,color,opacity,pattern,true).
+ draw_line(x1,y1,x2,y2,color,opacity,pattern,false).
+ draw_line(x2,y2,x0,y0,color,opacity,pattern,false);
+ return *this;
+ }
+
+ //! Draw a filled 2d triangle, with z-buffering.
+ /**
+ \param zbuffer Z-buffer image.
+ \param x0 X-coordinate of the first vertex.
+ \param y0 Y-coordinate of the first vertex.
+ \param z0 Z-coordinate of the first vertex.
+ \param x1 X-coordinate of the second vertex.
+ \param y1 Y-coordinate of the second vertex.
+ \param z1 Z-coordinate of the second vertex.
+ \param x2 X-coordinate of the third vertex.
+ \param y2 Y-coordinate of the third vertex.
+ \param z2 Z-coordinate of the third vertex.
+ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param brightness Brightness factor.
+ **/
+ template<typename tz, typename tc>
+ CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const tc *const color, const float opacity=1,
+ const float brightness=1) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Specified color is (null).",
+ cimg_instance);
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_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);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f),
+ nbrightness = cimg::cut(brightness,0,2);
+ const longT whd = (longT)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 = xleft<=xright?zbuffer.data(xleft,y):0;
+ 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 Gouraud-shaded 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex in the image instance.
+ \param y0 Y-coordinate of the first vertex in the image instance.
+ \param x1 X-coordinate of the second vertex in the image instance.
+ \param y1 Y-coordinate of the second vertex in the image instance.
+ \param x2 X-coordinate of the third vertex in the image instance.
+ \param y2 Y-coordinate of the third vertex in the image instance.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param brightness0 Brightness factor of the first vertex (in [0,2]).
+ \param brightness1 brightness factor of the second vertex (in [0,2]).
+ \param brightness2 brightness factor of the third vertex (in [0,2]).
+ \param opacity Drawing opacity.
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Specified color is (null).",
+ cimg_instance);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const longT whd = (longT)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 Gouraud-shaded 2d triangle, with z-buffering \overloading.
+ template<typename tz, typename tc>
+ CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const tc *const color,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Specified color is (null).",
+ cimg_instance);
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_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);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const longT whd = (longT)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 = xleft<=xright?zbuffer.data(xleft,y):0;
+ 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 color-interpolated 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex in the image instance.
+ \param y0 Y-coordinate of the first vertex in the image instance.
+ \param x1 X-coordinate of the second vertex in the image instance.
+ \param y1 Y-coordinate of the second vertex in the image instance.
+ \param x2 X-coordinate of the third vertex in the image instance.
+ \param y2 Y-coordinate of the third vertex in the image instance.
+ \param color1 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the first vertex.
+ \param color2 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the seconf vertex.
+ \param color3 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the third vertex.
+ \param opacity Drawing opacity.
+ **/
+ 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 textured 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex in the image instance.
+ \param y0 Y-coordinate of the first vertex in the image instance.
+ \param x1 X-coordinate of the second vertex in the image instance.
+ \param y1 Y-coordinate of the second vertex in the image instance.
+ \param x2 X-coordinate of the third vertex in the image instance.
+ \param y2 Y-coordinate of the third vertex in the image instance.
+ \param texture Texture image used to fill the triangle.
+ \param tx0 X-coordinate of the first vertex in the texture image.
+ \param ty0 Y-coordinate of the first vertex in the texture image.
+ \param tx1 X-coordinate of the second vertex in the texture image.
+ \param ty1 Y-coordinate of the second vertex in the texture image.
+ \param tx2 X-coordinate of the third vertex in the texture image.
+ \param ty2 Y-coordinate of the third vertex in the texture image.
+ \param opacity Drawing opacity.
+ \param brightness Brightness factor of the drawing (in [0,2]).
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float opacity=1,
+ const float brightness=1) {
+ if (is_empty()) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (is_overlapped(texture))
+ return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f),
+ nbrightness = cimg::cut(brightness,0,2);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ 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._atXY(txleft,tyleft);
+ cimg_forC(*this,c) {
+ *ptrd = (T)*col;
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY(txleft,tyleft);
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nbrightness**col);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY(txleft,tyleft);
+ cimg_forC(*this,c) {
+ *ptrd = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY(txleft,tyleft);
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nopacity**col + *ptrd*copacity);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY(txleft,tyleft);
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY(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+=twh;
+ }
+ ptrd-=offx;
+ txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
+ tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2d textured triangle, with perspective correction.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float opacity=1,
+ const float brightness=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (is_overlapped(texture))
+ return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f),
+ nbrightness = cimg::cut(brightness,0,2);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)*col;
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nbrightness**col);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nopacity**col + *ptrd*copacity);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((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+=twh;
+ }
+ ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ }
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ //! Draw a textured 2d triangle, with perspective correction and 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 CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float opacity=1,
+ const float brightness=1) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_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 (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)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float
+ nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f),
+ nbrightness = cimg::cut(brightness,0,2);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)*col;
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nbrightness**col);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)((2 - nbrightness)**col + (nbrightness - 1)*maxval);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nopacity**col + *ptrd*copacity);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ cimg_forC(*this,c) {
+ *ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY((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+=twh;
+ }
+ ptrd-=offx;
+ }
+ zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
+ }
+ }
+ zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
+ }
+ return *this;
+ }
+
+ //! Draw a Phong-shaded 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex in the image instance.
+ \param y0 Y-coordinate of the first vertex in the image instance.
+ \param x1 X-coordinate of the second vertex in the image instance.
+ \param y1 Y-coordinate of the second vertex in the image instance.
+ \param x2 X-coordinate of the third vertex in the image instance.
+ \param y2 Y-coordinate of the third vertex in the image instance.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param light Light image.
+ \param lx0 X-coordinate of the first vertex in the light image.
+ \param ly0 Y-coordinate of the first vertex in the light image.
+ \param lx1 X-coordinate of the second vertex in the light image.
+ \param ly1 Y-coordinate of the second vertex in the light image.
+ \param lx2 X-coordinate of the third vertex in the light image.
+ \param ly2 Y-coordinate of the third vertex in the light image.
+ \param opacity Drawing opacity.
+ **/
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const tc *const color,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Specified color is (null).",
+ cimg_instance);
+ if (light._depth>1 || light._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).",
+ cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+ if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
+ nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ lwh = (ulongT)light._width*light._height,
+ 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;
+ const tl *lig = &light._atXY(lxleft,lyleft);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ *ptrd = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval));
+ ptrd+=whd; lig+=lwh;
+ }
+ 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;
+ const tl *lig = &light._atXY(lxleft,lyleft);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ const T val = (T)(l<1?l**(col++):((2 - l)**(col++) + (l - 1)*maxval));
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whd; lig+=lwh;
+ }
+ ptrd-=offx;
+ lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
+ lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a Phong-shaded 2d triangle, with z-buffering.
+ template<typename tz, typename tc, typename tl>
+ CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const tc *const color,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Specified color is (null).",
+ cimg_instance);
+ if (light._depth>1 || light._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).",
+ cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_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 (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)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ lwh = (ulongT)light._width*light._height,
+ 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 = xleft<=xright?zbuffer.data(xleft,y):0;
+ if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
+ if (zleft>=(tzfloat)*ptrz) {
+ *ptrz = (tz)zleft;
+ const tc *col = color;
+ const tl *lig = &light._atXY(lxleft,lyleft);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ const tc cval = *(col++);
+ *ptrd = (T)(l<1?l*cval:(2 - l)*cval + (l - 1)*maxval);
+ ptrd+=whd; lig+=lwh;
+ }
+ 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;
+ const tl *lig = &light._atXY(lxleft,lyleft);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ 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; lig+=lwh;
+ }
+ 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 textured Gouraud-shaded 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex in the image instance.
+ \param y0 Y-coordinate of the first vertex in the image instance.
+ \param x1 X-coordinate of the second vertex in the image instance.
+ \param y1 Y-coordinate of the second vertex in the image instance.
+ \param x2 X-coordinate of the third vertex in the image instance.
+ \param y2 Y-coordinate of the third vertex in the image instance.
+ \param texture Texture image used to fill the triangle.
+ \param tx0 X-coordinate of the first vertex in the texture image.
+ \param ty0 Y-coordinate of the first vertex in the texture image.
+ \param tx1 X-coordinate of the second vertex in the texture image.
+ \param ty1 Y-coordinate of the second vertex in the texture image.
+ \param tx2 X-coordinate of the third vertex in the texture image.
+ \param ty2 Y-coordinate of the third vertex in the texture image.
+ \param brightness0 Brightness factor of the first vertex.
+ \param brightness1 Brightness factor of the second vertex.
+ \param brightness2 Brightness factor of the third vertex.
+ \param opacity Drawing opacity.
+ **/
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (is_overlapped(texture))
+ return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
+ brightness0,brightness1,brightness2,opacity);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ 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._atXY(txleft,tyleft);
+ cimg_forC(*this,c) {
+ *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256);
+ ptrd+=whd; col+=twh;
+ }
+ 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._atXY(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+=twh;
+ }
+ 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 textured Gouraud-shaded 2d triangle, with perspective correction \overloading.
+ template<typename tc>
+ CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
+ brightness0,brightness1,brightness2,opacity);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ 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._atXY((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+=twh;
+ }
+ 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._atXY((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+=twh;
+ }
+ 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 textured Gouraud-shaded 2d triangle, with perspective correction and z-buffering \overloading.
+ template<typename tz, typename tc>
+ CImg<T>& draw_triangle(CImg<tz>& zbuffer,
+ const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const float brightness0,
+ const float brightness1,
+ const float brightness2,
+ const float opacity=1) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_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 (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)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ 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._atXY((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+=twh;
+ }
+ 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._atXY((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+=twh;
+ }
+ 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 textured Phong-shaded 2d triangle.
+ /**
+ \param x0 X-coordinate of the first vertex in the image instance.
+ \param y0 Y-coordinate of the first vertex in the image instance.
+ \param x1 X-coordinate of the second vertex in the image instance.
+ \param y1 Y-coordinate of the second vertex in the image instance.
+ \param x2 X-coordinate of the third vertex in the image instance.
+ \param y2 Y-coordinate of the third vertex in the image instance.
+ \param texture Texture image used to fill the triangle.
+ \param tx0 X-coordinate of the first vertex in the texture image.
+ \param ty0 Y-coordinate of the first vertex in the texture image.
+ \param tx1 X-coordinate of the second vertex in the texture image.
+ \param ty1 Y-coordinate of the second vertex in the texture image.
+ \param tx2 X-coordinate of the third vertex in the texture image.
+ \param ty2 Y-coordinate of the third vertex in the texture image.
+ \param light Light image.
+ \param lx0 X-coordinate of the first vertex in the light image.
+ \param ly0 Y-coordinate of the first vertex in the light image.
+ \param lx1 X-coordinate of the second vertex in the light image.
+ \param ly1 Y-coordinate of the second vertex in the light image.
+ \param lx2 X-coordinate of the third vertex in the light image.
+ \param ly2 Y-coordinate of the third vertex in the light image.
+ \param opacity Drawing opacity.
+ **/
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(const int x0, const int y0,
+ const int x1, const int y1,
+ const int x2, const int y2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty()) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (light._depth>1 || light._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).",
+ cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+ if (is_overlapped(texture))
+ return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ if (is_overlapped(light))
+ return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ lwh = (ulongT)light._width*light._height,
+ 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;
+ const bool is_bump = texture._spectrum>=_spectrum + 2;
+ const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1);
+
+ _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._atXY(txleft,tyleft);
+ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0;
+ const tl *lig = &light._atXY(lxleft + bx,lyleft + by);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval);
+ ptrd+=whd; col+=twh; lig+=lwh;
+ }
+ 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._atXY(txleft,tyleft);
+ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0;
+ const tl *lig = &light._atXY(lxleft + bx,lyleft + by);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whd; col+=twh; lig+=lwh;
+ }
+ 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 textured Phong-shaded 2d triangle, with perspective correction.
+ template<typename tc, typename tl>
+ CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
+ const int x1, const int y1, const float z1,
+ const int x2, const int y2, const float z2,
+ const CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (light._depth>1 || light._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).",
+ cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+ if (is_overlapped(texture))
+ return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
+ light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ if (is_overlapped(light))
+ return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2,
+ +light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ lwh = (ulongT)light._width*light._height,
+ 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)));
+ const bool is_bump = texture._spectrum>=_spectrum + 2;
+ const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1);
+
+ _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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0;
+ const tl *lig = &light._atXY(lxleft + bx,lyleft + by);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval);
+ ptrd+=whd; col+=twh; lig+=lwh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0;
+ const tl *lig = &light._atXY(lxleft + bx,lyleft + by);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whd; col+=twh; lig+=lwh;
+ }
+ 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 textured Phong-shaded 2d triangle, with perspective correction and 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 CImg<tc>& texture,
+ const int tx0, const int ty0,
+ const int tx1, const int ty1,
+ const int tx2, const int ty2,
+ const CImg<tl>& light,
+ const int lx0, const int ly0,
+ const int lx1, const int ly1,
+ const int lx2, const int ly2,
+ const float opacity=1) {
+ typedef typename cimg::superset<tz,float>::type tzfloat;
+ if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
+ if (!is_sameXY(zbuffer))
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have "
+ "different dimensions.",
+ cimg_instance,
+ zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data);
+ if (texture._depth>1 || texture._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
+ cimg_instance,
+ texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
+ if (light._depth>1 || light._spectrum<_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).",
+ cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);
+ if (is_overlapped(texture))
+ return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
+ +texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ if (is_overlapped(light))
+ return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
+ texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+ static const T maxval = (T)std::min(cimg::type<T>::max(),(T)cimg::type<tc>::max());
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT
+ whd = (ulongT)_width*_height*_depth,
+ twh = (ulongT)texture._width*texture._height,
+ lwh = (ulongT)light._width*light._height,
+ 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)));
+ const bool is_bump = texture._spectrum>=_spectrum + 2;
+ const ulongT obx = twh*_spectrum, oby = twh*(_spectrum + 1);
+
+ _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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0;
+ const tl *lig = &light._atXY(lxleft + bx,lyleft + by);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ *ptrd = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval);
+ ptrd+=whd; col+=twh; lig+=lwh;
+ }
+ 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._atXY((int)(txleft*invz),(int)(tyleft*invz));
+ const int bx = is_bump?128 - (int)col[obx]:0, by = is_bump?128 - (int)col[oby]:0;
+ const tl *lig = &light._atXY(lxleft + bx,lyleft + by);
+ cimg_forC(*this,c) {
+ const tl l = *lig;
+ const T val = (T)(l<1?l**col:(2 - l)**col + (l - 1)*maxval);
+ *ptrd = (T)(nopacity*val + *ptrd*copacity);
+ ptrd+=whd; col+=twh; lig+=lwh;
+ }
+ 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 filled 4d rectangle.
+ /**
+ \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.
+ **/
+ 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 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;
+ 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 ulongT
+ offX = (ulongT)_width - lX,
+ offY = (ulongT)_width*(_height - lY),
+ offZ = (ulongT)_width*_height*(_depth - lZ);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ 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 filled 3d rectangle.
+ /**
+ \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.
+ **/
+ template<typename tc>
+ CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
+ const int x1, const int y1, const int z1,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_rectangle(): Specified color is (null).",
+ cimg_instance);
+ cimg_forC(*this,c) draw_rectangle(x0,y0,z0,c,x1,y1,z1,c,(T)color[c],opacity);
+ return *this;
+ }
+
+ //! Draw an outlined 3d rectangle \overloading.
+ 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 filled 2d rectangle.
+ /**
+ \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.
+ **/
+ 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 outlined 2d rectangle \overloading.
+ 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 int
+ nx0 = x0<x1?x0:x1, nx1 = x0^x1^nx0,
+ ny0 = y0<y1?y0:y1, ny1 = y0^y1^ny0;
+ 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 2d polygon.
+ /**
+ \param points Set of polygon vertices.
+ \param color Pointer to \c spectrum() consecutive values of type \c T, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ template<typename tp, typename tc>
+ CImg<T>& draw_polygon(const CImg<tp>& points,
+ const tc *const color, const float opacity=1) {
+ if (is_empty() || !points) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_polygon(): Specified color is (null).",
+ cimg_instance);
+ if (points._width==1) return draw_point((int)points(0,0),(int)points(0,1),color,opacity);
+ if (points._width==2) return draw_line((int)points(0,0),(int)points(0,1),
+ (int)points(1,0),(int)points(1,1),color,opacity);
+ if (points._width==3) return draw_triangle((int)points(0,0),(int)points(0,1),
+ (int)points(1,0),(int)points(1,1),
+ (int)points(2,0),(int)points(2,1),color,opacity);
+ cimg_init_scanline(color,opacity);
+ int
+ xmin = 0, ymin = 0,
+ xmax = points.get_shared_row(0).max_min(xmin),
+ ymax = points.get_shared_row(1).max_min(ymin);
+ if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this;
+ if (ymin==ymax) return draw_line(xmin,ymin,xmax,ymax,color,opacity);
+
+ ymin = std::max(0,ymin);
+ ymax = std::min(height() - 1,ymax);
+ CImg<intT> Xs(points._width,ymax - ymin + 1);
+ CImg<uintT> count(Xs._height,1,1,1,0);
+ unsigned int n = 0, nn = 1;
+ bool go_on = true;
+
+ while (go_on) {
+ unsigned int an = (nn + 1)%points._width;
+ const int
+ x0 = (int)points(n,0),
+ y0 = (int)points(n,1);
+ if (points(nn,1)==y0) while (points(an,1)==y0) { nn = an; (an+=1)%=points._width; }
+ const int
+ x1 = (int)points(nn,0),
+ y1 = (int)points(nn,1);
+ unsigned int tn = an;
+ while (points(tn,1)==y1) (tn+=1)%=points._width;
+
+ if (y0!=y1) {
+ const int
+ y2 = (int)points(tn,1),
+ x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1,
+ dy = cimg::sign(y01),
+ tmax = std::max(1,cimg::abs(y01)),
+ tend = tmax - (dy==cimg::sign(y12));
+ unsigned int y = (unsigned int)y0 - ymin;
+ for (int t = 0; t<=tend; ++t, y+=dy)
+ if (y<Xs._height) Xs(count[y]++,y) = x0 + t*x01/tmax;
+ }
+
+ go_on = nn>n;
+ n = nn;
+ nn = an;
+ }
+
+ cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>32))
+ cimg_forY(Xs,y) {
+ const CImg<intT> Xsy = Xs.get_shared_points(0,count[y] - 1,y).sort();
+ int px = width();
+ for (unsigned int n = 0; n<Xsy._width; n+=2) {
+ int x0 = Xsy[n];
+ const int x1 = Xsy[n + 1];
+ x0+=x0==px;
+ cimg_draw_scanline(x0,x1,y + ymin,color,opacity,1);
+ px = x1;
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a outlined 2d polygon \overloading.
+ 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 2d 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 Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ 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 2d ellipse \overloading.
+ /**
+ \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 Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ 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 2d 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 Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the outline pattern.
+ **/
+ 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 2d ellipse \overloading.
+ /**
+ \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 Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the outline pattern.
+ **/
+ 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);
+ }
+
+ template<typename tc>
+ CImg<T>& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle,
+ const tc *const color, const float opacity,
+ const unsigned int pattern) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_ellipse(): Specified color is (null).",
+ cimg_instance);
+ if (r1<=0 || r2<=0) return draw_point(x0,y0,color,opacity);
+ if (r1==r2 && (float)(int)r1==r1) {
+ if (pattern) return draw_circle(x0,y0,r1,color,opacity,pattern);
+ else return draw_circle(x0,y0,r1,color,opacity);
+ }
+ cimg_init_scanline(color,opacity);
+ const float
+ nr1 = cimg::abs(r1) - 0.5, nr2 = cimg::abs(r2) - 0.5,
+ nangle = (float)(angle*cimg::PI/180),
+ u = (float)std::cos(nangle),
+ v = (float)std::sin(nangle),
+ rmax = std::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)cimg::round(fxmin), xmax = (int)cimg::round(fxmax);
+ if (!pattern) cimg_draw_scanline(xmin,xmax,y,color,opacity,1);
+ else {
+ if (first_line) {
+ if (y0 - yb>=0) cimg_draw_scanline(xmin,xmax,y,color,opacity,1);
+ else draw_point(xmin,y,color,opacity).draw_point(xmax,y,color,opacity);
+ first_line = false;
+ } else {
+ if (xmin<oxmin) cimg_draw_scanline(xmin,oxmin - 1,y,color,opacity,1);
+ else cimg_draw_scanline(oxmin + (oxmin==xmin?0:1),xmin,y,color,opacity,1);
+ if (xmax<oxmax) cimg_draw_scanline(xmax,oxmax - 1,y,color,opacity,1);
+ else cimg_draw_scanline(oxmax + (oxmax==xmax?0:1),xmax,y,color,opacity,1);
+ if (y==tymax) cimg_draw_scanline(xmin + 1,xmax - 1,y,color,opacity,1);
+ }
+ }
+ oxmin = xmin; oxmax = xmax;
+ }
+ return *this;
+ }
+
+ //! Draw a filled 2d circle.
+ /**
+ \param x0 X-coordinate of the circle center.
+ \param y0 Y-coordinate of the circle center.
+ \param radius Circle radius.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \note
+ - Circle version of the Bresenham's algorithm is used.
+ **/
+ template<typename tc>
+ CImg<T>& draw_circle(const int x0, const int y0, int radius,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_circle(): Specified color is (null).",
+ cimg_instance);
+ cimg_init_scanline(color,opacity);
+ if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this;
+ if (y0>=0 && y0<height()) cimg_draw_scanline(x0 - radius,x0 + radius,y0,color,opacity,1);
+ 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()) cimg_draw_scanline(x1,x2,y1,color,opacity,1);
+ if (y2>=0 && y2<height()) cimg_draw_scanline(x1,x2,y2,color,opacity,1);
+ 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()) cimg_draw_scanline(x1,x2,y1,color,opacity,1);
+ if (y2>=0 && y2<height()) cimg_draw_scanline(x1,x2,y2,color,opacity,1);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw an outlined 2d circle.
+ /**
+ \param x0 X-coordinate of the circle center.
+ \param y0 Y-coordinate of the circle center.
+ \param radius Circle radius.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern An integer whose bits describe the outline pattern.
+ **/
+ 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 pattern) {
+ cimg::unused(pattern);
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_circle(): Specified color is (null).",
+ cimg_instance);
+ if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) return *this;
+ if (!radius) return draw_point(x0,y0,color,opacity);
+ draw_point(x0 - radius,y0,color,opacity).draw_point(x0 + radius,y0,color,opacity).
+ draw_point(x0,y0 - radius,color,opacity).draw_point(x0,y0 + radius,color,opacity);
+ if (radius==1) return *this;
+ for (int f = 1 - radius, ddFx = 0, ddFy = -(radius<<1), x = 0, y = radius; x<y; ) {
+ if (f>=0) { f+=(ddFy+=2); --y; }
+ ++x; ++(f+=(ddFx+=2));
+ if (x!=y + 1) {
+ const int x1 = x0 - y, x2 = x0 + y, y1 = y0 - x, y2 = y0 + x,
+ x3 = x0 - x, x4 = x0 + x, y3 = y0 - y, y4 = y0 + y;
+ draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity).
+ draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity);
+ if (x!=y)
+ draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity).
+ draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw 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.
+ **/
+ template<typename t>
+ CImg<T>& draw_image(const int x0, const int y0, const int z0, const int c0,
+ const CImg<t>& sprite, const float opacity=1) {
+ if (is_empty() || !sprite) return *this;
+ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity);
+ if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared())
+ 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*(ulongT)sprite.width():0) +
+ (bz?-z0*(ulongT)sprite.width()*sprite.height():0) +
+ (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0);
+ const ulongT
+ offX = (ulongT)_width - lX,
+ soffX = (ulongT)sprite._width - lX,
+ offY = (ulongT)_width*(_height - lY),
+ soffY = (ulongT)sprite._width*(sprite._height - lY),
+ offZ = (ulongT)_width*_height*(_depth - lZ),
+ soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ 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;
+ }
+
+ //! Draw an image \specialization.
+ CImg<T>& draw_image(const int x0, const int y0, const int z0, const int c0,
+ const CImg<T>& sprite, const float opacity=1) {
+ if (is_empty() || !sprite) return *this;
+ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,opacity);
+ if (x0==0 && y0==0 && z0==0 && c0==0 && is_sameXYZC(sprite) && opacity>=1 && !is_shared())
+ 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*(ulongT)sprite.width():0) +
+ (bz?-z0*(ulongT)sprite.width()*sprite.height():0) +
+ (bc?-c0*(ulongT)sprite.width()*sprite.height()*sprite.depth():0);
+ const ulongT
+ offX = (ulongT)_width - lX,
+ soffX = (ulongT)sprite._width - lX,
+ offY = (ulongT)_width*(_height - lY),
+ soffY = (ulongT)sprite._width*(sprite._height - lY),
+ offZ = (ulongT)_width*_height*(_depth - lZ),
+ soffZ = (ulongT)sprite._width*sprite._height*(sprite._depth - lZ),
+ slX = lX*sizeof(T);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ 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 \overloading.
+ 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 \overloading.
+ 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 \overloading.
+ 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 \overloading.
+ template<typename t>
+ CImg<T>& draw_image(const CImg<t>& sprite, const float opacity=1) {
+ return draw_image(0,sprite,opacity);
+ }
+
+ //! Draw a masked image.
+ /**
+ \param sprite Sprite image.
+ \param mask Mask image.
+ \param x0 X-coordinate of the sprite position in the image instance.
+ \param y0 Y-coordinate of the sprite position in the image instance.
+ \param z0 Z-coordinate of the sprite position in the image instance.
+ \param c0 C-coordinate of the sprite position in the image instance.
+ \param mask_max_value Maximum pixel value of the mask image \c mask.
+ \param opacity Drawing opacity.
+ \note
+ - Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite.
+ - 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_max_value=1) {
+ if (is_empty() || !sprite || !mask) return *this;
+ if (is_overlapped(sprite)) return draw_image(x0,y0,z0,c0,+sprite,mask,opacity,mask_max_value);
+ if (is_overlapped(mask)) return draw_image(x0,y0,z0,c0,sprite,+mask,opacity,mask_max_value);
+ 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 ulongT
+ coff = (bx?-x0:0) +
+ (by?-y0*(ulongT)mask.width():0) +
+ (bz?-z0*(ulongT)mask.width()*mask.height():0) +
+ (bc?-c0*(ulongT)mask.width()*mask.height()*mask.depth():0),
+ ssize = (ulongT)mask.width()*mask.height()*mask.depth()*mask.spectrum();
+ const ti *ptrs = sprite._data + coff;
+ const tm *ptrm = mask._data + coff;
+ const ulongT
+ offX = (ulongT)_width - lX,
+ soffX = (ulongT)sprite._width - lX,
+ offY = (ulongT)_width*(_height - lY),
+ soffY = (ulongT)sprite._width*(sprite._height - lY),
+ offZ = (ulongT)_width*_height*(_depth - lZ),
+ soffZ = (ulongT)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_max_value - std::max(mopacity,0.0f);
+ *ptrd = (T)((nopacity*(*(ptrs++)) + *ptrd*copacity)/mask_max_value);
+ ++ptrd;
+ }
+ ptrd+=offX; ptrs+=soffX; ptrm+=soffX;
+ }
+ ptrd+=offY; ptrs+=soffY; ptrm+=soffY;
+ }
+ ptrd+=offZ; ptrs+=soffZ; ptrm+=soffZ;
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a masked image \overloading.
+ 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_max_value=1) {
+ return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_max_value);
+ }
+
+ //! Draw a image \overloading.
+ 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_max_value=1) {
+ return draw_image(x0,y0,0,sprite,mask,opacity,mask_max_value);
+ }
+
+ //! Draw a image \overloading.
+ 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_max_value=1) {
+ return draw_image(x0,0,sprite,mask,opacity,mask_max_value);
+ }
+
+ //! 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_max_value=1) {
+ return draw_image(0,sprite,mask,opacity,mask_max_value);
+ }
+
+ //! Draw a text string.
+ /**
+ \param x0 X-coordinate of the text in the image instance.
+ \param y0 Y-coordinate of the text in the image instance.
+ \param text Format of the text ('printf'-style format string).
+ \param foreground_color Pointer to \c spectrum() consecutive values, defining the foreground drawing color.
+ \param background_color Pointer to \c spectrum() consecutive values, defining the background drawing color.
+ \param opacity Drawing opacity.
+ \param font Font used for drawing text.
+ **/
+ 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;
+ CImg<charT> tmp(2048);
+ std::va_list ap; va_start(ap,font);
+ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font,false);
+ }
+
+ //! Draw a text string \overloading.
+ /**
+ \note A transparent background is used for the text.
+ **/
+ template<typename tc, typename t>
+ CImg<T>& draw_text(const int x0, const int y0,
+ const char *const text,
+ const tc *const foreground_color, const int,
+ const float opacity, const CImgList<t>& font, ...) {
+ if (!font) return *this;
+ CImg<charT> tmp(2048);
+ std::va_list ap; va_start(ap,font);
+ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,foreground_color,(tc*)0,opacity,font,false);
+ }
+
+ //! Draw a text string \overloading.
+ /**
+ \note A transparent foreground is used for the text.
+ **/
+ 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;
+ CImg<charT> tmp(2048);
+ std::va_list ap; va_start(ap,font);
+ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap);
+ return _draw_text(x0,y0,tmp,(tc*)0,background_color,opacity,font,false);
+ }
+
+ //! Draw a text string \overloading.
+ /**
+ \param x0 X-coordinate of the text in the image instance.
+ \param y0 Y-coordinate of the text in the image instance.
+ \param text Format of the text ('printf'-style format string).
+ \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 opacity Drawing opacity.
+ \param font_height Height of the text font (exact match for 13,23,53,103, interpolated otherwise).
+ **/
+ 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;
+ CImg<charT> tmp(2048);
+ std::va_list ap; va_start(ap,font_height);
+ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap);
+ const CImgList<ucharT>& font = CImgList<ucharT>::font(font_height,true);
+ _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font,true);
+ return *this;
+ }
+
+ //! Draw a text string \overloading.
+ template<typename tc>
+ CImg<T>& draw_text(const int x0, const int y0,
+ const char *const text,
+ const tc *const foreground_color, const int background_color=0,
+ const float opacity=1, const unsigned int font_height=13, ...) {
+ if (!font_height) return *this;
+ cimg::unused(background_color);
+ CImg<charT> tmp(2048);
+ std::va_list ap; va_start(ap,font_height);
+ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap);
+ return draw_text(x0,y0,"%s",foreground_color,(const tc*)0,opacity,font_height,tmp._data);
+ }
+
+ //! Draw a text string \overloading.
+ 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;
+ CImg<charT> tmp(2048);
+ std::va_list ap; va_start(ap,font_height);
+ cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap);
+ return draw_text(x0,y0,"%s",(tc*)0,background_color,opacity,font_height,tmp._data);
+ }
+
+ 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,
+ const bool is_native_font) {
+ if (!text) return *this;
+ if (!font)
+ throw CImgArgumentException(_cimg_instance
+ "draw_text(): Empty specified font.",
+ cimg_instance);
+
+ const unsigned int text_length = (unsigned int)std::strlen(text);
+ const bool _is_empty = is_empty();
+ 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 = (unsigned char)text[i];
+ switch (c) {
+ case '\n' : y+=font[0]._height; if (x>w) w = x; x = 0; break;
+ case '\t' : x+=4*font[' ']._width; break;
+ default : if (c<font._width) x+=font[c]._width;
+ }
+ }
+ if (x!=0 || c=='\n') {
+ if (x>w) w=x;
+ y+=font[0]._height;
+ }
+ assign(x0 + w,y0 + y,1,is_native_font?1:font[0]._spectrum,(T)0);
+ }
+
+ int x = x0, y = y0;
+ for (unsigned int i = 0; i<text_length; ++i) {
+ const unsigned char c = (unsigned char)text[i];
+ switch (c) {
+ case '\n' : y+=font[0]._height; x = x0; break;
+ case '\t' : x+=4*font[' ']._width; break;
+ default : if (c<font._width) {
+ CImg<T> letter = font[c];
+ if (letter) {
+ if (is_native_font && _spectrum>letter._spectrum) letter.resize(-100,-100,1,_spectrum,0,2);
+ const unsigned int cmin = std::min(_spectrum,letter._spectrum);
+ if (foreground_color)
+ for (unsigned int c = 0; c<cmin; ++c)
+ if (foreground_color[c]!=1) letter.get_shared_channel(c)*=foreground_color[c];
+ if (c + 256<font.width()) { // Letter has mask.
+ if (background_color)
+ for (unsigned int c = 0; c<cmin; ++c)
+ draw_rectangle(x,y,0,c,x + letter._width - 1,y + letter._height - 1,0,c,
+ background_color[c],opacity);
+ draw_image(x,y,letter,font[c + 256],opacity,255.0f);
+ } else draw_image(x,y,letter,opacity); // Letter has no mask.
+ x+=letter._width;
+ }
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a 2d vector field.
+ /**
+ \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 opacity Drawing opacity.
+ \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 is_arrow Tells if arrows must be drawn, instead of oriented segments.
+ \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 is_arrow=true, const unsigned int pattern=~0U) {
+ return draw_quiver(flow,CImg<t2>(color,_spectrum,1,1,1,true),opacity,sampling,factor,is_arrow,pattern);
+ }
+
+ //! Draw a 2d vector field, using a field of colors.
+ /**
+ \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 opacity Opacity of the drawing.
+ \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 is_arrow Tells if arrows must be drawn, instead of oriented segments.
+ \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 is_arrow=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,is_arrow,pattern);
+ float vmax,fact;
+ if (factor<=0) {
+ float m, M = (float)flow.get_norm(2).max_min(m);
+ vmax = (float)std::max(cimg::abs(m),cimg::abs(M));
+ if (!vmax) vmax = 1;
+ fact = -factor;
+ } else { fact = factor; vmax = 1; }
+
+ for (unsigned int y = sampling/2; y<_height; y+=sampling)
+ for (unsigned int x = sampling/2; x<_width; x+=sampling) {
+ const unsigned int X = x*flow._width/_width, Y = y*flow._height/_height;
+ float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax;
+ if (is_arrow) {
+ const int xx = (int)(x + u), yy = (int)(y + 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.
+ /**
+ \param values_x Values along the horizontal axis.
+ \param y Y-coordinate of the horizontal axis in the image instance.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern Drawing pattern.
+ \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise).
+ \param allow_zero Enable/disable the drawing of label '0' if found.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_axis(const CImg<t>& values_x, const int y,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const unsigned int font_height=13,
+ const bool allow_zero=true) {
+ if (is_empty()) return *this;
+ const int yt = (y + 3 + font_height)<_height?y + 3:y - 2 - (int)font_height;
+ const int siz = (int)values_x.size() - 1;
+ CImg<charT> txt(32);
+ CImg<T> label;
+ if (siz<=0) { // Degenerated case.
+ draw_line(0,y,_width - 1,y,color,opacity,pattern);
+ if (!siz) {
+ cimg_snprintf(txt,txt._width,"%g",(double)*values_x);
+ label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+ const int
+ _xt = (width() - label.width())/2,
+ xt = _xt<3?3:_xt + label.width()>=width() - 2?width() - 3 - label.width():_xt;
+ draw_point(width()/2,y - 1,color,opacity).draw_point(width()/2,y + 1,color,opacity);
+ if (allow_zero || *txt!='0' || txt[1]!=0)
+ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+ }
+ } else { // Regular case.
+ if (values_x[0]<values_x[siz]) draw_arrow(0,y,_width - 1,y,color,opacity,30,5,pattern);
+ else draw_arrow(_width - 1,y,0,y,color,opacity,30,5,pattern);
+ cimg_foroff(values_x,x) {
+ cimg_snprintf(txt,txt._width,"%g",(double)values_x(x));
+ label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+ const int
+ xi = (int)(x*(_width - 1)/siz),
+ _xt = xi - label.width()/2,
+ xt = _xt<3?3:_xt + label.width()>=width() - 2?width() - 3 - label.width():_xt;
+ draw_point(xi,y - 1,color,opacity).draw_point(xi,y + 1,color,opacity);
+ if (allow_zero || *txt!='0' || txt[1]!=0)
+ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a labeled vertical axis.
+ /**
+ \param x X-coordinate of the vertical axis in the image instance.
+ \param values_y Values along the Y-axis.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern Drawing pattern.
+ \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise).
+ \param allow_zero Enable/disable the drawing of label '0' if found.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_axis(const int x, const CImg<t>& values_y,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern=~0U, const unsigned int font_height=13,
+ const bool allow_zero=true) {
+ if (is_empty()) return *this;
+ int siz = (int)values_y.size() - 1;
+ CImg<charT> txt(32);
+ CImg<T> label;
+ if (siz<=0) { // Degenerated case.
+ draw_line(x,0,x,_height - 1,color,opacity,pattern);
+ if (!siz) {
+ cimg_snprintf(txt,txt._width,"%g",(double)*values_y);
+ label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+ const int
+ _yt = (height() - label.height())/2,
+ yt = _yt<0?0:_yt + label.height()>=height()?height() - 1-label.height():_yt,
+ _xt = x - 2 - label.width(),
+ xt = _xt>=0?_xt:x + 3;
+ draw_point(x - 1,height()/2,color,opacity).draw_point(x + 1,height()/2,color,opacity);
+ if (allow_zero || *txt!='0' || txt[1]!=0)
+ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+ }
+ } else { // Regular case.
+ if (values_y[0]<values_y[siz]) draw_arrow(x,0,x,_height - 1,color,opacity,30,5,pattern);
+ else draw_arrow(x,_height - 1,x,0,color,opacity,30,5,pattern);
+ cimg_foroff(values_y,y) {
+ cimg_snprintf(txt,txt._width,"%g",(double)values_y(y));
+ label.assign().draw_text(0,0,txt,color,(tc*)0,opacity,font_height);
+ const int
+ yi = (int)(y*(_height - 1)/siz),
+ _yt = yi - label.height()/2,
+ yt = _yt<0?0:_yt + label.height()>=height()?height() - 1-label.height():_yt,
+ _xt = x - 2 - label.width(),
+ xt = _xt>=0?_xt:x + 3;
+ draw_point(x - 1,yi,color,opacity).draw_point(x + 1,yi,color,opacity);
+ if (allow_zero || *txt!='0' || txt[1]!=0)
+ draw_text(xt,yt,txt,color,(tc*)0,opacity,font_height);
+ }
+ }
+ return *this;
+ }
+
+ //! Draw labeled horizontal and vertical axes.
+ /**
+ \param values_x Values along the X-axis.
+ \param values_y Values along the Y-axis.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern_x Drawing pattern for the X-axis.
+ \param pattern_y Drawing pattern for the Y-axis.
+ \param font_height Height of the labels (exact match for 13,23,53,103, interpolated otherwise).
+ \param allow_zero Enable/disable the drawing of label '0' if found.
+ **/
+ template<typename tx, typename ty, typename tc>
+ CImg<T>& draw_axes(const CImg<tx>& values_x, const CImg<ty>& values_y,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U,
+ const unsigned int font_height=13, const bool allow_zero=true) {
+ if (is_empty()) return *this;
+ const CImg<tx> nvalues_x(values_x._data,values_x.size(),1,1,1,true);
+ const int sizx = (int)values_x.size() - 1, wm1 = width() - 1;
+ if (sizx>=0) {
+ float ox = (float)*nvalues_x;
+ for (unsigned int x = sizx?1U:0U; x<_width; ++x) {
+ const float nx = (float)nvalues_x._linear_atX((float)x*sizx/wm1);
+ if (nx*ox<=0) { draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero); break; }
+ ox = nx;
+ }
+ }
+ const CImg<ty> nvalues_y(values_y._data,values_y.size(),1,1,1,true);
+ const int sizy = (int)values_y.size() - 1, hm1 = height() - 1;
+ if (sizy>0) {
+ float oy = (float)nvalues_y[0];
+ for (unsigned int y = sizy?1U:0U; y<_height; ++y) {
+ const float ny = (float)nvalues_y._linear_atX((float)y*sizy/hm1);
+ if (ny*oy<=0) { draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero); break; }
+ oy = ny;
+ }
+ }
+ return *this;
+ }
+
+ //! Draw labeled horizontal and vertical axes \overloading.
+ 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 pattern_x=~0U, const unsigned int pattern_y=~0U,
+ const unsigned int font_height=13) {
+ if (is_empty()) return *this;
+ const bool allow_zero = (x0*x1>0) || (y0*y1>0);
+ const float
+ dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0),
+ px = dx<=0?1:precisionx==0?(float)std::pow(10.0,(int)std::log10(dx) - 2.0):precisionx,
+ py = dy<=0?1:precisiony==0?(float)std::pow(10.0,(int)std::log10(dy) - 2.0):precisiony;
+ if (x0!=x1 && y0!=y1)
+ draw_axes(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),
+ CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py),
+ color,opacity,pattern_x,pattern_y,font_height,allow_zero);
+ else if (x0==x1 && y0!=y1)
+ draw_axis((int)x0,CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py),
+ color,opacity,pattern_y,font_height);
+ else if (x0!=x1 && y0==y1)
+ draw_axis(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),(int)y0,
+ color,opacity,pattern_x,font_height);
+ return *this;
+ }
+
+ //! Draw 2d grid.
+ /**
+ \param values_x X-coordinates of the vertical lines.
+ \param values_y Y-coordinates of the horizontal lines.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ \param pattern_x Drawing pattern for vertical lines.
+ \param pattern_y Drawing pattern for horizontal lines.
+ **/
+ template<typename tx, typename ty, typename tc>
+ CImg<T>& draw_grid(const CImg<tx>& values_x, const CImg<ty>& values_y,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) {
+ if (is_empty()) return *this;
+ if (values_x) cimg_foroff(values_x,x) {
+ const int xi = (int)values_x[x];
+ if (xi>=0 && xi<width()) draw_line(xi,0,xi,_height - 1,color,opacity,pattern_x);
+ }
+ if (values_y) cimg_foroff(values_y,y) {
+ const int yi = (int)values_y[y];
+ if (yi>=0 && yi<height()) draw_line(0,yi,_width - 1,yi,color,opacity,pattern_y);
+ }
+ return *this;
+ }
+
+ //! Draw 2d grid \simplification.
+ template<typename tc>
+ CImg<T>& draw_grid(const float delta_x, const float delta_y,
+ const float offsetx, const float offsety,
+ const bool invertx, const bool inverty,
+ const tc *const color, const float opacity=1,
+ const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U) {
+ if (is_empty()) return *this;
+ CImg<uintT> seqx, seqy;
+ if (delta_x!=0) {
+ const float dx = delta_x>0?delta_x:_width*-delta_x/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 (delta_y!=0) {
+ const float dy = delta_y>0?delta_y:_height*-delta_y/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,pattern_x,pattern_y);
+ }
+
+ //! Draw 1d graph.
+ /**
+ \param data Image containing the graph values I = f(x).
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+
+ \param plot_type Define the type of the plot:
+ - 0 = No plot.
+ - 1 = Plot using segments.
+ - 2 = Plot using cubic splines.
+ - 3 = Plot with bars.
+ \param vertex_type Define the type of points:
+ - 0 = No points.
+ - 1 = Point.
+ - 2 = Straight cross.
+ - 3 = Diagonal cross.
+ - 4 = Filled circle.
+ - 5 = Outlined circle.
+ - 6 = Square.
+ - 7 = Diamond.
+ \param ymin Lower bound of the y-range.
+ \param ymax Upper bound of the y-range.
+ \param pattern Drawing pattern.
+ \note
+ - if \c ymin==ymax==0, the y-range is computed automatically from the input samples.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_graph(const CImg<t>& data,
+ const tc *const color, const float opacity=1,
+ const unsigned int plot_type=1, const int vertex_type=1,
+ const double ymin=0, const double ymax=0, const unsigned int pattern=~0U) {
+ if (is_empty() || _height<=1) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_graph(): Specified color is (null).",
+ cimg_instance);
+
+ // Create shaded colors for displaying bar plots.
+ CImg<tc> color1, color2;
+ if (plot_type==3) {
+ color1.assign(_spectrum); color2.assign(_spectrum);
+ cimg_forC(*this,c) {
+ color1[c] = (tc)std::min((float)cimg::type<tc>::max(),(float)color[c]*1.2f);
+ color2[c] = (tc)(color[c]*0.4f);
+ }
+ }
+
+ // Compute min/max and normalization factors.
+ const ulongT
+ siz = data.size(),
+ _siz1 = siz - (plot_type!=3),
+ siz1 = _siz1?_siz1:1;
+ const unsigned int
+ _width1 = _width - (plot_type!=3),
+ width1 = _width1?_width1:1;
+ double m = ymin, M = ymax;
+ if (ymin==ymax) m = (double)data.max_min(M);
+ if (m==M) { --m; ++M; }
+ const float ca = (float)(M-m)/(_height - 1);
+ bool init_hatch = true;
+
+ // Draw graph edges
+ switch (plot_type%4) {
+ case 1 : { // Segments
+ int oX = 0, oY = (int)((data[0] - m)/ca);
+ if (siz==1) {
+ const int Y = (int)((*data - m)/ca);
+ draw_line(0,Y,width() - 1,Y,color,opacity,pattern);
+ } else {
+ const float fx = (float)_width/siz1;
+ for (ulongT off = 1; off<siz; ++off) {
+ const int
+ X = (int)(off*fx) - 1,
+ Y = (int)((data[off]-m)/ca);
+ draw_line(oX,oY,X,Y,color,opacity,pattern,init_hatch);
+ oX = X; oY = Y;
+ init_hatch = false;
+ }
+ }
+ } break;
+ case 2 : { // Spline
+ const CImg<t> ndata(data._data,siz,1,1,1,true);
+ int oY = (int)((data[0] - m)/ca);
+ cimg_forX(*this,x) {
+ const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-m)/ca);
+ if (x>0) draw_line(x,oY,x + 1,Y,color,opacity,pattern,init_hatch);
+ init_hatch = false;
+ oY = Y;
+ }
+ } break;
+ case 3 : { // Bars
+ const int Y0 = (int)(-m/ca);
+ const float fx = (float)_width/siz1;
+ int oX = 0;
+ cimg_foroff(data,off) {
+ const int
+ X = (int)((off + 1)*fx) - 1,
+ Y = (int)((data[off] - m)/ca);
+ draw_rectangle(oX,Y0,X,Y,color,opacity).
+ draw_line(oX,Y,oX,Y0,color2.data(),opacity).
+ draw_line(oX,Y0,X,Y0,Y<=Y0?color2.data():color1.data(),opacity).
+ draw_line(X,Y,X,Y0,color1.data(),opacity).
+ draw_line(oX,Y,X,Y,Y<=Y0?color1.data():color2.data(),opacity);
+ oX = X + 1;
+ }
+ } break;
+ default : break; // No edges
+ }
+
+ // Draw graph points
+ const unsigned int wb2 = plot_type==3?_width1/(2*siz):0;
+ const float fx = (float)_width1/siz1;
+ switch (vertex_type%8) {
+ case 1 : { // Point
+ cimg_foroff(data,off) {
+ const int
+ X = (int)(off*fx + wb2),
+ Y = (int)((data[off]-m)/ca);
+ draw_point(X,Y,color,opacity);
+ }
+ } break;
+ case 2 : { // Straight Cross
+ cimg_foroff(data,off) {
+ const int
+ X = (int)(off*fx + wb2),
+ Y = (int)((data[off]-m)/ca);
+ draw_line(X - 3,Y,X + 3,Y,color,opacity).draw_line(X,Y - 3,X,Y + 3,color,opacity);
+ }
+ } break;
+ case 3 : { // Diagonal Cross
+ cimg_foroff(data,off) {
+ const int
+ X = (int)(off*fx + wb2),
+ Y = (int)((data[off]-m)/ca);
+ draw_line(X - 3,Y - 3,X + 3,Y + 3,color,opacity).draw_line(X - 3,Y + 3,X + 3,Y - 3,color,opacity);
+ }
+ } break;
+ case 4 : { // Filled Circle
+ cimg_foroff(data,off) {
+ const int
+ X = (int)(off*fx + wb2),
+ Y = (int)((data[off]-m)/ca);
+ draw_circle(X,Y,3,color,opacity);
+ }
+ } break;
+ case 5 : { // Outlined circle
+ cimg_foroff(data,off) {
+ const int
+ X = (int)(off*fx + wb2),
+ Y = (int)((data[off]-m)/ca);
+ draw_circle(X,Y,3,color,opacity,0U);
+ }
+ } break;
+ case 6 : { // Square
+ cimg_foroff(data,off) {
+ const int
+ X = (int)(off*fx + wb2),
+ Y = (int)((data[off]-m)/ca);
+ draw_rectangle(X - 3,Y - 3,X + 3,Y + 3,color,opacity,~0U);
+ }
+ } break;
+ case 7 : { // Diamond
+ cimg_foroff(data,off) {
+ const int
+ X = (int)(off*fx + wb2),
+ Y = (int)((data[off]-m)/ca);
+ draw_line(X,Y - 4,X + 4,Y,color,opacity).
+ draw_line(X + 4,Y,X,Y + 4,color,opacity).
+ draw_line(X,Y + 4,X - 4,Y,color,opacity).
+ draw_line(X - 4,Y,X,Y - 4,color,opacity);
+ }
+ } break;
+ default : break; // No points
+ }
+ return *this;
+ }
+
+ bool _draw_fill(const int x, const int y, const int z,
+ const CImg<T>& ref, const float tolerance2) const {
+ const T *ptr1 = data(x,y,z), *ptr2 = ref._data;
+ const unsigned long off = _width*_height*_depth;
+ float diff = 0;
+ cimg_forC(*this,c) { diff += cimg::sqr(*ptr1 - *(ptr2++)); ptr1+=off; }
+ return diff<=tolerance2;
+ }
+
+ //! Draw filled 3d region with the flood fill algorithm.
+ /**
+ \param x0 X-coordinate of the starting point of the region to fill.
+ \param y0 Y-coordinate of the starting point of the region to fill.
+ \param z0 Z-coordinate of the starting point of the region to fill.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param[out] region Image that will contain the mask of the filled region mask, as an output.
+ \param tolerance Tolerance concerning neighborhood values.
+ \param opacity Opacity of the drawing.
+ \param is_high_connexity Tells if 8-connexity must be used.
+ \return \c region is initialized with the binary mask of the filled region.
+ **/
+ template<typename tc, typename t>
+ CImg<T>& draw_fill(const int x0, const int y0, const int z0,
+ const tc *const color, const float opacity,
+ CImg<t> &region,
+ const float tolerance = 0,
+ const bool is_high_connectivity = false) {
+#define _draw_fill_push(x,y,z) if (N>=stack._width) stack.resize(2*N + 1,1,1,3,0); \
+ stack[N] = x; stack(N,1) = y; stack(N++,2) = z
+#define _draw_fill_pop(x,y,z) x = stack[--N]; y = stack(N,1); z = stack(N,2)
+#define _draw_fill_is_inside(x,y,z) !_region(x,y,z) && _draw_fill(x,y,z,ref,tolerance2)
+
+ if (!containsXYZC(x0,y0,z0,0)) return *this;
+ const float nopacity = cimg::abs((float)opacity), copacity = 1 - std::max((float)opacity,0.0f);
+ const float tolerance2 = cimg::sqr(tolerance);
+ const CImg<T> ref = get_vector_at(x0,y0,z0);
+ CImg<uintT> stack(256,1,1,3);
+ CImg<ucharT> _region(_width,_height,_depth,1,0);
+ unsigned int N = 0;
+ int x, y, z;
+
+ _draw_fill_push(x0,y0,z0);
+ while (N>0) {
+ _draw_fill_pop(x,y,z);
+ if (!_region(x,y,z)) {
+ const int yp = y - 1, yn = y + 1, zp = z - 1, zn = z + 1;
+ int xl = x, xr = x;
+
+ // Using these booleans reduces the number of pushes drastically.
+ bool is_yp = false, is_yn = false, is_zp = false, is_zn = false;
+ for (int step = -1; step<2; step+=2) {
+ while (x>=0 && x<width() && _draw_fill_is_inside(x,y,z)) {
+ if (yp>=0 && _draw_fill_is_inside(x,yp,z)) {
+ if (!is_yp) { _draw_fill_push(x,yp,z); is_yp = true; }
+ } else is_yp = false;
+ if (yn<height() && _draw_fill_is_inside(x,yn,z)) {
+ if (!is_yn) { _draw_fill_push(x,yn,z); is_yn = true; }
+ } else is_yn = false;
+ if (depth()>1) {
+ if (zp>=0 && _draw_fill_is_inside(x,y,zp)) {
+ if (!is_zp) { _draw_fill_push(x,y,zp); is_zp = true; }
+ } else is_zp = false;
+ if (zn<depth() && _draw_fill_is_inside(x,y,zn)) {
+ if (!is_zn) { _draw_fill_push(x,y,zn); is_zn = true; }
+ } else is_zn = false;
+ }
+ if (is_high_connectivity) {
+ const int xp = x - 1, xn = x + 1;
+ if (yp>=0 && !is_yp) {
+ if (xp>=0 && _draw_fill_is_inside(xp,yp,z)) {
+ _draw_fill_push(xp,yp,z); if (step<0) is_yp = true;
+ }
+ if (xn<width() && _draw_fill_is_inside(xn,yp,z)) {
+ _draw_fill_push(xn,yp,z); if (step>0) is_yp = true;
+ }
+ }
+ if (yn<height() && !is_yn) {
+ if (xp>=0 && _draw_fill_is_inside(xp,yn,z)) {
+ _draw_fill_push(xp,yn,z); if (step<0) is_yn = true;
+ }
+ if (xn<width() && _draw_fill_is_inside(xn,yn,z)) {
+ _draw_fill_push(xn,yn,z); if (step>0) is_yn = true;
+ }
+ }
+ if (depth()>1) {
+ if (zp>=0 && !is_zp) {
+ if (xp>=0 && _draw_fill_is_inside(xp,y,zp)) {
+ _draw_fill_push(xp,y,zp); if (step<0) is_zp = true;
+ }
+ if (xn<width() && _draw_fill_is_inside(xn,y,zp)) {
+ _draw_fill_push(xn,y,zp); if (step>0) is_zp = true;
+ }
+
+ if (yp>=0 && !is_yp) {
+ if (_draw_fill_is_inside(x,yp,zp)) { _draw_fill_push(x,yp,zp); }
+ if (xp>=0 && _draw_fill_is_inside(xp,yp,zp)) { _draw_fill_push(xp,yp,zp); }
+ if (xn<width() && _draw_fill_is_inside(xn,yp,zp)) { _draw_fill_push(xn,yp,zp); }
+ }
+ if (yn<height() && !is_yn) {
+ if (_draw_fill_is_inside(x,yn,zp)) { _draw_fill_push(x,yn,zp); }
+ if (xp>=0 && _draw_fill_is_inside(xp,yn,zp)) { _draw_fill_push(xp,yn,zp); }
+ if (xn<width() && _draw_fill_is_inside(xn,yn,zp)) { _draw_fill_push(xn,yn,zp); }
+ }
+ }
+
+ if (zn<depth() && !is_zn) {
+ if (xp>=0 && _draw_fill_is_inside(xp,y,zn)) {
+ _draw_fill_push(xp,y,zn); if (step<0) is_zn = true;
+ }
+ if (xn<width() && _draw_fill_is_inside(xn,y,zn)) {
+ _draw_fill_push(xn,y,zn); if (step>0) is_zn = true;
+ }
+
+ if (yp>=0 && !is_yp) {
+ if (_draw_fill_is_inside(x,yp,zn)) { _draw_fill_push(x,yp,zn); }
+ if (xp>=0 && _draw_fill_is_inside(xp,yp,zn)) { _draw_fill_push(xp,yp,zn); }
+ if (xn<width() && _draw_fill_is_inside(xn,yp,zn)) { _draw_fill_push(xn,yp,zn); }
+ }
+ if (yn<height() && !is_yn) {
+ if (_draw_fill_is_inside(x,yn,zn)) { _draw_fill_push(x,yn,zn); }
+ if (xp>=0 && _draw_fill_is_inside(xp,yn,zn)) { _draw_fill_push(xp,yn,zn); }
+ if (xn<width() && _draw_fill_is_inside(xn,yn,zn)) { _draw_fill_push(xn,yn,zn); }
+ }
+ }
+ }
+ }
+ x+=step;
+ }
+ if (step<0) { xl = ++x; x = xr + 1; is_yp = is_yn = is_zp = is_zn = false; }
+ else xr = --x;
+ }
+ std::memset(_region.data(xl,y,z),1,xr - xl + 1);
+ if (opacity==1) {
+ if (sizeof(T)==1) {
+ const int dx = xr - xl + 1;
+ cimg_forC(*this,c) std::memset(data(xl,y,z,c),(int)color[c],dx);
+ } else cimg_forC(*this,c) {
+ const T val = (T)color[c];
+ T *ptri = data(xl,y,z,c); for (int k = xl; k<=xr; ++k) *(ptri++) = val;
+ }
+ } else cimg_forC(*this,c) {
+ const T val = (T)(color[c]*nopacity);
+ T *ptri = data(xl,y,z,c); for (int k = xl; k<=xr; ++k) { *ptri = (T)(val + *ptri*copacity); ++ptri; }
+ }
+ }
+ }
+ _region.move_to(region);
+ return *this;
+ }
+
+ //! Draw filled 3d region with the flood fill algorithm \simplification.
+ template<typename tc>
+ CImg<T>& draw_fill(const int x0, const int y0, const int z0,
+ const tc *const color, const float opacity=1,
+ const float tolerance=0, const bool is_high_connexity=false) {
+ CImg<ucharT> tmp;
+ return draw_fill(x0,y0,z0,color,opacity,tmp,tolerance,is_high_connexity);
+ }
+
+ //! Draw filled 2d region with the flood fill algorithm \simplification.
+ template<typename tc>
+ CImg<T>& draw_fill(const int x0, const int y0,
+ const tc *const color, const float opacity=1,
+ const float tolerance=0, const bool is_high_connexity=false) {
+ CImg<ucharT> tmp;
+ return draw_fill(x0,y0,0,color,opacity,tmp,tolerance,is_high_connexity);
+ }
+
+ //! Draw a random plasma texture.
+ /**
+ \param alpha Alpha-parameter.
+ \param beta Beta-parameter.
+ \param scale Scale-parameter.
+ \note Use the mid-point algorithm to render.
+ **/
+ CImg<T>& draw_plasma(const float alpha=1, const float beta=0, const unsigned int scale=8) {
+ if (is_empty()) return *this;
+ const int w = width(), h = height();
+ const Tfloat m = (Tfloat)cimg::type<T>::min(), M = (Tfloat)cimg::type<T>::max();
+ cimg_forZC(*this,z,c) {
+ CImg<T> ref = get_shared_slice(z,c);
+ for (int delta = 1<<std::min(scale,31U); delta>1; delta>>=1) {
+ const int delta2 = delta>>1;
+ const float r = alpha*delta + beta;
+
+ // Square step.
+ for (int y0 = 0; y0<h; y0+=delta)
+ for (int x0 = 0; x0<w; x0+=delta) {
+ const int x1 = (x0 + delta)%w, y1 = (y0 + delta)%h, xc = (x0 + delta2)%w, yc = (y0 + delta2)%h;
+ const Tfloat val = (Tfloat)(0.25f*(ref(x0,y0) + ref(x0,y1) + ref(x0,y1) + ref(x1,y1)) +
+ r*cimg::rand(-1,1));
+ ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+ }
+
+ // Diamond steps.
+ for (int y = -delta2; y<h; y+=delta)
+ for (int x0=0; x0<w; x0+=delta) {
+ const int y0 = cimg::mod(y,h), x1 = (x0 + delta)%w, y1 = (y + delta)%h,
+ xc = (x0 + delta2)%w, yc = (y + delta2)%h;
+ const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) +
+ r*cimg::rand(-1,1));
+ ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+ }
+ for (int y0 = 0; y0<h; y0+=delta)
+ for (int x = -delta2; x<w; x+=delta) {
+ const int x0 = cimg::mod(x,w), x1 = (x + delta)%w, y1 = (y0 + delta)%h,
+ xc = (x + delta2)%w, yc = (y0 + delta2)%h;
+ const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) +
+ r*cimg::rand(-1,1));
+ ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+ }
+ for (int y = -delta2; y<h; y+=delta)
+ for (int x = -delta2; x<w; x+=delta) {
+ const int x0 = cimg::mod(x,w), y0 = cimg::mod(y,h), x1 = (x + delta)%w, y1 = (y + delta)%h,
+ xc = (x + delta2)%w, yc = (y + delta2)%h;
+ const Tfloat val = (Tfloat)(0.25f*(ref(xc,y0) + ref(x0,yc) + ref(xc,y1) + ref(x1,yc)) +
+ r*cimg::rand(-1,1));
+ ref(xc,yc) = (T)(val<m?m:val>M?M:val);
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Draw a quadratic Mandelbrot or Julia 2d fractal.
+ /**
+ \param x0 X-coordinate of the upper-left pixel.
+ \param y0 Y-coordinate of the upper-left pixel.
+ \param x1 X-coordinate of the lower-right pixel.
+ \param y1 Y-coordinate of the lower-right pixel.
+ \param colormap Colormap.
+ \param opacity Drawing opacity.
+ \param z0r Real part of the upper-left fractal vertex.
+ \param z0i Imaginary part of the upper-left fractal vertex.
+ \param z1r Real part of the lower-right fractal vertex.
+ \param z1i Imaginary part of the lower-right fractal vertex.
+ \param iteration_max Maximum number of iterations for each estimated point.
+ \param is_normalized_iteration Tells if iterations are normalized.
+ \param is_julia_set Tells if the Mandelbrot or Julia set is rendered.
+ \param param_r Real part of the Julia set parameter.
+ \param param_i Imaginary part of the Julia set parameter.
+ \note Fractal rendering is done by 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>& colormap, 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 is_normalized_iteration=false,
+ const bool is_julia_set=false,
+ const double param_r=0, const double param_i=0) {
+ if (is_empty()) return *this;
+ CImg<tc> palette;
+ if (colormap) palette.assign(colormap._data,colormap.size()/colormap._spectrum,1,1,colormap._spectrum,true);
+ if (palette && palette._spectrum!=_spectrum)
+ throw CImgArgumentException(_cimg_instance
+ "draw_mandelbrot(): Instance and specified colormap (%u,%u,%u,%u,%p) have "
+ "incompatible dimensions.",
+ cimg_instance,
+ colormap._width,colormap._height,colormap._depth,colormap._spectrum,colormap._data);
+
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f), ln2 = (float)std::log(2.0);
+ const int
+ _x0 = cimg::cut(x0,0,width() - 1),
+ _y0 = cimg::cut(y0,0,height() - 1),
+ _x1 = cimg::cut(x1,0,width() - 1),
+ _y1 = cimg::cut(y1,0,height() - 1);
+
+ cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if((1 + _x1 - _x0)*(1 + _y1 - _y0)>=2048))
+ for (int q = _y0; q<=_y1; ++q)
+ for (int p = _x0; p<=_x1; ++p) {
+ unsigned int iteration = 0;
+ const double x = z0r + p*(z1r-z0r)/_width, y = z0i + q*(z1i-z0i)/_height;
+ double zr, zi, cr, ci;
+ if (is_julia_set) { zr = x; zi = y; cr = param_r; ci = param_i; }
+ else { zr = param_r; zi = param_i; 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 (is_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 2d fractal \overloading.
+ template<typename tc>
+ CImg<T>& draw_mandelbrot(const CImg<tc>& colormap, 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 is_normalized_iteration=false,
+ const bool is_julia_set=false,
+ const double param_r=0, const double param_i=0) {
+ return draw_mandelbrot(0,0,_width - 1,_height - 1,colormap,opacity,
+ z0r,z0i,z1r,z1i,iteration_max,is_normalized_iteration,is_julia_set,param_r,param_i);
+ }
+
+ //! Draw a 1d gaussian function.
+ /**
+ \param xc X-coordinate of the gaussian center.
+ \param sigma Standard variation of the gaussian distribution.
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ template<typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float sigma,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_gaussian(): Specified color is (null).",
+ cimg_instance);
+ const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT whd = (ulongT)_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 a 2d gaussian function.
+ /**
+ \param xc X-coordinate of the gaussian center.
+ \param yc Y-coordinate of the gaussian center.
+ \param tensor Covariance matrix (must be 2x2).
+ \param color Pointer to \c spectrum() consecutive values, defining the drawing color.
+ \param opacity Drawing opacity.
+ **/
+ template<typename t, typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ if (tensor._width!=2 || tensor._height!=2 || tensor._depth!=1 || tensor._spectrum!=1)
+ throw CImgArgumentException(_cimg_instance
+ "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 2x2 matrix.",
+ cimg_instance,
+ tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data);
+ if (!color)
+ throw CImgArgumentException(_cimg_instance
+ "draw_gaussian(): Specified color is (null).",
+ cimg_instance);
+ typedef typename CImg<t>::Tfloat tfloat;
+ const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
+ const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = invT2(1,1);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT whd = (ulongT)_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 a 2d gaussian function \overloading.
+ 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 a 2d gaussian function \overloading.
+ 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 a 3d gaussian function \overloading.
+ template<typename t, typename tc>
+ CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
+ const tc *const color, const float opacity=1) {
+ if (is_empty()) return *this;
+ typedef typename CImg<t>::Tfloat tfloat;
+ if (tensor._width!=3 || tensor._height!=3 || tensor._depth!=1 || tensor._spectrum!=1)
+ throw CImgArgumentException(_cimg_instance
+ "draw_gaussian(): Specified tensor (%u,%u,%u,%u,%p) is not a 3x3 matrix.",
+ cimg_instance,
+ tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data);
+
+ const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
+ const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = 2*invT2(2,0), d = invT2(1,1), e = 2*invT2(2,1), f = invT2(2,2);
+ const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.0f);
+ const ulongT whd = (ulongT)_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 a 3d gaussian function \overloading.
+ 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 x0 X-coordinate of the 3d object position
+ \param y0 Y-coordinate of the 3d object position
+ \param z0 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 d Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud)
+ \param is_double_sided Tells if object faces have two sides or are oriented.
+ \param focale length of the focale (0 for parallel projection)
+ \param lightx X-coordinate of the light
+ \param lighty Y-coordinate of the light
+ \param lightz Z-coordinate of the light
+ \param specular_lightness Amount of specular light.
+ \param specular_shininess 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 is_double_sided=false, const float focale=700,
+ const float lightx=0, const float lighty=0, const float lightz=-5e8,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f) {
+ return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type,
+ is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,CImg<floatT>::empty());
+ }
+
+ //! Draw a 3d object \simplification.
+ 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 is_double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_lightness, const float specular_shininess,
+ CImg<tz>& zbuffer) {
+ return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,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 is_double_sided=false, const float focale=700,
+ const float lightx=0, const float lighty=0, const float lightz=-5e8,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f) {
+ return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,
+ is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,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 is_double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_lightness, const float specular_shininess,
+ CImg<tz>& zbuffer) {
+ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,1);
+ }
+#endif
+
+ //! Draw a 3d object \simplification.
+ 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 is_double_sided=false, const float focale=700,
+ const float lightx=0, const float lighty=0, const float lightz=-5e8,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f) {
+ return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type,
+ is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,CImg<floatT>::empty());
+ }
+
+ //! Draw a 3d object \simplification.
+ 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 is_double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_lightness, const float specular_shininess,
+ CImg<tz>& zbuffer) {
+ return _draw_object3d(0,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,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 is_double_sided=false, const float focale=700,
+ const float lightx=0, const float lighty=0, const float lightz=-5e8,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f) {
+ return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,
+ is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,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 is_double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_lightness, const float specular_shininess,
+ CImg<tz>& zbuffer) {
+ return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,vertices,primitives,colors,opacities,
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,1);
+ }
+#endif
+
+ //! Draw a 3d object \simplification.
+ 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 is_double_sided=false, const float focale=700,
+ const float lightx=0, const float lighty=0, const float lightz=-5e8,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f) {
+ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::const_empty(),
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,CImg<floatT>::empty());
+ }
+
+ //! Draw a 3d object \simplification.
+ 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 is_double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_lightness, const float specular_shininess,
+ CImg<tz>& zbuffer) {
+ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::const_empty(),
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,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 is_double_sided=false, const float focale=700,
+ const float lightx=0, const float lighty=0, const float lightz=-5e8,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f) {
+ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::const_empty(),
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,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 is_double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_lightness, const float specular_shininess,
+ CImg<tz>& zbuffer) {
+ return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg<floatT>::const_empty(),
+ render_type,is_double_sided,focale,lightx,lighty,lightz,
+ specular_lightness,specular_shininess,zbuffer);
+ }
+#endif
+
+ template<typename t, typename to>
+ static float __draw_object3d(const CImgList<t>& opacities, const unsigned int n_primitive, CImg<to>& opacity) {
+ if (n_primitive>=opacities._width || opacities[n_primitive].is_empty()) { opacity.assign(); return 1; }
+ if (opacities[n_primitive].size()==1) { opacity.assign(); return opacities(n_primitive,0); }
+ opacity.assign(opacities[n_primitive],true);
+ return 1.0f;
+ }
+
+ template<typename t, typename to>
+ static float __draw_object3d(const CImg<t>& opacities, const unsigned int n_primitive, CImg<to>& opacity) {
+ opacity.assign();
+ return n_primitive>=opacities._width?1.0f:(float)opacities[n_primitive];
+ }
+
+ template<typename t>
+ static float ___draw_object3d(const CImgList<t>& opacities, const unsigned int n_primitive) {
+ return n_primitive<opacities._width && opacities[n_primitive].size()==1?(float)opacities(n_primitive,0):1.0f;
+ }
+
+ template<typename t>
+ static float ___draw_object3d(const CImg<t>& opacities, const unsigned int n_primitive) {
+ return n_primitive<opacities._width?(float)opacities[n_primitive]:1.0f;
+ }
+
+ 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 is_double_sided, const float focale,
+ const float lightx, const float lighty, const float lightz,
+ const float specular_lightness, const float specular_shininess,
+ const float sprite_scale) {
+ typedef typename cimg::superset2<tp,tz,float>::type tpfloat;
+ typedef typename to::value_type _to;
+ if (is_empty() || !vertices || !primitives) return *this;
+ CImg<char> error_message(1024);
+ 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.data());
+#ifndef cimg_use_board
+ if (pboard) return *this;
+#endif
+ if (render_type==5) cimg::mutex(10); // Static variable used in this case, breaks thread-safety.
+
+ const float
+ nspec = 1 - (specular_lightness<0.0f?0.0f:(specular_lightness>1.0f?1.0f:specular_lightness)),
+ nspec2 = 1 + (specular_shininess<0.0f?0.0f:specular_shininess),
+ nsl1 = (nspec2 - 1)/cimg::sqr(nspec - 1),
+ nsl2 = 1 - 2*nsl1*nspec,
+ nsl3 = nspec2 - nsl1 - nsl2;
+
+ // Create light texture for phong-like rendering.
+ CImg<floatT> light_texture;
+ if (render_type==5) {
+ if (colors._width>primitives._width) {
+ static CImg<floatT> default_light_texture;
+ static const tc *lptr = 0;
+ static tc ref_values[64] = { 0 };
+ const CImg<tc>& img = colors.back();
+ bool is_same_texture = (lptr==img._data);
+ if (is_same_texture)
+ for (unsigned int r = 0, j = 0; j<8; ++j)
+ for (unsigned int i = 0; i<8; ++i)
+ if (ref_values[r++]!=img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum)) {
+ is_same_texture = false; break;
+ }
+ if (!is_same_texture || default_light_texture._spectrum<_spectrum) {
+ (default_light_texture.assign(img,false)/=255).resize(-100,-100,1,_spectrum);
+ lptr = colors.back().data();
+ for (unsigned int r = 0, j = 0; j<8; ++j)
+ for (unsigned int i = 0; i<8; ++i)
+ ref_values[r++] = img(i*img._width/9,j*img._height/9,0,(i + j)%img._spectrum);
+ }
+ light_texture.assign(default_light_texture,true);
+ } else {
+ static CImg<floatT> default_light_texture;
+ static float olightx = 0, olighty = 0, olightz = 0, ospecular_shininess = 0;
+ if (!default_light_texture ||
+ lightx!=olightx || lighty!=olighty || lightz!=olightz ||
+ specular_shininess!=ospecular_shininess || default_light_texture._spectrum<_spectrum) {
+ default_light_texture.assign(512,512);
+ const float
+ dlx = lightx - X,
+ dly = lighty - Y,
+ dlz = lightz - Z,
+ nl = cimg::hypot(dlx,dly,dlz),
+ nlx = (default_light_texture._width - 1)/2*(1 + dlx/nl),
+ nly = (default_light_texture._height - 1)/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) = std::min(2.0f,nsl1*factor*factor + nsl2*factor + nsl3);
+ }
+ default_light_texture.resize(-100,-100,1,_spectrum);
+ olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shininess = specular_shininess;
+ }
+ light_texture.assign(default_light_texture,true);
+ }
+ }
+
+ // Compute 3d to 2d projection.
+ CImg<tpfloat> projections(vertices._width,2);
+ tpfloat parallzmin = cimg::type<tpfloat>::max();
+ const float absfocale = focale?cimg::abs(focale):0;
+ if (absfocale) {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(projections.size()>4096))
+ cimg_forX(projections,l) { // Perspective projection
+ const tpfloat
+ x = (tpfloat)vertices(l,0),
+ y = (tpfloat)vertices(l,1),
+ z = (tpfloat)vertices(l,2);
+ const tpfloat projectedz = z + Z + absfocale;
+ projections(l,1) = Y + absfocale*y/projectedz;
+ projections(l,0) = X + absfocale*x/projectedz;
+ }
+ } else {
+ cimg_pragma_openmp(parallel for cimg_openmp_if(projections.size()>4096))
+ 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;
+ }
+ }
+
+ const float _focale = absfocale?absfocale:(1e5f-parallzmin);
+ float zmax = 0;
+ if (zbuffer) zmax = vertices.get_shared_row(2).max();
+
+ // Compute visible primitives.
+ CImg<uintT> visibles(primitives._width,1,1,1,~0U);
+ CImg<tpfloat> zrange(primitives._width);
+ const tpfloat zmin = absfocale?(tpfloat)(1.5f - absfocale):cimg::type<tpfloat>::min();
+ bool is_forward = zbuffer?true:false;
+
+ cimg_pragma_openmp(parallel for cimg_openmp_if(primitives.size()>4096))
+ cimglist_for(primitives,l) {
+ const CImg<tf>& primitive = primitives[l];
+ switch (primitive.size()) {
+ case 1 : { // Point
+ CImg<_to> _opacity;
+ __draw_object3d(opacities,l,_opacity);
+ if (l<=colors.width() && (colors[l].size()!=_spectrum || _opacity)) is_forward = false;
+ const unsigned int i0 = (unsigned int)primitive(0);
+ const tpfloat z0 = Z + vertices(i0,2);
+ if (z0>zmin) {
+ visibles(l) = (unsigned int)l;
+ zrange(l) = z0;
+ }
+ } break;
+ case 5 : { // Sphere
+ const unsigned int
+ i0 = (unsigned int)primitive(0),
+ i1 = (unsigned int)primitive(1);
+ const tpfloat
+ Xc = 0.5f*((float)vertices(i0,0) + (float)vertices(i1,0)),
+ Yc = 0.5f*((float)vertices(i0,1) + (float)vertices(i1,1)),
+ Zc = 0.5f*((float)vertices(i0,2) + (float)vertices(i1,2)),
+ _zc = Z + Zc,
+ zc = _zc + _focale,
+ xc = X + Xc*(absfocale?absfocale/zc:1),
+ yc = Y + Yc*(absfocale?absfocale/zc:1),
+ radius = 0.5f*cimg::hypot(vertices(i1,0) - vertices(i0,0),
+ vertices(i1,1) - vertices(i0,1),
+ vertices(i1,2) - vertices(i0,2))*(absfocale?absfocale/zc:1),
+ xm = xc - radius,
+ ym = yc - radius,
+ xM = xc + radius,
+ yM = yc + radius;
+ if (xM>=0 && xm<_width && yM>=0 && ym<_height && _zc>zmin) {
+ visibles(l) = (unsigned int)l;
+ zrange(l) = _zc;
+ }
+ is_forward = false;
+ } 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(l) = (unsigned int)l;
+ zrange(l) = (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 (is_double_sided || d<0) {
+ visibles(l) = (unsigned int)l;
+ zrange(l) = (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 && z3>zmin) {
+ const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0);
+ if (is_double_sided || d<0) {
+ visibles(l) = (unsigned int)l;
+ zrange(l) = (z0 + z1 + z2 + z3)/4;
+ }
+ }
+ } break;
+ default :
+ if (render_type==5) cimg::mutex(10,0);
+ 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());
+ }
+ }
+
+ // Force transparent primitives to be drawn last when zbuffer is activated
+ // (and if object contains no spheres or sprites).
+ if (is_forward)
+ cimglist_for(primitives,l)
+ if (___draw_object3d(opacities,l)!=1) zrange(l) = 2*zmax - zrange(l);
+
+ // Sort only visibles primitives.
+ unsigned int *p_visibles = visibles._data;
+ tpfloat *p_zrange = zrange._data;
+ const tpfloat *ptrz = p_zrange;
+ cimg_for(visibles,ptr,unsigned int) {
+ if (*ptr!=~0U) { *(p_visibles++) = *ptr; *(p_zrange++) = *ptrz; }
+ ++ptrz;
+ }
+ const unsigned int nb_visibles = (unsigned int)(p_zrange - zrange._data);
+ if (!nb_visibles) {
+ if (render_type==5) cimg::mutex(10,0);
+ return *this;
+ }
+ CImg<uintT> permutations;
+ CImg<tpfloat>(zrange._data,nb_visibles,1,1,1,true).sort(permutations,is_forward);
+
+ // Compute light properties
+ CImg<floatT> lightprops;
+ switch (render_type) {
+ case 3 : { // Flat Shading
+ lightprops.assign(nb_visibles);
+ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096))
+ cimg_forX(lightprops,l) {
+ const CImg<tf>& primitive = primitives(visibles(permutations(l)));
+ const unsigned int psize = (unsigned int)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 = 1e-5f + cimg::hypot(nx,ny,nz),
+ lx = X + (x0 + x1 + x2)/3 - lightx,
+ ly = Y + (y0 + y1 + y2)/3 - lighty,
+ lz = Z + (z0 + z1 + z2)/3 - lightz,
+ nl = 1e-5f + cimg::hypot(lx,ly,lz),
+ factor = std::max(cimg::abs(-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)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,6,1,1,0);
+ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096))
+ for (unsigned int l = 0; l<nb_visibles; ++l) {
+ const CImg<tf>& primitive = primitives[visibles(l)];
+ const unsigned int psize = (unsigned int)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 = 1e-5f + cimg::hypot(nnx,nny,nnz),
+ nx = nnx/norm,
+ ny = nny/norm,
+ nz = nnz/norm;
+ unsigned int ix = 0, iy = 1, iz = 2;
+ if (is_double_sided && nz>0) { ix = 3; iy = 4; iz = 5; }
+ vertices_normals(i0,ix)+=nx; vertices_normals(i0,iy)+=ny; vertices_normals(i0,iz)+=nz;
+ vertices_normals(i1,ix)+=nx; vertices_normals(i1,iy)+=ny; vertices_normals(i1,iz)+=nz;
+ vertices_normals(i2,ix)+=nx; vertices_normals(i2,iy)+=ny; vertices_normals(i2,iz)+=nz;
+ if (rectangle_flag) {
+ vertices_normals(i3,ix)+=nx; vertices_normals(i3,iy)+=ny; vertices_normals(i3,iz)+=nz;
+ }
+ }
+ }
+
+ if (is_double_sided) cimg_forX(vertices_normals,p) {
+ const float
+ nx0 = vertices_normals(p,0), ny0 = vertices_normals(p,1), nz0 = vertices_normals(p,2),
+ nx1 = vertices_normals(p,3), ny1 = vertices_normals(p,4), nz1 = vertices_normals(p,5),
+ n0 = nx0*nx0 + ny0*ny0 + nz0*nz0, n1 = nx1*nx1 + ny1*ny1 + nz1*nz1;
+ if (n1>n0) {
+ vertices_normals(p,0) = -nx1;
+ vertices_normals(p,1) = -ny1;
+ vertices_normals(p,2) = -nz1;
+ }
+ }
+
+ if (render_type==4) {
+ lightprops.assign(vertices._width);
+ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096))
+ cimg_forX(lightprops,l) {
+ const tpfloat
+ nx = vertices_normals(l,0),
+ ny = vertices_normals(l,1),
+ nz = vertices_normals(l,2),
+ norm = 1e-5f + cimg::hypot(nx,ny,nz),
+ lx = X + vertices(l,0) - lightx,
+ ly = Y + vertices(l,1) - lighty,
+ lz = Z + vertices(l,2) - lightz,
+ nl = 1e-5f + cimg::hypot(lx,ly,lz),
+ factor = std::max((-lx*nx - ly*ny - lz*nz)/(norm*nl),(tpfloat)0);
+ lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
+ }
+ } else {
+ const unsigned int
+ lw2 = light_texture._width/2 - 1,
+ lh2 = light_texture._height/2 - 1;
+ lightprops.assign(vertices._width,2);
+ cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096))
+ cimg_forX(lightprops,l) {
+ const tpfloat
+ nx = vertices_normals(l,0),
+ ny = vertices_normals(l,1),
+ nz = vertices_normals(l,2),
+ norm = 1e-5f + cimg::hypot(nx,ny,nz),
+ nnx = nx/norm,
+ nny = ny/norm;
+ lightprops(l,0) = lw2*(1 + nnx);
+ lightprops(l,1) = lh2*(1 + nny);
+ }
+ }
+ } break;
+ }
+
+ // Draw visible primitives
+ const CImg<tc> default_color(1,_spectrum,1,1,(tc)200);
+ CImg<_to> _opacity;
+
+ for (unsigned int l = 0; l<nb_visibles; ++l) {
+ const unsigned int n_primitive = visibles(permutations(l));
+ const CImg<tf>& primitive = primitives[n_primitive];
+ const CImg<tc>
+ &__color = n_primitive<colors._width?colors[n_primitive]:CImg<tc>(),
+ _color = (__color && __color.size()!=_spectrum && __color._spectrum<_spectrum)?
+ __color.get_resize(-100,-100,-100,_spectrum,0):CImg<tc>(),
+ &color = _color?_color:(__color?__color:default_color);
+ const tc *const pcolor = color._data;
+ const float opacity = __draw_object3d(opacities,n_primitive,_opacity);
+
+#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 (_opacity.is_empty()) { // Scalar opacity.
+
+ if (color.size()==_spectrum) { // Colored point.
+ draw_point(x0,y0,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ board.drawDot((float)x0,height()-(float)y0);
+ }
+#endif
+ } else { // Sprite.
+ const tpfloat z = Z + vertices(n0,2);
+ const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1);
+ const unsigned int
+ _sw = (unsigned int)(color._width*factor),
+ _sh = (unsigned int)(color._height*factor),
+ sw = _sw?_sw:1, sh = _sh?_sh:1;
+ const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2;
+ if (sw<=3*_width/2 && sh<=3*_height/2 &&
+ (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_image(nx0,ny0,sprite,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128);
+ board.setFillColor(LibBoard::Color::Null);
+ board.drawRectangle((float)nx0,height() - (float)ny0,sw,sh);
+ }
+#endif
+ }
+ }
+ } else { // Opacity mask.
+ const tpfloat z = Z + vertices(n0,2);
+ const float factor = focale<0?1:sprite_scale*(absfocale?absfocale/(z + absfocale):1);
+ const unsigned int
+ _sw = (unsigned int)(std::max(color._width,_opacity._width)*factor),
+ _sh = (unsigned int)(std::max(color._height,_opacity._height)*factor),
+ sw = _sw?_sw:1, sh = _sh?_sh:1;
+ const int nx0 = x0 - (int)sw/2, ny0 = y0 - (int)sh/2;
+ if (sw<=3*_width/2 && sh<=3*_height/2 &&
+ (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;
+ const CImg<_to>
+ _nopacity = (sw!=_opacity._width || sh!=_opacity._height)?
+ _opacity.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg<_to>(),
+ &nopacity = _nopacity?_nopacity:_opacity;
+ draw_image(nx0,ny0,sprite,nopacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128);
+ board.setFillColor(LibBoard::Color::Null);
+ 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,opacity);
+ else draw_line(x0,y0,x1,y1,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ board.drawLine((float)x0,height() - (float)y0,x1,height() - (float)y1);
+ }
+#endif
+ } else {
+ draw_point(x0,y0,pcolor,opacity).draw_point(x1,y1,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ board.drawDot((float)x0,height() - (float)y0);
+ board.drawDot((float)x1,height() - (float)y1);
+ }
+#endif
+ }
+ } break;
+ case 5 : { // Colored sphere
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ is_wireframe = (unsigned int)primitive[2];
+ const float
+ Xc = 0.5f*((float)vertices(n0,0) + (float)vertices(n1,0)),
+ Yc = 0.5f*((float)vertices(n0,1) + (float)vertices(n1,1)),
+ Zc = 0.5f*((float)vertices(n0,2) + (float)vertices(n1,2)),
+ zc = Z + Zc + _focale,
+ xc = X + Xc*(absfocale?absfocale/zc:1),
+ yc = Y + Yc*(absfocale?absfocale/zc:1),
+ radius = 0.5f*cimg::hypot(vertices(n1,0) - vertices(n0,0),
+ vertices(n1,1) - vertices(n0,1),
+ vertices(n1,2) - vertices(n0,2))*(absfocale?absfocale/zc:1);
+ switch (render_type) {
+ case 0 :
+ draw_point((int)xc,(int)yc,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ board.drawDot(xc,height() - yc);
+ }
+#endif
+ break;
+ case 1 :
+ draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ board.setFillColor(LibBoard::Color::Null);
+ board.drawCircle(xc,height() - yc,radius);
+ }
+#endif
+ break;
+ default :
+ if (is_wireframe) draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity,~0U);
+ else draw_circle((int)xc,(int)yc,(int)radius,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ if (!is_wireframe) board.fillCircle(xc,height() - yc,radius);
+ else {
+ board.setFillColor(LibBoard::Color::Null);
+ board.drawCircle(xc,height() - yc,radius);
+ }
+ }
+#endif
+ break;
+ }
+ } break;
+ case 6 : { // Textured line
+ if (!__color) {
+ if (render_type==5) cimg::mutex(10,0);
+ throw CImgArgumentException(_cimg_instance
+ "draw_object3d(): Undefined texture for line primitive [%u].",
+ cimg_instance,n_primitive);
+ }
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1];
+ const int
+ tx0 = (int)primitive[2], ty0 = (int)primitive[3],
+ tx1 = (int)primitive[4], ty1 = (int)primitive[5],
+ 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,opacity);
+ else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255));
+ board.drawLine((float)x0,height() - (float)y0,(float)x1,height() - (float)y1);
+ }
+#endif
+ } else {
+ draw_point(x0,y0,color.get_vector_at(tx0<=0?0:tx0>=color.width()?color.width() - 1:tx0,
+ ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity).
+ draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1,
+ ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255));
+ board.drawDot((float)x0,height() - (float)y0);
+ board.drawDot((float)x1,height() - (float)y1);
+ }
+#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,opacity).draw_point(x1,y1,pcolor,opacity).draw_point(x2,y2,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ board.drawDot((float)x0,height() - (float)y0);
+ board.drawDot((float)x1,height() - (float)y1);
+ board.drawDot((float)x2,height() - (float)y2);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,pcolor,opacity).
+ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity);
+ else
+ draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x0,y0,x2,y2,pcolor,opacity).
+ draw_line(x1,y1,x2,y2,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*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,opacity);
+ else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*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,opacity,lightprops(l));
+ else _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = std::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(color[0]*lp),
+ (unsigned char)(color[1]*lp),
+ (unsigned char)(color[2]*lp),
+ (unsigned char)(opacity*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),opacity);
+ else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprops(n0),lightprops(n1),lightprops(n2),opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi((unsigned char)(color[0]),
+ (unsigned char)(color[1]),
+ (unsigned char)(color[2]),
+ (unsigned char)(opacity*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,opacity);
+ else draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
+#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)(opacity*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,opacity).draw_point(x1,y1,pcolor,opacity).
+ draw_point(x2,y2,pcolor,opacity).draw_point(x3,y3,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*255));
+ board.drawDot((float)x0,height() - (float)y0);
+ board.drawDot((float)x1,height() - (float)y1);
+ board.drawDot((float)x2,height() - (float)y2);
+ board.drawDot((float)x3,height() - (float)y3);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,pcolor,opacity).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,pcolor,opacity).
+ draw_line(zbuffer,x2,y2,z2,x3,y3,z3,pcolor,opacity).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,pcolor,opacity);
+ else
+ draw_line(x0,y0,x1,y1,pcolor,opacity).draw_line(x1,y1,x2,y2,pcolor,opacity).
+ draw_line(x2,y2,x3,y3,pcolor,opacity).draw_line(x3,y3,x0,y0,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*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,opacity).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity);
+ else
+ draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity).draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opacity*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,opacity,lightprops(l)).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,opacity,lightprops(l));
+ else
+ _draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,opacity,lightprops(l)).
+ _draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,opacity,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = std::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(color[0]*lp),
+ (unsigned char)(color[1]*lp),
+ (unsigned char)(color[2]*lp),(unsigned char)(opacity*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,opacity).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,lightprop0,lightprop2,lightprop3,opacity);
+ else
+ draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,lightprop0,lightprop1,lightprop2,opacity).
+ draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,lightprop0,lightprop2,lightprop3,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi((unsigned char)(color[0]),
+ (unsigned char)(color[1]),
+ (unsigned char)(color[2]),
+ (unsigned char)(opacity*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,opacity).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opacity);
+ else
+ draw_triangle(x0,y0,x1,y1,x2,y2,pcolor,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opacity).
+ draw_triangle(x0,y0,x2,y2,x3,y3,pcolor,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opacity);
+#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)(opacity*255));
+ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0,
+ (float)x1,height() - (float)y1,l1,
+ (float)x2,height() - (float)y2,l2);
+ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0,
+ (float)x2,height() - (float)y2,l2,
+ (float)x3,height() - (float)y3,l3);
+ }
+#endif
+ } break;
+ }
+ } break;
+ case 9 : { // Textured triangle
+ if (!__color) {
+ if (render_type==5) cimg::mutex(10,0);
+ throw CImgArgumentException(_cimg_instance
+ "draw_object3d(): Undefined texture for triangle primitive [%u].",
+ cimg_instance,n_primitive);
+ }
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ n2 = (unsigned int)primitive[2];
+ const int
+ tx0 = (int)primitive[3], ty0 = (int)primitive[4],
+ tx1 = (int)primitive[5], ty1 = (int)primitive[6],
+ tx2 = (int)primitive[7], ty2 = (int)primitive[8],
+ 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<=0?0:tx0>=color.width()?color.width() - 1:tx0,
+ ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity).
+ draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1,
+ ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity).
+ draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2,
+ ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255));
+ board.drawDot((float)x0,height() - (float)y0);
+ board.drawDot((float)x1,height() - (float)y1);
+ board.drawDot((float)x2,height() - (float)y2);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity).
+ draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity).
+ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity);
+ else
+ draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity).
+ draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opacity).
+ draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*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,opacity);
+ else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*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,opacity,lightprops(l));
+ else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = std::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(opacity*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),opacity);
+ 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),opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*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),
+ opacity);
+ 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),
+ opacity);
+#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)(opacity*255));
+ board.fillGouraudTriangle((float)x0,height() - (float)y0,l0,
+ (float)x1,height() - (float)y1,l1,
+ (float)x2,height() - (float)y2,l2);
+ }
+#endif
+ break;
+ }
+ } break;
+ case 12 : { // Textured quadrangle
+ if (!__color) {
+ if (render_type==5) cimg::mutex(10,0);
+ throw CImgArgumentException(_cimg_instance
+ "draw_object3d(): Undefined texture for quadrangle primitive [%u].",
+ cimg_instance,n_primitive);
+ }
+ const unsigned int
+ n0 = (unsigned int)primitive[0],
+ n1 = (unsigned int)primitive[1],
+ n2 = (unsigned int)primitive[2],
+ n3 = (unsigned int)primitive[3];
+ const int
+ tx0 = (int)primitive[4], ty0 = (int)primitive[5],
+ tx1 = (int)primitive[6], ty1 = (int)primitive[7],
+ tx2 = (int)primitive[8], ty2 = (int)primitive[9],
+ tx3 = (int)primitive[10], ty3 = (int)primitive[11],
+ 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<=0?0:tx0>=color.width()?color.width() - 1:tx0,
+ ty0<=0?0:ty0>=color.height()?color.height() - 1:ty0)._data,opacity).
+ draw_point(x1,y1,color.get_vector_at(tx1<=0?0:tx1>=color.width()?color.width() - 1:tx1,
+ ty1<=0?0:ty1>=color.height()?color.height() - 1:ty1)._data,opacity).
+ draw_point(x2,y2,color.get_vector_at(tx2<=0?0:tx2>=color.width()?color.width() - 1:tx2,
+ ty2<=0?0:ty2>=color.height()?color.height() - 1:ty2)._data,opacity).
+ draw_point(x3,y3,color.get_vector_at(tx3<=0?0:tx3>=color.width()?color.width() - 1:tx3,
+ ty3<=0?0:ty3>=color.height()?color.height() - 1:ty3)._data,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255));
+ board.drawDot((float)x0,height() - (float)y0);
+ board.drawDot((float)x1,height() - (float)y1);
+ board.drawDot((float)x2,height() - (float)y2);
+ board.drawDot((float)x3,height() - (float)y3);
+ }
+#endif
+ break;
+ case 1 :
+ if (zbuffer)
+ draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity).
+ draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity).
+ draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity).
+ draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity);
+ else
+ draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity).
+ draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opacity).
+ draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opacity).
+ draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*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,opacity).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity);
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity).
+ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*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,opacity,lightprops(l)).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l));
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opacity,lightprops(l)).
+ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opacity,lightprops(l));
+#ifdef cimg_use_board
+ if (pboard) {
+ const float lp = std::min(lightprops(l),1);
+ board.setPenColorRGBi((unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(128*lp),
+ (unsigned char)(opacity*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,opacity).
+ draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,
+ lightprop0,lightprop2,lightprop3,opacity);
+ else
+ draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,
+ lightprop0,lightprop1,lightprop2,opacity).
+ draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,
+ lightprop0,lightprop2,lightprop3,opacity);
+#ifdef cimg_use_board
+ if (pboard) {
+ board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*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,opacity).
+ 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,opacity);
+ 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,opacity).
+ 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,opacity);
+#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)(opacity*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;
+ }
+ }
+
+ if (render_type==5) cimg::mutex(10,0);
+ return *this;
+ }
+
+ //@}
+ //---------------------------
+ //
+ //! \name Data Input
+ //@{
+ //---------------------------
+
+ //! Launch simple interface to select a shape from an image.
+ /**
+ \param disp Display window to use.
+ \param feature_type Type of feature to select. Can be <tt>{ 0=point | 1=line | 2=rectangle | 3=ellipse }</tt>.
+ \param XYZ Pointer to 3 values X,Y,Z which tells about the projection point coordinates, for volumetric images.
+ **/
+ CImg<T>& select(CImgDisplay &disp,
+ const unsigned int feature_type=2, unsigned int *const XYZ=0,
+ const bool exit_on_anykey=false) {
+ return get_select(disp,feature_type,XYZ,exit_on_anykey).move_to(*this);
+ }
+
+ //! Simple interface to select a shape from an image \overloading.
+ CImg<T>& select(const char *const title,
+ const unsigned int feature_type=2, unsigned int *const XYZ=0,
+ const bool exit_on_anykey=false) {
+ return get_select(title,feature_type,XYZ,exit_on_anykey).move_to(*this);
+ }
+
+ //! Simple interface to select a shape from an image \newinstance.
+ CImg<intT> get_select(CImgDisplay &disp,
+ const unsigned int feature_type=2, unsigned int *const XYZ=0,
+ const bool exit_on_anykey=false) const {
+ return _select(disp,0,feature_type,XYZ,0,0,0,exit_on_anykey,true,false);
+ }
+
+ //! Simple interface to select a shape from an image \newinstance.
+ CImg<intT> get_select(const char *const title,
+ const unsigned int feature_type=2, unsigned int *const XYZ=0,
+ const bool exit_on_anykey=false) const {
+ CImgDisplay disp;
+ return _select(disp,title,feature_type,XYZ,0,0,0,exit_on_anykey,true,false);
+ }
+
+ CImg<intT> _select(CImgDisplay &disp, const char *const title,
+ const unsigned int feature_type, unsigned int *const XYZ,
+ const int origX, const int origY, const int origZ,
+ const bool exit_on_anykey,
+ const bool reset_view3d,
+ const bool force_display_z_coord) const {
+ if (is_empty()) return CImg<intT>(1,feature_type==0?3:6,1,1,-1);
+ if (!disp) {
+ disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1);
+ if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum);
+ } else if (title) disp.set_title("%s",title);
+
+ CImg<T> thumb;
+ if (width()>disp.screen_width() || height()>disp.screen_height())
+ get_resize(cimg_fitscreen(width(),height(),1),1,-100).move_to(thumb);
+
+ const unsigned int old_normalization = disp.normalization();
+ bool old_is_resized = disp.is_resized();
+ disp._normalization = 0;
+ disp.show().set_key(0).set_wheel().show_mouse();
+
+ static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 };
+
+ int area = 0, starting_area = 0, clicked_area = 0, phase = 0,
+ X0 = (int)((XYZ?XYZ[0]:(_width - 1)/2)%_width),
+ Y0 = (int)((XYZ?XYZ[1]:(_height - 1)/2)%_height),
+ Z0 = (int)((XYZ?XYZ[2]:(_depth - 1)/2)%_depth),
+ X1 =-1, Y1 = -1, Z1 = -1,
+ X3d = -1, Y3d = -1,
+ oX3d = X3d, oY3d = -1,
+ omx = -1, omy = -1;
+ float X = -1, Y = -1, Z = -1;
+ unsigned int old_button = 0, key = 0;
+
+ bool shape_selected = false, text_down = false, visible_cursor = true;
+ static CImg<floatT> pose3d;
+ static bool is_view3d = false, is_axes = true;
+ if (reset_view3d) { pose3d.assign(); is_view3d = false; }
+ CImg<floatT> points3d, opacities3d, sel_opacities3d;
+ CImgList<uintT> primitives3d, sel_primitives3d;
+ CImgList<ucharT> colors3d, sel_colors3d;
+ CImg<ucharT> visu, visu0, view3d;
+ CImg<charT> text(1024); *text = 0;
+
+ while (!key && !disp.is_closed() && !shape_selected) {
+
+ // Handle mouse motion and selection
+ int
+ mx = disp.mouse_x(),
+ my = disp.mouse_y();
+
+ const float
+ mX = mx<0?-1.0f:(float)mx*(width() + (depth()>1?depth():0))/disp.width(),
+ mY = my<0?-1.0f:(float)my*(height() + (depth()>1?depth():0))/disp.height();
+
+ area = 0;
+ if (mX>=0 && mY>=0 && mX<width() && mY<height()) { area = 1; X = mX; Y = mY; Z = (float)(phase?Z1:Z0); }
+ if (mX>=0 && mX<width() && mY>=height()) { area = 2; X = mX; Z = mY - _height; Y = (float)(phase?Y1:Y0); }
+ if (mY>=0 && mX>=width() && mY<height()) { area = 3; Y = mY; Z = mX - _width; X = (float)(phase?X1:X0); }
+ if (mX>=width() && mY>=height()) area = 4;
+ if (disp.button()) { if (!clicked_area) clicked_area = area; } else clicked_area = 0;
+
+ CImg<charT> filename(32);
+
+ 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::keyA : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ is_axes = !is_axes; disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
+ CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false).
+ _is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).resize(cimg_fitscreen(_width,_height,_depth),false)._is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ is_view3d = !is_view3d; disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ static unsigned int snap_number = 0;
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++);
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ if (visu0) {
+ (+visu0).draw_text(0,0," Saving snapshot... ",foreground_color,background_color,0.7f,13).display(disp);
+ visu0.save(filename);
+ (+visu0).draw_text(0,0," Snapshot '%s' saved. ",foreground_color,background_color,0.7f,13,filename._data).
+ 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;
+ std::FILE *file;
+ do {
+#ifdef cimg_use_zlib
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+visu0).draw_text(0,0," Saving instance... ",foreground_color,background_color,0.7f,13).display(disp);
+ save(filename);
+ (+visu0).draw_text(0,0," Instance '%s' saved. ",foreground_color,background_color,0.7f,13,filename._data).
+ display(disp);
+ disp.set_key(key,false); key = 0;
+ } break;
+ }
+
+ switch (area) {
+
+ case 0 : // When mouse is out of image range.
+ mx = my = -1; X = Y = Z = -1;
+ break;
+
+ case 1 : case 2 : case 3 : // When mouse is over the XY,XZ or YZ projections.
+ if (disp.button()&1 && phase<2 && clicked_area==area) { // When selection has been started (1st step).
+ if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign();
+ X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z;
+ }
+ if (!(disp.button()&1) && phase>=2 && clicked_area!=area) { // When selection is at 2nd step (for volumes).
+ switch (starting_area) {
+ case 1 : if (Z1!=(int)Z) visu0.assign(); Z1 = (int)Z; break;
+ case 2 : if (Y1!=(int)Y) visu0.assign(); Y1 = (int)Y; break;
+ case 3 : if (X1!=(int)X) visu0.assign(); X1 = (int)X; break;
+ }
+ }
+ if (disp.button()&2 && clicked_area==area) { // When moving through the image/volume.
+ if (phase) {
+ if (_depth>1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign();
+ X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z;
+ } else {
+ if (_depth>1 && (X0!=(int)X || Y0!=(int)Y || Z0!=(int)Z)) visu0.assign();
+ X0 = (int)X; Y0 = (int)Y; Z0 = (int)Z;
+ }
+ }
+ if (disp.button()&4) {
+ X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = area = clicked_area = starting_area = 0;
+ visu0.assign();
+ }
+ if (disp.wheel()) { // When moving through the slices of the volume (with mouse wheel).
+ if (_depth>1 && !disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT() &&
+ !disp.is_keySHIFTLEFT() && !disp.is_keySHIFTRIGHT() &&
+ !disp.is_keyALT() && !disp.is_keyALTGR()) {
+ switch (area) {
+ case 1 :
+ if (phase) Z = (float)(Z1+=disp.wheel()); else Z = (float)(Z0+=disp.wheel());
+ visu0.assign(); break;
+ case 2 :
+ if (phase) Y = (float)(Y1+=disp.wheel()); else Y = (float)(Y0+=disp.wheel());
+ visu0.assign(); break;
+ case 3 :
+ if (phase) X = (float)(X1+=disp.wheel()); else X = (float)(X0+=disp.wheel());
+ visu0.assign(); break;
+ }
+ disp.set_wheel();
+ } else key = ~0U;
+ }
+ if ((disp.button()&1)!=old_button) { // When left button has just been pressed or released.
+ switch (phase) {
+ case 0 :
+ if (area==clicked_area) {
+ X0 = X1 = (int)X; Y0 = Y1 = (int)Y; Z0 = Z1 = (int)Z; starting_area = area; ++phase;
+ } break;
+ case 1 :
+ if (area==starting_area) {
+ X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; ++phase;
+ } else if (!(disp.button()&1)) { X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = 0; visu0.assign(); }
+ break;
+ case 2 : ++phase; break;
+ }
+ old_button = disp.button()&1;
+ }
+ break;
+
+ case 4 : // When mouse is over the 3d view.
+ if (is_view3d && points3d) {
+ X3d = mx - width()*disp.width()/(width() + (depth()>1?depth():0));
+ Y3d = my - height()*disp.height()/(height() + (depth()>1?depth():0));
+ if (oX3d<0) { oX3d = X3d; oY3d = Y3d; }
+ // Left + right buttons: reset.
+ if ((disp.button()&3)==3) { pose3d.assign(); view3d.assign(); oX3d = oY3d = X3d = Y3d = -1; }
+ else if (disp.button()&1 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Left button: rotate.
+ const float
+ R = 0.45f*std::min(view3d._width,view3d._height),
+ R2 = R*R,
+ u0 = (float)(oX3d - view3d.width()/2),
+ v0 = (float)(oY3d - view3d.height()/2),
+ u1 = (float)(X3d - view3d.width()/2),
+ v1 = (float)(Y3d - view3d.height()/2),
+ n0 = cimg::hypot(u0,v0),
+ n1 = cimg::hypot(u1,v1),
+ nu0 = n0>R?(u0*R/n0):u0,
+ nv0 = n0>R?(v0*R/n0):v0,
+ nw0 = (float)std::sqrt(std::max(0.0f,R2 - nu0*nu0 - nv0*nv0)),
+ nu1 = n1>R?(u1*R/n1):u1,
+ nv1 = n1>R?(v1*R/n1):v1,
+ nw1 = (float)std::sqrt(std::max(0.0f,R2 - nu1*nu1 - nv1*nv1)),
+ u = nv0*nw1 - nw0*nv1,
+ v = nw0*nu1 - nu0*nw1,
+ w = nv0*nu1 - nu0*nv1,
+ n = cimg::hypot(u,v,w),
+ alpha = (float)std::asin(n/R2)*180/cimg::PI;
+ pose3d.draw_image(CImg<floatT>::rotation_matrix(u,v,w,-alpha)*pose3d.get_crop(0,0,2,2));
+ view3d.assign();
+ } else if (disp.button()&2 && pose3d && oY3d!=Y3d) { // Right button: zoom.
+ pose3d(3,2)-=(oY3d - Y3d)*1.5f; view3d.assign();
+ }
+ if (disp.wheel()) { // Wheel: zoom
+ pose3d(3,2)-=disp.wheel()*15; view3d.assign(); disp.set_wheel();
+ }
+ if (disp.button()&4 && pose3d && (oX3d!=X3d || oY3d!=Y3d)) { // Middle button: shift.
+ pose3d(3,0)-=oX3d - X3d; pose3d(3,1)-=oY3d - Y3d; view3d.assign();
+ }
+ oX3d = X3d; oY3d = Y3d;
+ }
+ mx = my = -1; X = Y = Z = -1;
+ break;
+ }
+
+ if (phase) {
+ if (!feature_type) shape_selected = phase?true:false;
+ else {
+ if (_depth>1) shape_selected = (phase==3)?true:false;
+ else shape_selected = (phase==2)?true:false;
+ }
+ }
+
+ if (X0<0) X0 = 0;
+ if (X0>=width()) X0 = width() - 1;
+ if (Y0<0) Y0 = 0;
+ if (Y0>=height()) Y0 = height() - 1;
+ if (Z0<0) Z0 = 0;
+ if (Z0>=depth()) Z0 = depth() - 1;
+ if (X1<1) X1 = 0;
+ if (X1>=width()) X1 = width() - 1;
+ if (Y1<0) Y1 = 0;
+ if (Y1>=height()) Y1 = height() - 1;
+ if (Z1<0) Z1 = 0;
+ if (Z1>=depth()) Z1 = depth() - 1;
+
+ // Draw visualization image on the display
+ if (mx!=omx || my!=omy || !visu0 || (_depth>1 && !view3d)) {
+
+ if (!visu0) { // Create image of projected planes.
+ if (thumb) thumb.__get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0);
+ else __get_select(disp,old_normalization,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0).move_to(visu0);
+ visu0.resize(disp);
+ view3d.assign();
+ points3d.assign();
+ }
+
+ if (is_view3d && _depth>1 && !view3d) { // Create 3d view for volumetric images.
+ const unsigned int
+ _x3d = (unsigned int)cimg::round((float)_width*visu0._width/(_width + _depth),1,1),
+ _y3d = (unsigned int)cimg::round((float)_height*visu0._height/(_height + _depth),1,1),
+ x3d = _x3d>=visu0._width?visu0._width - 1:_x3d,
+ y3d = _y3d>=visu0._height?visu0._height - 1:_y3d;
+ CImg<ucharT>(1,2,1,1,64,128).resize(visu0._width - x3d,visu0._height - y3d,1,visu0._spectrum,3).
+ move_to(view3d);
+ if (!points3d) {
+ get_projections3d(primitives3d,colors3d,phase?X1:X0,phase?Y1:Y0,phase?Z1:Z0,true).move_to(points3d);
+ points3d.append(CImg<floatT>(8,3,1,1,
+ 0,_width - 1,_width - 1,0,0,_width - 1,_width - 1,0,
+ 0,0,_height - 1,_height - 1,0,0,_height - 1,_height - 1,
+ 0,0,0,0,_depth - 1,_depth - 1,_depth - 1,_depth - 1),'x');
+ CImg<uintT>::vector(12,13).move_to(primitives3d); CImg<uintT>::vector(13,14).move_to(primitives3d);
+ CImg<uintT>::vector(14,15).move_to(primitives3d); CImg<uintT>::vector(15,12).move_to(primitives3d);
+ CImg<uintT>::vector(16,17).move_to(primitives3d); CImg<uintT>::vector(17,18).move_to(primitives3d);
+ CImg<uintT>::vector(18,19).move_to(primitives3d); CImg<uintT>::vector(19,16).move_to(primitives3d);
+ CImg<uintT>::vector(12,16).move_to(primitives3d); CImg<uintT>::vector(13,17).move_to(primitives3d);
+ CImg<uintT>::vector(14,18).move_to(primitives3d); CImg<uintT>::vector(15,19).move_to(primitives3d);
+ colors3d.insert(12,CImg<ucharT>::vector(255,255,255));
+ opacities3d.assign(primitives3d.width(),1,1,1,0.5f);
+ if (!phase) {
+ opacities3d[0] = opacities3d[1] = opacities3d[2] = 0.8f;
+ sel_primitives3d.assign();
+ sel_colors3d.assign();
+ sel_opacities3d.assign();
+ } else {
+ if (feature_type==2) {
+ points3d.append(CImg<floatT>(8,3,1,1,
+ X0,X1,X1,X0,X0,X1,X1,X0,
+ Y0,Y0,Y1,Y1,Y0,Y0,Y1,Y1,
+ Z0,Z0,Z0,Z0,Z1,Z1,Z1,Z1),'x');
+ sel_primitives3d.assign();
+ CImg<uintT>::vector(20,21).move_to(sel_primitives3d);
+ CImg<uintT>::vector(21,22).move_to(sel_primitives3d);
+ CImg<uintT>::vector(22,23).move_to(sel_primitives3d);
+ CImg<uintT>::vector(23,20).move_to(sel_primitives3d);
+ CImg<uintT>::vector(24,25).move_to(sel_primitives3d);
+ CImg<uintT>::vector(25,26).move_to(sel_primitives3d);
+ CImg<uintT>::vector(26,27).move_to(sel_primitives3d);
+ CImg<uintT>::vector(27,24).move_to(sel_primitives3d);
+ CImg<uintT>::vector(20,24).move_to(sel_primitives3d);
+ CImg<uintT>::vector(21,25).move_to(sel_primitives3d);
+ CImg<uintT>::vector(22,26).move_to(sel_primitives3d);
+ CImg<uintT>::vector(23,27).move_to(sel_primitives3d);
+ } else {
+ points3d.append(CImg<floatT>(2,3,1,1,
+ X0,X1,
+ Y0,Y1,
+ Z0,Z1),'x');
+ sel_primitives3d.assign(CImg<uintT>::vector(20,21));
+ }
+ sel_colors3d.assign(sel_primitives3d._width,CImg<ucharT>::vector(255,255,255));
+ sel_opacities3d.assign(sel_primitives3d._width,1,1,1,0.8f);
+ }
+ points3d.shift_object3d(-0.5f*(_width - 1),-0.5f*(_height - 1),-0.5f*(_depth - 1)).resize_object3d();
+ points3d*=0.75f*std::min(view3d._width,view3d._height);
+ }
+
+ if (!pose3d) CImg<floatT>(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose3d);
+ CImg<floatT> zbuffer3d(view3d._width,view3d._height,1,1,0);
+ const CImg<floatT> rotated_points3d = pose3d.get_crop(0,0,2,2)*points3d;
+ if (sel_primitives3d)
+ view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width,
+ pose3d(3,1) + 0.5f*view3d._height,
+ pose3d(3,2),
+ rotated_points3d,sel_primitives3d,sel_colors3d,sel_opacities3d,
+ 2,true,500,0,0,0,0,0,zbuffer3d);
+ view3d.draw_object3d(pose3d(3,0) + 0.5f*view3d._width,
+ pose3d(3,1) + 0.5f*view3d._height,
+ pose3d(3,2),
+ rotated_points3d,primitives3d,colors3d,opacities3d,
+ 2,true,500,0,0,0,0,0,zbuffer3d);
+ visu0.draw_image(x3d,y3d,view3d);
+ }
+ visu = visu0;
+
+ if (X<0 || Y<0 || Z<0) { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }}
+ else {
+ if (is_axes) { if (visible_cursor) { disp.hide_mouse(); visible_cursor = false; }}
+ else { if (!visible_cursor) { disp.show_mouse(); visible_cursor = true; }}
+ const int d = (depth()>1)?depth():0;
+ int
+ _vX = (int)X, _vY = (int)Y, _vZ = (int)Z,
+ w = disp.width(), W = width() + d,
+ h = disp.height(), H = height() + d,
+ _xp = (int)(_vX*(float)w/W), xp = _xp + ((int)(_xp*(float)W/w)!=_vX),
+ _yp = (int)(_vY*(float)h/H), yp = _yp + ((int)(_yp*(float)H/h)!=_vY),
+ _xn = (int)((_vX + 1.0f)*w/W - 1), xn = _xn + ((int)((_xn + 1.0f)*W/w)!=_vX + 1),
+ _yn = (int)((_vY + 1.0f)*h/H - 1), yn = _yn + ((int)((_yn + 1.0f)*H/h)!=_vY + 1),
+ _zxp = (int)((_vZ + width())*(float)w/W), zxp = _zxp + ((int)(_zxp*(float)W/w)!=_vZ + width()),
+ _zyp = (int)((_vZ + height())*(float)h/H), zyp = _zyp + ((int)(_zyp*(float)H/h)!=_vZ + height()),
+ _zxn = (int)((_vZ + width() + 1.0f)*w/W - 1),
+ zxn = _zxn + ((int)((_zxn + 1.0f)*W/w)!=_vZ + width() + 1),
+ _zyn = (int)((_vZ + height() + 1.0f)*h/H - 1),
+ zyn = _zyn + ((int)((_zyn + 1.0f)*H/h)!=_vZ + height() + 1),
+ _xM = (int)(width()*(float)w/W - 1), xM = _xM + ((int)((_xM + 1.0f)*W/w)!=width()),
+ _yM = (int)(height()*(float)h/H - 1), yM = _yM + ((int)((_yM + 1.0f)*H/h)!=height()),
+ xc = (xp + xn)/2,
+ yc = (yp + yn)/2,
+ zxc = (zxp + zxn)/2,
+ zyc = (zyp + zyn)/2,
+ xf = (int)(X*w/W),
+ yf = (int)(Y*h/H),
+ zxf = (int)((Z + width())*w/W),
+ zyf = (int)((Z + height())*h/H);
+
+ if (is_axes) { // Draw axes.
+ visu.draw_line(0,yf,visu.width() - 1,yf,foreground_color,0.7f,0xFF00FF00).
+ draw_line(0,yf,visu.width() - 1,yf,background_color,0.7f,0x00FF00FF).
+ draw_line(xf,0,xf,visu.height() - 1,foreground_color,0.7f,0xFF00FF00).
+ draw_line(xf,0,xf,visu.height() - 1,background_color,0.7f,0x00FF00FF);
+ if (_depth>1)
+ visu.draw_line(zxf,0,zxf,yM,foreground_color,0.7f,0xFF00FF00).
+ draw_line(zxf,0,zxf,yM,background_color,0.7f,0x00FF00FF).
+ draw_line(0,zyf,xM,zyf,foreground_color,0.7f,0xFF00FF00).
+ draw_line(0,zyf,xM,zyf,background_color,0.7f,0x00FF00FF);
+ }
+
+ // Draw box cursor.
+ if (xn - xp>=4 && yn - yp>=4) visu.draw_rectangle(xp,yp,xn,yn,foreground_color,0.2f).
+ draw_rectangle(xp,yp,xn,yn,foreground_color,1,0xAAAAAAAA).
+ draw_rectangle(xp,yp,xn,yn,background_color,1,0x55555555);
+ if (_depth>1) {
+ if (yn - yp>=4 && zxn - zxp>=4) visu.draw_rectangle(zxp,yp,zxn,yn,background_color,0.2f).
+ draw_rectangle(zxp,yp,zxn,yn,foreground_color,1,0xAAAAAAAA).
+ draw_rectangle(zxp,yp,zxn,yn,background_color,1,0x55555555);
+ if (xn - xp>=4 && zyn - zyp>=4) visu.draw_rectangle(xp,zyp,xn,zyn,background_color,0.2f).
+ draw_rectangle(xp,zyp,xn,zyn,foreground_color,1,0xAAAAAAAA).
+ draw_rectangle(xp,zyp,xn,zyn,background_color,1,0x55555555);
+ }
+
+ // Draw selection.
+ if (phase) {
+ const int
+ _xp0 = (int)(X0*(float)w/W), xp0 = _xp0 + ((int)(_xp0*(float)W/w)!=X0),
+ _yp0 = (int)(Y0*(float)h/H), yp0 = _yp0 + ((int)(_yp0*(float)H/h)!=Y0),
+ _xn0 = (int)((X0 + 1.0f)*w/W - 1), xn0 = _xn0 + ((int)((_xn0 + 1.0f)*W/w)!=X0 + 1),
+ _yn0 = (int)((Y0 + 1.0f)*h/H - 1), yn0 = _yn0 + ((int)((_yn0 + 1.0f)*H/h)!=Y0 + 1),
+ _zxp0 = (int)((Z0 + width())*(float)w/W), zxp0 = _zxp0 + ((int)(_zxp0*(float)W/w)!=Z0 + width()),
+ _zyp0 = (int)((Z0 + height())*(float)h/H), zyp0 = _zyp0 + ((int)(_zyp0*(float)H/h)!=Z0 + height()),
+ _zxn0 = (int)((Z0 + width() + 1.0f)*w/W - 1),
+ zxn0 = _zxn0 + ((int)((_zxn0 + 1.0f)*W/w)!=Z0 + width() + 1),
+ _zyn0 = (int)((Z0 + height() + 1.0f)*h/H - 1),
+ zyn0 = _zyn0 + ((int)((_zyn0 + 1.0f)*H/h)!=Z0 + height() + 1),
+ xc0 = (xp0 + xn0)/2,
+ yc0 = (yp0 + yn0)/2,
+ zxc0 = (zxp0 + zxn0)/2,
+ zyc0 = (zyp0 + zyn0)/2;
+
+ switch (feature_type) {
+ case 1 : {
+ visu.draw_arrow(xc0,yc0,xc,yc,background_color,0.9f,30,5,0x55555555).
+ draw_arrow(xc0,yc0,xc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA);
+ if (d) {
+ visu.draw_arrow(zxc0,yc0,zxc,yc,background_color,0.9f,30,5,0x55555555).
+ draw_arrow(zxc0,yc0,zxc,yc,foreground_color,0.9f,30,5,0xAAAAAAAA).
+ draw_arrow(xc0,zyc0,xc,zyc,background_color,0.9f,30,5,0x55555555).
+ draw_arrow(xc0,zyc0,xc,zyc,foreground_color,0.9f,30,5,0xAAAAAAAA);
+ }
+ } break;
+ case 2 : {
+ visu.draw_rectangle(X0<X1?xp0:xp,Y0<Y1?yp0:yp,X0<X1?xn:xn0,Y0<Y1?yn:yn0,background_color,0.2f).
+ draw_rectangle(X0<X1?xp0:xp,Y0<Y1?yp0:yp,X0<X1?xn:xn0,Y0<Y1?yn:yn0,foreground_color,0.9f,0xAAAAAAAA).
+ draw_rectangle(X0<X1?xp0:xp,Y0<Y1?yp0:yp,X0<X1?xn:xn0,Y0<Y1?yn:yn0,background_color,0.9f,0x55555555);
+ if (d) {
+ visu.draw_rectangle(Z0<Z1?zxp0:zxp,Y0<Y1?yp0:yp,Z0<Z1?zxn:zxn0,Y0<Y1?yn:yn0,background_color,0.2f).
+ draw_rectangle(Z0<Z1?zxp0:zxp,Y0<Y1?yp0:yp,Z0<Z1?zxn:zxn0,Y0<Y1?yn:yn0,
+ foreground_color,0.9f,0xAAAAAAAA).
+ draw_rectangle(Z0<Z1?zxp0:zxp,Y0<Y1?yp0:yp,Z0<Z1?zxn:zxn0,Y0<Y1?yn:yn0,
+ background_color,0.9f,0x55555555).
+ draw_rectangle(X0<X1?xp0:xp,Z0<Z1?zyp0:zyp,X0<X1?xn:xn0,Z0<Z1?zyn:zyn0,
+ background_color,0.2f).
+ draw_rectangle(X0<X1?xp0:xp,Z0<Z1?zyp0:zyp,X0<X1?xn:xn0,Z0<Z1?zyn:zyn0,
+ foreground_color,0.9f,0xAAAAAAAA).
+ draw_rectangle(X0<X1?xp0:xp,Z0<Z1?zyp0:zyp,X0<X1?xn:xn0,Z0<Z1?zyn:zyn0,
+ background_color,0.9f,0x55555555);
+ }
+ } break;
+ case 3 : {
+ visu.draw_ellipse(xc0,yc0,
+ (float)cimg::abs(xc - xc0),
+ (float)cimg::abs(yc - yc0),0,background_color,0.2f).
+ draw_ellipse(xc0,yc0,
+ (float)cimg::abs(xc - xc0),
+ (float)cimg::abs(yc - yc0),0,foreground_color,0.9f,~0U).
+ draw_point(xc0,yc0,foreground_color,0.9f);
+ if (d) {
+ visu.draw_ellipse(zxc0,yc0,(float)cimg::abs(zxc - zxc0),(float)cimg::abs(yc - yc0),0,
+ background_color,0.2f).
+ draw_ellipse(zxc0,yc0,(float)cimg::abs(zxc - zxc0),(float)cimg::abs(yc - yc0),0,
+ foreground_color,0.9f,~0U).
+ draw_point(zxc0,yc0,foreground_color,0.9f).
+ draw_ellipse(xc0,zyc0,(float)cimg::abs(xc - xc0),(float)cimg::abs(zyc - zyc0),0,
+ background_color,0.2f).
+ draw_ellipse(xc0,zyc0,(float)cimg::abs(xc - xc0),(float)cimg::abs(zyc - zyc0),0,
+ foreground_color,0.9f,~0U).
+ draw_point(xc0,zyc0,foreground_color,0.9f);
+ }
+ } break;
+ }
+ }
+
+ // Draw text info.
+ if (my>=0 && my<13) text_down = true; else if (my>=visu.height() - 13) text_down = false;
+ if (!feature_type || !phase) {
+ if (X>=0 && Y>=0 && Z>=0 && X<width() && Y<height() && Z<depth()) {
+ if (_depth>1 || force_display_z_coord)
+ cimg_snprintf(text,text._width," Point (%d,%d,%d) = [ ",origX + (int)X,origY + (int)Y,origZ + (int)Z);
+ else cimg_snprintf(text,text._width," Point (%d,%d) = [ ",origX + (int)X,origY + (int)Y);
+ char *ctext = text._data + std::strlen(text), *const ltext = text._data + 512;
+ for (unsigned int c = 0; c<_spectrum && ctext<ltext; ++c) {
+ cimg_snprintf(ctext,text._width/2,cimg::type<T>::format_s(),
+ cimg::type<T>::format((*this)((int)X,(int)Y,(int)Z,c)));
+ ctext = text._data + std::strlen(text);
+ *(ctext++) = ' '; *ctext = 0;
+ }
+ std::strcpy(text._data + std::strlen(text),"] ");
+ }
+ } else switch (feature_type) {
+ case 1 : {
+ const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1),
+ norm = cimg::hypot(dX,dY,dZ);
+ if (_depth>1 || force_display_z_coord)
+ cimg_snprintf(text,text._width," 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,text._width," Vect (%d,%d)-(%d,%d), Norm = %g ",
+ origX + X0,origY + Y0,origX + X1,origY + Y1,norm);
+ } break;
+ case 2 :
+ if (_depth>1 || force_display_z_coord)
+ cimg_snprintf(text,text._width," 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,text._width," 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 || force_display_z_coord)
+ cimg_snprintf(text,text._width," 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,text._width," Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d) ",
+ origX + X0,origY + Y0,origX + X1,origY + Y1,
+ 1 + cimg::abs(X0 - X1),1 + cimg::abs(Y0 - Y1));
+ }
+ if (phase || (mx>=0 && my>=0))
+ visu.draw_text(0,text_down?visu.height() - 13:0,text,foreground_color,background_color,0.7f,13);
+ }
+
+ disp.display(visu).wait();
+ } else if (!shape_selected) disp.wait();
+ if (disp.is_resized()) { disp.resize(false)._is_resized = false; old_is_resized = true; visu0.assign(); }
+ omx = mx; omy = my;
+ if (!exit_on_anykey && key && key!=cimg::keyESC &&
+ (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
+ key = 0;
+ }
+ }
+
+ // Return result.
+ CImg<intT> res(1,feature_type==0?3:6,1,1,-1);
+ if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; }
+ if (shape_selected) {
+ if (feature_type==2) {
+ if (X0>X1) cimg::swap(X0,X1);
+ if (Y0>Y1) cimg::swap(Y0,Y1);
+ if (Z0>Z1) cimg::swap(Z0,Z1);
+ }
+ if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1;
+ switch (feature_type) {
+ case 1 : case 2 : res[0] = X0; res[1] = Y0; res[2] = Z0; res[3] = X1; res[4] = Y1; res[5] = Z1; break;
+ case 3 :
+ res[3] = cimg::abs(X1 - X0); res[4] = cimg::abs(Y1 - Y0); res[5] = cimg::abs(Z1 - Z0); // keep no break here!
+ default : res[0] = X0; res[1] = Y0; res[2] = Z0;
+ }
+ }
+ if (!exit_on_anykey || !(disp.button()&4)) disp.set_button();
+ if (!visible_cursor) disp.show_mouse();
+ disp._normalization = old_normalization;
+ disp._is_resized = old_is_resized;
+ if (key!=~0U) disp.set_key(key);
+ return res;
+ }
+
+ // Return a visualizable uchar8 image for display routines.
+ CImg<ucharT> __get_select(const CImgDisplay& disp, const int normalization,
+ const int x, const int y, const int z) const {
+ if (is_empty()) return CImg<ucharT>(1,1,1,1,0);
+ const CImg<T> crop = get_shared_channels(0,std::min(2,spectrum() - 1));
+ CImg<Tuchar> img2d;
+ if (_depth>1) {
+ const int mdisp = std::min(disp.screen_width(),disp.screen_height());
+ if (depth()>mdisp) {
+ crop.get_resize(-100,-100,mdisp,-100,0).move_to(img2d);
+ img2d.projections2d(x,y,z*img2d._depth/_depth);
+ } else crop.get_projections2d(x,y,z).move_to(img2d);
+ } else CImg<Tuchar>(crop,false).move_to(img2d);
+
+ // Check for inf and NaN values.
+ if (cimg::type<T>::is_float() && normalization) {
+ bool is_inf = false, is_nan = false;
+ cimg_for(img2d,ptr,Tuchar)
+ if (cimg::type<T>::is_inf(*ptr)) { is_inf = true; break; }
+ else if (cimg::type<T>::is_nan(*ptr)) { is_nan = true; break; }
+ if (is_inf || is_nan) {
+ Tint m0 = (Tint)cimg::type<T>::max(), M0 = (Tint)cimg::type<T>::min();
+ if (!normalization) { m0 = 0; M0 = 255; }
+ else if (normalization==2) { m0 = (Tint)disp._min; M0 = (Tint)disp._max; }
+ else
+ cimg_for(img2d,ptr,Tuchar)
+ if (!cimg::type<T>::is_inf(*ptr) && !cimg::type<T>::is_nan(*ptr)) {
+ if (*ptr<(Tuchar)m0) m0 = *ptr;
+ if (*ptr>(Tuchar)M0) M0 = *ptr;
+ }
+ const T
+ val_minf = (T)(normalization==1 || normalization==3?m0 - (M0 - m0)*20 - 1:m0),
+ val_pinf = (T)(normalization==1 || normalization==3?M0 + (M0 - m0)*20 + 1:M0);
+ if (is_nan)
+ cimg_for(img2d,ptr,Tuchar)
+ if (cimg::type<T>::is_nan(*ptr)) *ptr = val_minf; // Replace NaN values.
+ if (is_inf)
+ cimg_for(img2d,ptr,Tuchar)
+ if (cimg::type<T>::is_inf(*ptr)) *ptr = (float)*ptr<0?val_minf:val_pinf; // Replace +-inf values.
+ }
+ }
+
+ switch (normalization) {
+ case 1 : img2d.normalize((T)0,(T)255); break;
+ case 2 : {
+ const float m = disp._min, M = disp._max;
+ (img2d-=m)*=255.0f/(M - m>0?M - m:1);
+ } break;
+ case 3 :
+ if (cimg::type<T>::is_float()) img2d.normalize((T)0,(T)255);
+ else {
+ const float m = (float)cimg::type<T>::min(), M = (float)cimg::type<T>::max();
+ (img2d-=m)*=255.0f/(M - m>0?M - m:1);
+ } break;
+ }
+
+ if (img2d.spectrum()==2) img2d.channels(0,2);
+ return img2d;
+ }
+
+ //! 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 bool exit_on_anykey=false) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "select_graph(): Empty instance.",
+ cimg_instance);
+ if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0).
+ set_title("CImg<%s>",pixel_type());
+ const ulongT siz = (ulongT)_width*_height*_depth;
+ const unsigned int old_normalization = disp.normalization();
+ disp.show().set_button().set_wheel()._normalization = 0;
+
+ double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax;
+ if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; }
+ if (nymin==nymax) { --nymin; ++nymax; }
+ if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.0; }
+
+ static const unsigned char black[] = { 0, 0, 0 }, white[] = { 255, 255, 255 }, gray[] = { 220, 220, 220 };
+ static const unsigned char gray2[] = { 110, 110, 110 }, ngray[] = { 35, 35, 35 };
+ static unsigned int odimv = 0;
+ static CImg<ucharT> colormap;
+ if (odimv!=_spectrum) {
+ odimv = _spectrum;
+ colormap = CImg<ucharT>(3,_spectrum,1,1,120).noise(70,1);
+ if (_spectrum==1) { colormap[0] = colormap[1] = 120; colormap[2] = 200; }
+ else {
+ colormap(0,0) = 220; colormap(1,0) = 10; colormap(2,0) = 10;
+ if (_spectrum>1) { colormap(0,1) = 10; colormap(1,1) = 220; colormap(2,1) = 10; }
+ if (_spectrum>2) { colormap(0,2) = 10; colormap(1,2) = 10; colormap(2,2) = 220; }
+ }
+ }
+
+ CImg<ucharT> visu0, visu, graph, text, axes;
+ int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2;
+ const unsigned int one = plot_type==3?0U:1U;
+ unsigned int okey = 0, obutton = 0;
+ CImg<charT> message(1024);
+ CImg_3x3(I,unsigned char);
+
+ for (bool selected = false; !selected && !disp.is_closed() && !okey && !disp.wheel(); ) {
+ const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y();
+ const unsigned int key = disp.key(), button = disp.button();
+
+ // Generate graph representation.
+ if (!visu0) {
+ visu0.assign(disp.width(),disp.height(),1,3,220);
+ const int gdimx = disp.width() - 32, gdimy = disp.height() - 32;
+ if (gdimx>0 && gdimy>0) {
+ graph.assign(gdimx,gdimy,1,3,255);
+ if (siz<32) {
+ if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0,
+ false,true,black,0.2f,0x33333333,0x33333333);
+ } else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333);
+ cimg_forC(*this,c)
+ graph.draw_graph(get_shared_channel(c),&colormap(0,c),(plot_type!=3 || _spectrum==1)?1:0.6f,
+ plot_type,vertex_type,nymax,nymin);
+
+ axes.assign(gdimx,gdimy,1,1,0);
+ const float
+ dx = (float)cimg::abs(nxmax - nxmin), dy = (float)cimg::abs(nymax - nymin),
+ px = (float)std::pow(10.0,(int)std::log10(dx?dx:1) - 2.0),
+ py = (float)std::pow(10.0,(int)std::log10(dy?dy:1) - 2.0);
+ const CImg<Tdouble>
+ seqx = dx<=0?CImg<Tdouble>::vector(nxmin):
+ CImg<Tdouble>::sequence(1 + gdimx/60,nxmin,one?nxmax:nxmin + (nxmax - nxmin)*(siz + 1)/siz).round(px),
+ seqy = CImg<Tdouble>::sequence(1 + gdimy/60,nymax,nymin).round(py);
+
+ const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0);
+ axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero);
+ if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero);
+ if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero);
+ if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero);
+ if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero);
+
+ cimg_for3x3(axes,x,y,0,0,I,unsigned char)
+ if (Icc) {
+ if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0;
+ else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3);
+ }
+ else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp)
+ cimg_forC(graph,c) graph(x,y,c) = (unsigned char)((graph(x,y,c) + 511)/3);
+
+ visu0.draw_image(16,16,graph);
+ visu0.draw_line(15,15,16 + gdimx,15,gray2).draw_line(16 + gdimx,15,16 + gdimx,16 + gdimy,gray2).
+ draw_line(16 + gdimx,16 + gdimy,15,16 + gdimy,white).draw_line(15,16 + gdimy,15,15,white);
+ } else graph.assign();
+ text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1,13).resize(-100,-100,1,3);
+ visu0.draw_image((visu0.width() - text.width())/2,visu0.height() - 14,~text);
+ text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3);
+ visu0.draw_image(1,(visu0.height() - text.height())/2,~text);
+ visu.assign();
+ }
+
+ // Generate and display current view.
+ if (!visu) {
+ visu.assign(visu0);
+ if (graph && x0>=0 && x1>=0) {
+ const int
+ nx0 = x0<=x1?x0:x1,
+ nx1 = x0<=x1?x1:x0,
+ ny0 = y0<=y1?y0:y1,
+ ny1 = y0<=y1?y1:y0,
+ sx0 = (int)(16 + nx0*(visu.width() - 32)/std::max((ulongT)1,siz - one)),
+ sx1 = (int)(15 + (nx1 + 1)*(visu.width() - 32)/std::max((ulongT)1,siz - one)),
+ sy0 = 16 + ny0,
+ sy1 = 16 + ny1;
+ if (y0>=0 && y1>=0)
+ visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU);
+ else visu.draw_rectangle(sx0,0,sx1,visu.height() - 17,gray,0.5f).
+ draw_line(sx0,16,sx0,visu.height() - 17,black,0.5f,0xCCCCCCCCU).
+ draw_line(sx1,16,sx1,visu.height() - 17,black,0.5f,0xCCCCCCCCU);
+ }
+ if (mouse_x>=16 && mouse_y>=16 && mouse_x<visu.width() - 16 && mouse_y<visu.height() - 16) {
+ if (graph) visu.draw_line(mouse_x,16,mouse_x,visu.height() - 17,black,0.5f,0x55555555U);
+ const unsigned int
+ x = (unsigned int)cimg::round((mouse_x - 16.0f)*(siz - one)/(disp.width() - 32),1,one?0:-1);
+ const double cx = nxmin + x*(nxmax - nxmin)/std::max((ulongT)1,siz - 1);
+ if (_spectrum>=7)
+ cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx,
+ (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2),
+ (double)(*this)(x,0,0,_spectrum - 4),(double)(*this)(x,0,0,_spectrum - 3),
+ (double)(*this)(x,0,0,_spectrum - 1));
+ else {
+ cimg_snprintf(message,message._width,"Value[%u:%g] = ( ",x,cx);
+ cimg_forC(*this,c) cimg_sprintf(message._data + std::strlen(message),"%g ",(double)(*this)(x,0,0,c));
+ cimg_sprintf(message._data + std::strlen(message),")");
+ }
+ if (x0>=0 && x1>=0) {
+ const unsigned int
+ nx0 = (unsigned int)(x0<=x1?x0:x1),
+ nx1 = (unsigned int)(x0<=x1?x1:x0),
+ ny0 = (unsigned int)(y0<=y1?y0:y1),
+ ny1 = (unsigned int)(y0<=y1?y1:y0);
+ const double
+ cx0 = nxmin + nx0*(nxmax - nxmin)/std::max((ulongT)1,siz - 1),
+ cx1 = nxmin + (nx1 + one)*(nxmax - nxmin)/std::max((ulongT)1,siz - 1),
+ cy0 = nymax - ny0*(nymax - nymin)/(visu._height - 32),
+ cy1 = nymax - ny1*(nymax - nymin)/(visu._height - 32);
+ if (y0>=0 && y1>=0)
+ cimg_sprintf(message._data + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )",
+ x0,cx0,cy0,x1 + one,cx1,cy1);
+ else
+ cimg_sprintf(message._data + std::strlen(message)," - Range [ %u:%g - %u:%g ]",
+ x0,cx0,x1 + one,cx1);
+ }
+ text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3);
+ visu.draw_image((visu.width() - text.width())/2,1,~text);
+ }
+ visu.display(disp);
+ }
+
+ // Test keys.
+ CImg<charT> filename(32);
+ 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(CImgDisplay::screen_width()/2,
+ CImgDisplay::screen_height()/2,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;
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++);
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+screen).draw_text(0,0," Saving snapshot... ",black,gray,1,13).display(disp);
+ screen.save(filename);
+ (+screen).draw_text(0,0," Snapshot '%s' saved. ",black,gray,1,13,filename._data).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;
+ std::FILE *file;
+ do {
+#ifdef cimg_use_zlib
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+screen).draw_text(0,0," Saving instance... ",black,gray,1,13).display(disp);
+ save(filename);
+ (+screen).draw_text(0,0," Instance '%s' saved. ",black,gray,1,13,filename._data).display(disp);
+ }
+ disp.set_key(key,false); okey = 0;
+ } break;
+ }
+
+ // Handle mouse motion and mouse buttons
+ if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) {
+ visu.assign();
+ if (disp.mouse_x()>=0 && disp.mouse_y()>=0) {
+ const int
+ mx = (mouse_x - 16)*(int)(siz - one)/(disp.width() - 32),
+ cx = cimg::cut(mx,0,(int)(siz - 1 - one)),
+ my = mouse_y - 16,
+ cy = cimg::cut(my,0,disp.height() - 32);
+ if (button&1) {
+ if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; }
+ }
+ else if (button&2) {
+ if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; }
+ }
+ else if (obutton) { x1 = x1>=0?cx:-1; y1 = y1>=0?cy:-1; selected = true; }
+ } else if (!button && obutton) selected = true;
+ obutton = button; omouse_x = mouse_x; omouse_y = mouse_y;
+ }
+ if (disp.is_resized()) { disp.resize(false); visu0.assign(); }
+ if (visu && visu0) disp.wait();
+ if (!exit_on_anykey && okey && okey!=cimg::keyESC &&
+ (okey!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
+ disp.set_key(key,false);
+ okey = 0;
+ }
+ }
+
+ disp._normalization = old_normalization;
+ if (x1>=0 && x1<x0) cimg::swap(x0,x1);
+ if (y1<y0) cimg::swap(y0,y1);
+ disp.set_key(okey);
+ return CImg<intT>(4,1,1,1,x0,y0,x1>=0?x1 + (int)one:-1,y1);
+ }
+
+ //! Load image from a file.
+ /**
+ \param filename Filename, as a C-string.
+ \note The extension of \c filename defines the file format. If no filename
+ extension is provided, CImg<T>::get_load() will try to load the file as a .cimg or .cimgz file.
+ **/
+ CImg<T>& load(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load(): Specified filename is (null).",
+ cimg_instance);
+
+ if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) {
+ CImg<charT> filename_local(256);
+ load(cimg::load_network(filename,filename_local));
+ std::remove(filename_local);
+ return *this;
+ }
+
+ const char *const ext = cimg::split_filename(filename);
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ bool is_loaded = true;
+ try {
+#ifdef cimg_load_plugin
+ cimg_load_plugin(filename);
+#endif
+#ifdef cimg_load_plugin1
+ cimg_load_plugin1(filename);
+#endif
+#ifdef cimg_load_plugin2
+ cimg_load_plugin2(filename);
+#endif
+#ifdef cimg_load_plugin3
+ cimg_load_plugin3(filename);
+#endif
+#ifdef cimg_load_plugin4
+ cimg_load_plugin4(filename);
+#endif
+#ifdef cimg_load_plugin5
+ cimg_load_plugin5(filename);
+#endif
+#ifdef cimg_load_plugin6
+ cimg_load_plugin6(filename);
+#endif
+#ifdef cimg_load_plugin7
+ cimg_load_plugin7(filename);
+#endif
+#ifdef cimg_load_plugin8
+ cimg_load_plugin8(filename);
+#endif
+ // Ascii formats
+ if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename);
+ else if (!cimg::strcasecmp(ext,"dlm") ||
+ !cimg::strcasecmp(ext,"txt")) load_dlm(filename);
+
+ // 2d binary formats
+ else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename);
+ else if (!cimg::strcasecmp(ext,"jpg") ||
+ !cimg::strcasecmp(ext,"jpeg") ||
+ !cimg::strcasecmp(ext,"jpe") ||
+ !cimg::strcasecmp(ext,"jfif") ||
+ !cimg::strcasecmp(ext,"jif")) load_jpeg(filename);
+ else if (!cimg::strcasecmp(ext,"png")) load_png(filename);
+ else if (!cimg::strcasecmp(ext,"ppm") ||
+ !cimg::strcasecmp(ext,"pgm") ||
+ !cimg::strcasecmp(ext,"pnm") ||
+ !cimg::strcasecmp(ext,"pbm") ||
+ !cimg::strcasecmp(ext,"pnk")) load_pnm(filename);
+ else if (!cimg::strcasecmp(ext,"pfm")) load_pfm(filename);
+ else if (!cimg::strcasecmp(ext,"tif") ||
+ !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+ else if (!cimg::strcasecmp(ext,"exr")) load_exr(filename);
+ else if (!cimg::strcasecmp(ext,"cr2") ||
+ !cimg::strcasecmp(ext,"crw") ||
+ !cimg::strcasecmp(ext,"dcr") ||
+ !cimg::strcasecmp(ext,"mrw") ||
+ !cimg::strcasecmp(ext,"nef") ||
+ !cimg::strcasecmp(ext,"orf") ||
+ !cimg::strcasecmp(ext,"pix") ||
+ !cimg::strcasecmp(ext,"ptx") ||
+ !cimg::strcasecmp(ext,"raf") ||
+ !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename);
+ else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename);
+
+ // 3d binary formats
+ else if (!cimg::strcasecmp(ext,"dcm") ||
+ !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename);
+ else if (!cimg::strcasecmp(ext,"hdr") ||
+ !cimg::strcasecmp(ext,"nii")) load_analyze(filename);
+ else if (!cimg::strcasecmp(ext,"par") ||
+ !cimg::strcasecmp(ext,"rec")) load_parrec(filename);
+ else if (!cimg::strcasecmp(ext,"mnc")) load_minc2(filename);
+ else if (!cimg::strcasecmp(ext,"inr")) load_inr(filename);
+ else if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename);
+ else if (!cimg::strcasecmp(ext,"cimg") ||
+ !cimg::strcasecmp(ext,"cimgz") ||
+ !*ext) return load_cimg(filename);
+
+ // Archive files
+ else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+
+ // Image sequences
+ else if (!cimg::strcasecmp(ext,"avi") ||
+ !cimg::strcasecmp(ext,"mov") ||
+ !cimg::strcasecmp(ext,"asf") ||
+ !cimg::strcasecmp(ext,"divx") ||
+ !cimg::strcasecmp(ext,"flv") ||
+ !cimg::strcasecmp(ext,"mpg") ||
+ !cimg::strcasecmp(ext,"m1v") ||
+ !cimg::strcasecmp(ext,"m2v") ||
+ !cimg::strcasecmp(ext,"m4v") ||
+ !cimg::strcasecmp(ext,"mjp") ||
+ !cimg::strcasecmp(ext,"mp4") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"ogg") ||
+ !cimg::strcasecmp(ext,"ogv") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) load_video(filename);
+ else is_loaded = false;
+ } catch (CImgIOException&) { is_loaded = false; }
+
+ // If nothing loaded, try to guess file format from magic number in file.
+ if (!is_loaded) {
+ std::FILE *file = std_fopen(filename,"rb");
+ if (!file) {
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimg_instance
+ "load(): Failed to open file '%s'.",
+ cimg_instance,
+ filename);
+ }
+
+ const char *const f_type = cimg::ftype(file,filename);
+ std::fclose(file);
+ is_loaded = true;
+ try {
+ 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,"inr")) load_inr(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,"gif")) load_gif_external(filename);
+ else if (!cimg::strcasecmp(f_type,"dcm")) load_medcon_external(filename);
+ else is_loaded = false;
+ } catch (CImgIOException&) { is_loaded = false; }
+ }
+
+ // If nothing loaded, try to load file with other means.
+ if (!is_loaded) {
+ try {
+ load_other(filename);
+ } catch (CImgIOException&) {
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimg_instance
+ "load(): Failed to recognize format of file '%s'.",
+ cimg_instance,
+ filename);
+ }
+ }
+ cimg::exception_mode(omode);
+ return *this;
+ }
+
+ //! Load image from a file \newinstance.
+ static CImg<T> get_load(const char *const filename) {
+ return CImg<T>().load(filename);
+ }
+
+ //! Load image from an ascii file.
+ /**
+ \param filename Filename, as a C -string.
+ **/
+ CImg<T>& load_ascii(const char *const filename) {
+ return _load_ascii(0,filename);
+ }
+
+ //! Load image from an ascii file \inplace.
+ static CImg<T> get_load_ascii(const char *const filename) {
+ return CImg<T>().load_ascii(filename);
+ }
+
+ //! Load image from an ascii file \overloading.
+ CImg<T>& load_ascii(std::FILE *const file) {
+ return _load_ascii(file,0);
+ }
+
+ //! Loadimage from an ascii file \newinstance.
+ 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");
+ CImg<charT> line(256); *line = 0;
+ int err = std::fscanf(nfile,"%255[^\n]",line._data);
+ unsigned int dx = 0, dy = 1, dz = 1, dc = 1;
+ cimg_sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dc);
+ err = std::fscanf(nfile,"%*[^0-9.eEinfa+-]");
+ 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 ulongT siz = size();
+ ulongT off = 0;
+ double val;
+ T *ptr = _data;
+ for (err = 1, off = 0; off<siz && err==1; ++off) {
+ err = std::fscanf(nfile,"%lf%*[^0-9.eEinfa+-]",&val);
+ *(ptr++) = (T)val;
+ }
+ if (err!=1)
+ cimg::warn(_cimg_instance
+ "load_ascii(): Only %lu/%lu values read from file '%s'.",
+ cimg_instance,
+ off - 1,siz,filename?filename:"(FILE*)");
+
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load image from a DLM file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_dlm(const char *const filename) {
+ return _load_dlm(0,filename);
+ }
+
+ //! Load image from a DLM file \newinstance.
+ static CImg<T> get_load_dlm(const char *const filename) {
+ return CImg<T>().load_dlm(filename);
+ }
+
+ //! Load image from a DLM file \overloading.
+ CImg<T>& load_dlm(std::FILE *const file) {
+ return _load_dlm(file,0);
+ }
+
+ //! Load image from a DLM file \newinstance.
+ 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");
+ CImg<charT> delimiter(256), tmp(256); *delimiter = *tmp = 0;
+ unsigned int cdx = 0, dx = 0, dy = 0;
+ int err = 0;
+ double val;
+ assign(256,256,1,1,(T)0);
+ while ((err = std::fscanf(nfile,"%lf%255[^0-9eEinfa.+-]",&val,delimiter._data))>0) {
+ if (err>0) (*this)(cdx++,dy) = (T)val;
+ if (cdx>=_width) resize(3*_width/2,_height,1,1,0);
+ char c = 0;
+ if (!cimg_sscanf(delimiter,"%255[^\n]%c",tmp._data,&c) || c=='\n') {
+ dx = std::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 image from a BMP file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_bmp(const char *const filename) {
+ return _load_bmp(0,filename);
+ }
+
+ //! Load image from a BMP file \newinstance.
+ static CImg<T> get_load_bmp(const char *const filename) {
+ return CImg<T>().load_bmp(filename);
+ }
+
+ //! Load image from a BMP file \overloading.
+ CImg<T>& load_bmp(std::FILE *const file) {
+ return _load_bmp(file,0);
+ }
+
+ //! Load image from a BMP file \newinstance.
+ 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");
+ CImg<ucharT> header(54);
+ cimg::fread(header._data,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),
+ header_size = header[0x0E] + (header[0x0F]<<8) + (header[0x10]<<16) + (header[0x11]<<24),
+ dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24),
+ dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24),
+ compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24),
+ nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24),
+ bpp = header[0x1C] + (header[0x1D]<<8);
+
+ if (!file_size || file_size==offset) {
+ cimg::fseek(nfile,0,SEEK_END);
+ file_size = (int)cimg::ftell(nfile);
+ cimg::fseek(nfile,54,SEEK_SET);
+ }
+ if (header_size>40) cimg::fseek(nfile,header_size - 40,SEEK_CUR);
+
+ const int
+ dx_bytes = (bpp==1)?(dx/8 + (dx%8?1:0)):((bpp==4)?(dx/2 + (dx%2)):(dx*bpp/8)),
+ align_bytes = (4 - dx_bytes%4)%4;
+ const longT
+ cimg_iobuffer = (longT)24*1024*1024,
+ buf_size = std::min((longT)cimg::abs(dy)*(dx_bytes + align_bytes),(longT)file_size - offset);
+
+ CImg<intT> colormap;
+ if (bpp<16) { if (!nb_colors) nb_colors = 1<<bpp; } else nb_colors = 0;
+ if (nb_colors) { colormap.assign(nb_colors); cimg::fread(colormap._data,nb_colors,nfile); }
+ const int xoffset = offset - 14 - header_size - 4*nb_colors;
+ if (xoffset>0) cimg::fseek(nfile,xoffset,SEEK_CUR);
+
+ CImg<ucharT> buffer;
+ if (buf_size<cimg_iobuffer) {
+ buffer.assign(cimg::abs(dy)*(dx_bytes + align_bytes),1,1,1,0);
+ 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 {
+ if (!file) cimg::fclose(nfile);
+ 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);
+ cimg::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*)(colormap._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);
+ cimg::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*)(colormap._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);
+ cimg::fseek(nfile,align_bytes,SEEK_CUR);
+ }
+ cimg_forX(*this,x) {
+ const unsigned char *col = (unsigned char*)(colormap._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);
+ cimg::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);
+ cimg::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);
+ cimg::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 image from a JPEG file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_jpeg(const char *const filename) {
+ return _load_jpeg(0,filename);
+ }
+
+ //! Load image from a JPEG file \newinstance.
+ static CImg<T> get_load_jpeg(const char *const filename) {
+ return CImg<T>().load_jpeg(filename);
+ }
+
+ //! Load image from a JPEG file \overloading.
+ CImg<T>& load_jpeg(std::FILE *const file) {
+ return _load_jpeg(file,0);
+ }
+
+ //! Load image from a JPEG file \newinstance.
+ 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
+
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ 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
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException(_cimg_instance
+ "load_jpeg(): Error message returned by libjpeg: %s.",
+ cimg_instance,jerr.message);
+ }
+
+ 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) {
+ cimg::fclose(nfile);
+ 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];
+ try { assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components); }
+ catch (...) { if (!file) cimg::fclose(nfile); throw; }
+ T *ptr_r = _data, *ptr_g = _data + 1UL*_width*_height, *ptr_b = _data + 2UL*_width*_height,
+ *ptr_a = _data + 3UL*_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 image from a file, using Magick++ library.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ // Added April/may 2006 by Christoph Hormann <chris_hormann@gmx.de>
+ // This is experimental code, not much tested, use with care.
+ CImg<T>& load_magick(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_magick(): Specified filename is (null).",
+ cimg_instance);
+#ifdef cimg_use_magick
+ Magick::Image image(filename);
+ const unsigned int W = image.size().width(), H = image.size().height();
+ switch (image.type()) {
+ case Magick::PaletteMatteType :
+ case Magick::TrueColorMatteType :
+ case Magick::ColorSeparationType : {
+ assign(W,H,1,4);
+ T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = data(0,0,0,3);
+ Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
+ for (ulongT off = (ulongT)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 (ulongT off = (ulongT)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 (ulongT off = (ulongT)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 (ulongT off = (ulongT)W*H; off; --off) {
+ *(ptr_r++) = (T)(pixels->red);
+ ++pixels;
+ }
+ }
+ }
+ return *this;
+#else
+ throw CImgIOException(_cimg_instance
+ "load_magick(): Unable to load file '%s' unless libMagick++ is enabled.",
+ cimg_instance,
+ filename);
+#endif
+ }
+
+ //! Load image from a file, using Magick++ library \newinstance.
+ static CImg<T> get_load_magick(const char *const filename) {
+ return CImg<T>().load_magick(filename);
+ }
+
+ //! Load image from a PNG file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_png(const char *const filename, unsigned int *const bits_per_pixel=0) {
+ return _load_png(0,filename,bits_per_pixel);
+ }
+
+ //! Load image from a PNG file \newinstance.
+ static CImg<T> get_load_png(const char *const filename, unsigned int *const bits_per_pixel=0) {
+ return CImg<T>().load_png(filename,bits_per_pixel);
+ }
+
+ //! Load image from a PNG file \overloading.
+ CImg<T>& load_png(std::FILE *const file, unsigned int *const bits_per_pixel=0) {
+ return _load_png(file,0,bits_per_pixel);
+ }
+
+ //! Load image from a PNG file \newinstance.
+ static CImg<T> get_load_png(std::FILE *const file, unsigned int *const bits_per_pixel=0) {
+ return CImg<T>().load_png(file,bits_per_pixel);
+ }
+
+ // (Note: Most of this function has been written by Eric Fausett)
+ CImg<T>& _load_png(std::FILE *const file, const char *const filename, unsigned int *const bits_per_pixel) {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_png(): Specified filename is (null).",
+ cimg_instance);
+
+#ifndef cimg_use_png
+ cimg::unused(bits_per_pixel);
+ 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
+#if defined __GNUC__
+ const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning.
+ std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb");
+#else
+ const char *nfilename = filename;
+ std::FILE *nfile = file?file:cimg::fopen(nfilename,"rb");
+#endif
+ unsigned char pngCheck[8] = { 0 };
+ cimg::fread(pngCheck,8,(std::FILE*)nfile);
+ if (png_sig_cmp(pngCheck,0,8)) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException(_cimg_instance
+ "load_png(): Invalid PNG file '%s'.",
+ cimg_instance,
+ nfilename?nfilename:"(FILE*)");
+ }
+
+ // Setup PNG structures for read
+ png_voidp user_error_ptr = 0;
+ png_error_ptr user_error_fn = 0, user_warning_fn = 0;
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn);
+ if (!png_ptr) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException(_cimg_instance
+ "load_png(): Failed to initialize 'png_ptr' structure for file '%s'.",
+ cimg_instance,
+ nfilename?nfilename:"(FILE*)");
+ }
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0);
+ throw CImgIOException(_cimg_instance
+ "load_png(): Failed to initialize 'info_ptr' structure for file '%s'.",
+ cimg_instance,
+ nfilename?nfilename:"(FILE*)");
+ }
+ png_infop end_info = png_create_info_struct(png_ptr);
+ if (!end_info) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0);
+ throw CImgIOException(_cimg_instance
+ "load_png(): Failed to initialize 'end_info' structure for file '%s'.",
+ cimg_instance,
+ nfilename?nfilename:"(FILE*)");
+ }
+
+ // Error handling callback for png file reading
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ if (!file) cimg::fclose((std::FILE*)nfile);
+ png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
+ throw CImgIOException(_cimg_instance
+ "load_png(): Encountered unknown fatal error in libpng for file '%s'.",
+ cimg_instance,
+ nfilename?nfilename:"(FILE*)");
+ }
+ png_init_io(png_ptr, nfile);
+ png_set_sig_bytes(png_ptr, 8);
+
+ // Get PNG Header Info up to data block
+ png_read_info(png_ptr,info_ptr);
+ png_uint_32 W, H;
+ int bit_depth, color_type, interlace_type;
+ bool is_gray = false;
+ png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,(int*)0,(int*)0);
+ if (bits_per_pixel) *bits_per_pixel = (unsigned int)bit_depth;
+
+ // Transforms to unify image data
+ if (color_type==PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(png_ptr);
+ color_type = PNG_COLOR_TYPE_RGB;
+ bit_depth = 8;
+ }
+ if (color_type==PNG_COLOR_TYPE_GRAY && bit_depth<8) {
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+ is_gray = true;
+ bit_depth = 8;
+ }
+ if (png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(png_ptr);
+ color_type |= PNG_COLOR_MASK_ALPHA;
+ }
+ if (color_type==PNG_COLOR_TYPE_GRAY || color_type==PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(png_ptr);
+ color_type |= PNG_COLOR_MASK_COLOR;
+ is_gray = true;
+ }
+ if (color_type==PNG_COLOR_TYPE_RGB)
+ png_set_filler(png_ptr,0xffffU,PNG_FILLER_AFTER);
+
+ png_read_update_info(png_ptr,info_ptr);
+ if (bit_depth!=8 && bit_depth!=16) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0);
+ throw CImgIOException(_cimg_instance
+ "load_png(): Invalid bit depth %u in file '%s'.",
+ cimg_instance,
+ bit_depth,nfilename?nfilename:"(FILE*)");
+ }
+ const int byte_depth = bit_depth>>3;
+
+ // Allocate Memory for Image Read
+ png_bytep *const imgData = new png_bytep[H];
+ for (unsigned int row = 0; row<H; ++row) imgData[row] = new png_byte[(size_t)byte_depth*4*W];
+ png_read_image(png_ptr,imgData);
+ png_read_end(png_ptr,end_info);
+
+ // Read pixel data
+ if (color_type!=PNG_COLOR_TYPE_RGB && color_type!=PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (!file) cimg::fclose(nfile);
+ png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0);
+ throw CImgIOException(_cimg_instance
+ "load_png(): Invalid color coding type %u in file '%s'.",
+ cimg_instance,
+ color_type,nfilename?nfilename:"(FILE*)");
+ }
+ const bool is_alpha = (color_type==PNG_COLOR_TYPE_RGBA);
+ try { assign(W,H,1,(is_gray?1:3) + (is_alpha?1:0)); }
+ catch (...) { if (!file) cimg::fclose(nfile); throw; }
+ T
+ *ptr_r = data(0,0,0,0),
+ *ptr_g = is_gray?0:data(0,0,0,1),
+ *ptr_b = is_gray?0:data(0,0,0,2),
+ *ptr_a = !is_alpha?0:data(0,0,0,is_gray?1:3);
+ switch (bit_depth) {
+ case 8 : {
+ cimg_forY(*this,y) {
+ const unsigned char *ptrs = (unsigned char*)imgData[y];
+ cimg_forX(*this,x) {
+ *(ptr_r++) = (T)*(ptrs++);
+ if (ptr_g) *(ptr_g++) = (T)*(ptrs++); else ++ptrs;
+ if (ptr_b) *(ptr_b++) = (T)*(ptrs++); else ++ptrs;
+ if (ptr_a) *(ptr_a++) = (T)*(ptrs++); else ++ptrs;
+ }
+ }
+ } break;
+ case 16 : {
+ cimg_forY(*this,y) {
+ const unsigned short *ptrs = (unsigned short*)(imgData[y]);
+ if (!cimg::endianness()) cimg::invert_endianness(ptrs,4*_width);
+ cimg_forX(*this,x) {
+ *(ptr_r++) = (T)*(ptrs++);
+ if (ptr_g) *(ptr_g++) = (T)*(ptrs++); else ++ptrs;
+ if (ptr_b) *(ptr_b++) = (T)*(ptrs++); else ++ptrs;
+ if (ptr_a) *(ptr_a++) = (T)*(ptrs++); else ++ptrs;
+ }
+ }
+ } break;
+ }
+ png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
+
+ // Deallocate Image Read Memory
+ cimg_forY(*this,n) delete[] imgData[n];
+ delete[] imgData;
+ if (!file) cimg::fclose(nfile);
+ return *this;
+#endif
+ }
+
+ //! Load image from a PNM file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_pnm(const char *const filename) {
+ return _load_pnm(0,filename);
+ }
+
+ //! Load image from a PNM file \newinstance.
+ static CImg<T> get_load_pnm(const char *const filename) {
+ return CImg<T>().load_pnm(filename);
+ }
+
+ //! Load image from a PNM file \overloading.
+ CImg<T>& load_pnm(std::FILE *const file) {
+ return _load_pnm(file,0);
+ }
+
+ //! Load image from a PNM file \newinstance.
+ static CImg<T> get_load_pnm(std::FILE *const file) {
+ return CImg<T>().load_pnm(file);
+ }
+
+ CImg<T>& _load_pnm(std::FILE *const file, const char *const filename) {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_pnm(): Specified filename is (null).",
+ cimg_instance);
+
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ unsigned int ppm_type, W, H, D = 1, colormax = 255;
+ CImg<charT> item(16384,1,1,1,0);
+ int err, rval, gval, bval;
+ const longT cimg_iobuffer = (longT)24*1024*1024;
+ while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+ if (cimg_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," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+ if ((err=cimg_sscanf(item," %u %u %u %u",&W,&H,&D,&colormax))<2) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException(_cimg_instance
+ "load_pnm(): WIDTH and HEIGHT fields undefined in file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+ }
+ if (ppm_type!=1 && ppm_type!=4) {
+ if (err==2 || (err==3 && (ppm_type==5 || ppm_type==7 || ppm_type==8 || ppm_type==9))) {
+ while ((err=std::fscanf(nfile," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+ if (cimg_sscanf(item,"%u",&colormax)!=1)
+ cimg::warn(_cimg_instance
+ "load_pnm(): COLORMAX field is undefined in file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+ } else { colormax = D; D = 1; }
+ }
+ std::fgetc(nfile);
+
+ switch (ppm_type) {
+ case 1 : { // 2d b&w ascii.
+ assign(W,H,1,1);
+ T* ptrd = _data;
+ cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)(rval?0:255); else break; }
+ } break;
+ case 2 : { // 2d grey ascii.
+ assign(W,H,1,1);
+ T* ptrd = _data;
+ cimg_foroff(*this,off) { if (std::fscanf(nfile,"%d",&rval)>0) *(ptrd++) = (T)rval; else break; }
+ } break;
+ case 3 : { // 2d color ascii.
+ assign(W,H,1,3);
+ T *ptrd = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+ cimg_forXY(*this,x,y) {
+ if (std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) {
+ *(ptrd++) = (T)rval; *(ptr_g++) = (T)gval; *(ptr_b++) = (T)bval;
+ } else break;
+ }
+ } break;
+ case 4 : { // 2d b&w binary (support 3D PINK extension).
+ CImg<ucharT> raw;
+ assign(W,H,D,1);
+ T *ptrd = data(0,0,0,0);
+ unsigned int w = 0, h = 0, d = 0;
+ for (longT to_read = (longT)((W/8 + (W%8?1:0))*H*D); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer));
+ cimg::fread(raw._data,raw._width,nfile);
+ to_read-=raw._width;
+ const unsigned char *ptrs = raw._data;
+ unsigned char mask = 0, val = 0;
+ for (ulongT off = (ulongT)raw._width; off || mask; mask>>=1) {
+ if (!mask) { if (off--) val = *(ptrs++); mask = 128; }
+ *(ptrd++) = (T)((val&mask)?0:255);
+ if (++w==W) { w = 0; mask = 0; if (++h==H) { h = 0; if (++d==D) break; }}
+ }
+ }
+ } break;
+ case 5 : case 7 : { // 2d/3d grey binary (support 3D PINK extension).
+ if (colormax<256) { // 8 bits.
+ CImg<ucharT> raw;
+ assign(W,H,D,1);
+ T *ptrd = data(0,0,0,0);
+ for (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer));
+ cimg::fread(raw._data,raw._width,nfile);
+ to_read-=raw._width;
+ const unsigned char *ptrs = raw._data;
+ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+ }
+ } else { // 16 bits.
+ CImg<ushortT> raw;
+ assign(W,H,D,1);
+ T *ptrd = data(0,0,0,0);
+ for (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer/2));
+ cimg::fread(raw._data,raw._width,nfile);
+ if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width);
+ to_read-=raw._width;
+ const unsigned short *ptrs = raw._data;
+ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+ }
+ }
+ } break;
+ case 6 : { // 2d color binary.
+ if (colormax<256) { // 8 bits.
+ CImg<ucharT> raw;
+ assign(W,H,1,3);
+ T
+ *ptr_r = data(0,0,0,0),
+ *ptr_g = data(0,0,0,1),
+ *ptr_b = data(0,0,0,2);
+ for (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer));
+ cimg::fread(raw._data,raw._width,nfile);
+ to_read-=raw._width;
+ const unsigned char *ptrs = raw._data;
+ for (ulongT off = (ulongT)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 (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer/2));
+ cimg::fread(raw._data,raw._width,nfile);
+ if (!cimg::endianness()) cimg::invert_endianness(raw._data,raw._width);
+ to_read-=raw._width;
+ const unsigned short *ptrs = raw._data;
+ for (ulongT off = (ulongT)raw._width/3; off; --off) {
+ *(ptr_r++) = (T)*(ptrs++);
+ *(ptr_g++) = (T)*(ptrs++);
+ *(ptr_b++) = (T)*(ptrs++);
+ }
+ }
+ }
+ } break;
+ case 8 : { // 2d/3d grey binary with int32 integers (PINK extension).
+ CImg<intT> raw;
+ assign(W,H,D,1);
+ T *ptrd = data(0,0,0,0);
+ for (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer));
+ cimg::fread(raw._data,raw._width,nfile);
+ to_read-=raw._width;
+ const int *ptrs = raw._data;
+ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+ }
+ } break;
+ case 9 : { // 2d/3d grey binary with float values (PINK extension).
+ CImg<floatT> raw;
+ assign(W,H,D,1);
+ T *ptrd = data(0,0,0,0);
+ for (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer));
+ cimg::fread(raw._data,raw._width,nfile);
+ to_read-=raw._width;
+ const float *ptrs = raw._data;
+ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++);
+ }
+ } break;
+ default :
+ assign();
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException(_cimg_instance
+ "load_pnm(): PNM type 'P%d' found, but type is not supported.",
+ cimg_instance,
+ filename?filename:"(FILE*)",ppm_type);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load image from a PFM file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_pfm(const char *const filename) {
+ return _load_pfm(0,filename);
+ }
+
+ //! Load image from a PFM file \newinstance.
+ static CImg<T> get_load_pfm(const char *const filename) {
+ return CImg<T>().load_pfm(filename);
+ }
+
+ //! Load image from a PFM file \overloading.
+ CImg<T>& load_pfm(std::FILE *const file) {
+ return _load_pfm(file,0);
+ }
+
+ //! Load image from a PFM file \newinstance.
+ 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;
+ CImg<charT> item(16384,1,1,1,0);
+ int W = 0, H = 0, err = 0;
+ double scale = 0;
+ while ((err=std::fscanf(nfile,"%16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+ if (cimg_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," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+ if ((err=cimg_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," %16383[^\n]",item.data()))!=EOF && (*item=='#' || !err)) std::fgetc(nfile);
+ if (cimg_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,(T)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,(T)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 mirror('y'); // Most of the .pfm files are flipped along the y-axis.
+ }
+
+ //! Load image from a RGB file.
+ /**
+ \param filename Filename, as a C-string.
+ \param dimw Width of the image buffer.
+ \param dimh Height of the image buffer.
+ **/
+ CImg<T>& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgb(0,filename,dimw,dimh);
+ }
+
+ //! Load image from a RGB file \newinstance.
+ 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 image from a RGB file \overloading.
+ CImg<T>& load_rgb(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgb(file,0,dimw,dimh);
+ }
+
+ //! Load image from a RGB file \newinstance.
+ 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 longT cimg_iobuffer = (longT)24*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 (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer));
+ cimg::fread(raw._data,raw._width,nfile);
+ to_read-=raw._width;
+ const unsigned char *ptrs = raw._data;
+ for (ulongT off = raw._width/3UL; off; --off) {
+ *(ptr_r++) = (T)*(ptrs++);
+ *(ptr_g++) = (T)*(ptrs++);
+ *(ptr_b++) = (T)*(ptrs++);
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load image from a RGBA file.
+ /**
+ \param filename Filename, as a C-string.
+ \param dimw Width of the image buffer.
+ \param dimh Height of the image buffer.
+ **/
+ CImg<T>& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgba(0,filename,dimw,dimh);
+ }
+
+ //! Load image from a RGBA file \newinstance.
+ 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 image from a RGBA file \overloading.
+ CImg<T>& load_rgba(std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
+ return _load_rgba(file,0,dimw,dimh);
+ }
+
+ //! Load image from a RGBA file \newinstance.
+ 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 longT cimg_iobuffer = (longT)24*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 (longT to_read = (longT)size(); to_read>0; ) {
+ raw.assign(std::min(to_read,cimg_iobuffer));
+ cimg::fread(raw._data,raw._width,nfile);
+ to_read-=raw._width;
+ const unsigned char *ptrs = raw._data;
+ for (ulongT off = raw._width/4UL; off; --off) {
+ *(ptr_r++) = (T)*(ptrs++);
+ *(ptr_g++) = (T)*(ptrs++);
+ *(ptr_b++) = (T)*(ptrs++);
+ *(ptr_a++) = (T)*(ptrs++);
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load image from a TIFF file.
+ /**
+ \param filename Filename, as a C-string.
+ \param first_frame First frame to read (for multi-pages tiff).
+ \param last_frame Last frame to read (for multi-pages tiff).
+ \param step_frame Step value of frame reading.
+ \note
+ - libtiff support is enabled by defining the precompilation
+ directive \c cimg_use_tif.
+ - When libtiff is enabled, 2D and 3D (multipage) several
+ channel per pixel are supported for
+ <tt>char,uchar,short,ushort,float</tt> and \c double pixel types.
+ - If \c cimg_use_tif is not defined at compile time the
+ function uses CImg<T>& load_other(const char*).
+ **/
+ 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,
+ float *const voxel_size=0,
+ CImg<charT> *const description=0) {
+ 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
+ cimg::unused(voxel_size,description);
+ 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
+#if cimg_verbosity<3
+ TIFFSetWarningHandler(0);
+ TIFFSetErrorHandler(0);
+#endif
+ 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,voxel_size,description);
+ 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(std::max(frame._width,_width),
+ std::max(frame._height,_height),-100,
+ std::max(frame._spectrum,_spectrum),0);
+ draw_image(0,0,(l - nfirst_frame)/nstep_frame,frame);
+ }
+ TIFFClose(tif);
+ } else throw CImgIOException(_cimg_instance
+ "load_tiff(): Failed to open file '%s'.",
+ cimg_instance,
+ filename);
+ return *this;
+#endif
+ }
+
+ //! Load image from a TIFF file \newinstance.
+ 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,
+ float *const voxel_size=0,
+ CImg<charT> *const description=0) {
+ return CImg<T>().load_tiff(filename,first_frame,last_frame,step_frame,voxel_size,description);
+ }
+
+ // (Original contribution by Jerome Boulanger).
+#ifdef cimg_use_tiff
+ template<typename t>
+ void _load_tiff_tiled_contig(TIFF *const tif, const uint16 samplesperpixel,
+ const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) {
+ t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgIOException(_cimg_instance
+ "load_tiff(): Invalid tile in file '%s'.",
+ cimg_instance,
+ TIFFFileName(tif));
+ }
+ const t *ptr = buf;
+ for (unsigned int rr = row; rr<std::min((unsigned int)(row + th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<std::min((unsigned int)(col + tw),(unsigned int)nx); ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ (*this)(cc,rr,vv) = (T)(ptr[(rr - row)*th*samplesperpixel + (cc - col)*samplesperpixel + vv]);
+ }
+ _TIFFfree(buf);
+ }
+ }
+
+ template<typename t>
+ void _load_tiff_tiled_separate(TIFF *const tif, const uint16 samplesperpixel,
+ const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) {
+ t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif));
+ if (buf) {
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ for (unsigned int row = 0; row<ny; row+=th)
+ for (unsigned int col = 0; col<nx; col+=tw) {
+ if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgIOException(_cimg_instance
+ "load_tiff(): Invalid tile in file '%s'.",
+ cimg_instance,
+ TIFFFileName(tif));
+ }
+ const t *ptr = buf;
+ for (unsigned int rr = row; rr<std::min((unsigned int)(row + th),(unsigned int)ny); ++rr)
+ for (unsigned int cc = col; cc<std::min((unsigned int)(col + tw),(unsigned int)nx); ++cc)
+ (*this)(cc,rr,vv) = (T)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ }
+
+ template<typename t>
+ void _load_tiff_contig(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) {
+ t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row + rowsperstrip>ny?ny - row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, 0);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgIOException(_cimg_instance
+ "load_tiff(): Invalid strip in file '%s'.",
+ cimg_instance,
+ TIFFFileName(tif));
+ }
+ const t *ptr = buf;
+ for (unsigned int rr = 0; rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<nx; ++cc)
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row + rr,vv) = (T)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ }
+
+ template<typename t>
+ void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) {
+ t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ uint32 row, rowsperstrip = (uint32)-1;
+ TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
+ for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
+ for (row = 0; row<ny; row+= rowsperstrip) {
+ uint32 nrow = (row + rowsperstrip>ny?ny - row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif, row, vv);
+ if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
+ _TIFFfree(buf); TIFFClose(tif);
+ throw CImgIOException(_cimg_instance
+ "load_tiff(): Invalid strip in file '%s'.",
+ cimg_instance,
+ TIFFFileName(tif));
+ }
+ const t *ptr = buf;
+ for (unsigned int rr = 0;rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<nx; ++cc)
+ (*this)(cc,row + rr,vv) = (T)*(ptr++);
+ }
+ _TIFFfree(buf);
+ }
+ }
+
+ CImg<T>& _load_tiff(TIFF *const tif, const unsigned int directory,
+ float *const voxel_size, CImg<charT> *const description) {
+ if (!TIFFSetDirectory(tif,directory)) return assign();
+ uint16 samplesperpixel = 1, bitspersample = 8, photo = 0;
+ uint16 sampleformat = 1;
+ uint32 nx = 1, ny = 1;
+ const char *const filename = TIFFFileName(tif);
+ const bool is_spp = (bool)TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel);
+ TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx);
+ TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny);
+ TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat);
+ TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample);
+ TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo);
+ if (voxel_size) {
+ const char *s_description = 0;
+ float vx = 0, vy = 0, vz = 0;
+ if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description) {
+ const char *s_desc = std::strstr(s_description,"VX=");
+ if (s_desc && cimg_sscanf(s_desc,"VX=%f VY=%f VZ=%f",&vx,&vy,&vz)==3) { // CImg format.
+ voxel_size[0] = vx; voxel_size[1] = vy; voxel_size[2] = vz;
+ }
+ s_desc = std::strstr(s_description,"spacing=");
+ if (s_desc && cimg_sscanf(s_desc,"spacing=%f",&vz)==1) { // fiji format.
+ voxel_size[2] = vz;
+ }
+ }
+ TIFFGetField(tif,TIFFTAG_XRESOLUTION,voxel_size);
+ TIFFGetField(tif,TIFFTAG_YRESOLUTION,voxel_size + 1);
+ voxel_size[0] = 1.0f/voxel_size[0];
+ voxel_size[1] = 1.0f/voxel_size[1];
+ }
+ if (description) {
+ const char *s_description = 0;
+ if (TIFFGetField(tif,TIFFTAG_IMAGEDESCRIPTION,&s_description) && s_description)
+ CImg<charT>::string(s_description).move_to(*description);
+ }
+ const unsigned int spectrum = !is_spp || photo>=3?(photo>1?3:1):samplesperpixel;
+ assign(nx,ny,1,spectrum);
+
+ if ((photo>=3 && sampleformat==1 &&
+ (bitspersample==4 || bitspersample==8) &&
+ (samplesperpixel==1 || samplesperpixel==3 || samplesperpixel==4)) ||
+ (bitspersample==1 && samplesperpixel==1)) {
+ // Special case for unsigned color images.
+ 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 (spectrum) {
+ case 1 :
+ cimg_forXY(*this,x,y)
+ (*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny - 1 -y) + x]);
+ 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);
+ } else { // Other cases.
+ uint16 config;
+ TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config);
+ if (TIFFIsTiled(tif)) {
+ uint32 tw = 1, th = 1;
+ 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;
+ case 64 :
+ if (sampleformat==SAMPLEFORMAT_UINT)
+ _load_tiff_tiled_contig<uint64T>(tif,samplesperpixel,nx,ny,tw,th);
+ else if (sampleformat==SAMPLEFORMAT_INT)
+ _load_tiff_tiled_contig<int64T>(tif,samplesperpixel,nx,ny,tw,th);
+ else _load_tiff_tiled_contig<double>(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;
+ case 64 :
+ if (sampleformat==SAMPLEFORMAT_UINT)
+ _load_tiff_tiled_separate<uint64T>(tif,samplesperpixel,nx,ny,tw,th);
+ else if (sampleformat==SAMPLEFORMAT_INT)
+ _load_tiff_tiled_separate<int64T>(tif,samplesperpixel,nx,ny,tw,th);
+ else _load_tiff_tiled_separate<double>(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;
+ case 64 :
+ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_contig<uint64T>(tif,samplesperpixel,nx,ny);
+ else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_contig<int64T>(tif,samplesperpixel,nx,ny);
+ else _load_tiff_contig<double>(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;
+ case 64 :
+ if (sampleformat==SAMPLEFORMAT_UINT) _load_tiff_separate<uint64T>(tif,samplesperpixel,nx,ny);
+ else if (sampleformat==SAMPLEFORMAT_INT) _load_tiff_separate<int64T>(tif,samplesperpixel,nx,ny);
+ else _load_tiff_separate<double>(tif,samplesperpixel,nx,ny);
+ break;
+ }
+ }
+ }
+ return *this;
+ }
+#endif
+
+ //! Load image from a MINC2 file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ // (Original code by Haz-Edine Assemlal).
+ CImg<T>& load_minc2(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_minc2(): Specified filename is (null).",
+ cimg_instance);
+#ifndef cimg_use_minc2
+ return load_other(filename);
+#else
+ minc::minc_1_reader rdr;
+ rdr.open(filename);
+ assign(rdr.ndim(1)?rdr.ndim(1):1,
+ rdr.ndim(2)?rdr.ndim(2):1,
+ rdr.ndim(3)?rdr.ndim(3):1,
+ rdr.ndim(4)?rdr.ndim(4):1);
+ if (typeid(T)==typeid(unsigned char))
+ rdr.setup_read_byte();
+ else if (typeid(T)==typeid(int))
+ rdr.setup_read_int();
+ else if (typeid(T)==typeid(double))
+ rdr.setup_read_double();
+ else
+ rdr.setup_read_float();
+ minc::load_standard_volume(rdr, this->_data);
+ return *this;
+#endif
+ }
+
+ //! Load image from a MINC2 file \newinstance.
+ static CImg<T> get_load_minc2(const char *const filename) {
+ return CImg<T>().load_analyze(filename);
+ }
+
+ //! Load image from an ANALYZE7.5/NIFTI file.
+ /**
+ \param filename Filename, as a C-string.
+ \param[out] voxel_size Pointer to the three voxel sizes read from the file.
+ **/
+ CImg<T>& load_analyze(const char *const filename, float *const voxel_size=0) {
+ return _load_analyze(0,filename,voxel_size);
+ }
+
+ //! Load image from an ANALYZE7.5/NIFTI file \newinstance.
+ static CImg<T> get_load_analyze(const char *const filename, float *const voxel_size=0) {
+ return CImg<T>().load_analyze(filename,voxel_size);
+ }
+
+ //! Load image from an ANALYZE7.5/NIFTI file \overloading.
+ CImg<T>& load_analyze(std::FILE *const file, float *const voxel_size=0) {
+ return _load_analyze(file,0,voxel_size);
+ }
+
+ //! Load image from an ANALYZE7.5/NIFTI file \newinstance.
+ static CImg<T> get_load_analyze(std::FILE *const file, float *const voxel_size=0) {
+ return CImg<T>().load_analyze(file,voxel_size);
+ }
+
+ CImg<T>& _load_analyze(std::FILE *const file, const char *const filename, float *const voxel_size=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) {
+ CImg<charT> body(1024);
+ 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");
+ cimg_sprintf(body._data + 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");
+ cimg_sprintf(body._data + 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-size 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 + 108),1);
+ cimg::invert_endianness((float*)(header + 112),1);
+ }
+
+ if (nfile_header==nfile) {
+ const unsigned int vox_offset = (unsigned int)*(float*)(header + 108);
+ std::fseek(nfile,vox_offset,SEEK_SET);
+ }
+
+ 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 = *(unsigned short*)(header + 70);
+ if (voxel_size) {
+ const float *vsize = (float*)(header + 76);
+ voxel_size[0] = vsize[1]; voxel_size[1] = vsize[2]; voxel_size[2] = vsize[3];
+ }
+ delete[] header;
+
+ // Read pixel data.
+ assign(dimx,dimy,dimz,dimv);
+ const size_t pdim = (size_t)dimx*dimy*dimz*dimv;
+ switch (datatype) {
+ case 2 : {
+ unsigned char *const buffer = new unsigned char[pdim];
+ cimg::fread(buffer,pdim,nfile);
+ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 4 : {
+ short *const buffer = new short[pdim];
+ cimg::fread(buffer,pdim,nfile);
+ if (endian) cimg::invert_endianness(buffer,pdim);
+ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 8 : {
+ int *const buffer = new int[pdim];
+ cimg::fread(buffer,pdim,nfile);
+ if (endian) cimg::invert_endianness(buffer,pdim);
+ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 16 : {
+ float *const buffer = new float[pdim];
+ cimg::fread(buffer,pdim,nfile);
+ if (endian) cimg::invert_endianness(buffer,pdim);
+ cimg_foroff(*this,off) _data[off] = (T)(buffer[off]*scalefactor);
+ delete[] buffer;
+ } break;
+ case 64 : {
+ double *const buffer = new double[pdim];
+ cimg::fread(buffer,pdim,nfile);
+ if (endian) cimg::invert_endianness(buffer,pdim);
+ 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 image from a .cimg[z] file.
+ /**
+ \param filename Filename, as a C-string.
+ \param axis Appending axis, if file contains multiple images. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignment.
+ **/
+ CImg<T>& load_cimg(const char *const filename, const char axis='z', const float align=0) {
+ CImgList<T> list;
+ list.load_cimg(filename);
+ if (list._width==1) return list[0].move_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ //! Load image from a .cimg[z] file \newinstance
+ static CImg<T> get_load_cimg(const char *const filename, const char axis='z', const float align=0) {
+ return CImg<T>().load_cimg(filename,axis,align);
+ }
+
+ //! Load image from a .cimg[z] file \overloading.
+ CImg<T>& load_cimg(std::FILE *const file, const char axis='z', const float align=0) {
+ CImgList<T> list;
+ list.load_cimg(file);
+ if (list._width==1) return list[0].move_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ //! Load image from a .cimg[z] file \newinstance
+ static CImg<T> get_load_cimg(std::FILE *const file, const char axis='z', const float align=0) {
+ return CImg<T>().load_cimg(file,axis,align);
+ }
+
+ //! Load sub-images of a .cimg file.
+ /**
+ \param filename Filename, as a C-string.
+ \param n0 Starting frame.
+ \param n1 Ending frame (~0U for max).
+ \param x0 X-coordinate of the starting sub-image vertex.
+ \param y0 Y-coordinate of the starting sub-image vertex.
+ \param z0 Z-coordinate of the starting sub-image vertex.
+ \param c0 C-coordinate of the starting sub-image vertex.
+ \param x1 X-coordinate of the ending sub-image vertex (~0U for max).
+ \param y1 Y-coordinate of the ending sub-image vertex (~0U for max).
+ \param z1 Z-coordinate of the ending sub-image vertex (~0U for max).
+ \param c1 C-coordinate of the ending sub-image vertex (~0U for max).
+ \param axis Appending axis, if file contains multiple images. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignment.
+ **/
+ CImg<T>& load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1,
+ const char axis='z', const float align=0) {
+ CImgList<T> list;
+ list.load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+ if (list._width==1) return list[0].move_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ //! Load sub-images of a .cimg file \newinstance.
+ static CImg<T> get_load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1,
+ const char axis='z', const float align=0) {
+ return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align);
+ }
+
+ //! Load sub-images of a .cimg file \overloading.
+ CImg<T>& load_cimg(std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1,
+ const char axis='z', const float align=0) {
+ CImgList<T> list;
+ list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+ if (list._width==1) return list[0].move_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ //! Load sub-images of a .cimg file \newinstance.
+ static CImg<T> get_load_cimg(std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1,
+ const char axis='z', const float align=0) {
+ return CImg<T>().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align);
+ }
+
+ //! Load image from an INRIMAGE-4 file.
+ /**
+ \param filename Filename, as a C-string.
+ \param[out] voxel_size Pointer to the three voxel sizes read from the file.
+ **/
+ CImg<T>& load_inr(const char *const filename, float *const voxel_size=0) {
+ return _load_inr(0,filename,voxel_size);
+ }
+
+ //! Load image from an INRIMAGE-4 file \newinstance.
+ static CImg<T> get_load_inr(const char *const filename, float *const voxel_size=0) {
+ return CImg<T>().load_inr(filename,voxel_size);
+ }
+
+ //! Load image from an INRIMAGE-4 file \overloading.
+ CImg<T>& load_inr(std::FILE *const file, float *const voxel_size=0) {
+ return _load_inr(file,0,voxel_size);
+ }
+
+ //! Load image from an INRIMAGE-4 file \newinstance.
+ static CImg<T> get_load_inr(std::FILE *const file, float *voxel_size=0) {
+ return CImg<T>().load_inr(file,voxel_size);
+ }
+
+ static void _load_inr_header(std::FILE *file, int out[8], float *const voxel_size) {
+ CImg<charT> item(1024), tmp1(64), tmp2(64);
+ *item = *tmp1 = *tmp2 = 0;
+ out[0] = std::fscanf(file,"%63s",item._data);
+ 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._data)!=EOF && std::strncmp(item,"##}",3)) {
+ cimg_sscanf(item," XDIM%*[^0-9]%d",out);
+ cimg_sscanf(item," YDIM%*[^0-9]%d",out + 1);
+ cimg_sscanf(item," ZDIM%*[^0-9]%d",out + 2);
+ cimg_sscanf(item," VDIM%*[^0-9]%d",out + 3);
+ cimg_sscanf(item," PIXSIZE%*[^0-9]%d",out + 6);
+ if (voxel_size) {
+ cimg_sscanf(item," VX%*[^0-9.+-]%f",voxel_size);
+ cimg_sscanf(item," VY%*[^0-9.+-]%f",voxel_size + 1);
+ cimg_sscanf(item," VZ%*[^0-9.+-]%f",voxel_size + 2);
+ }
+ if (cimg_sscanf(item," CPU%*[ =]%s",tmp1._data)) out[7] = cimg::strncasecmp(tmp1,"sun",3)?0:1;
+ switch (cimg_sscanf(item," TYPE%*[ =]%s %s",tmp1._data,tmp2._data)) {
+ case 0 : break;
+ case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; std::strncpy(tmp1,tmp2,tmp1._width - 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._data);
+ }
+ }
+ 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 voxel_size) {
+#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[(size_t)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 (voxel_size) voxel_size[0] = voxel_size[1] = voxel_size[2] = 1;
+ _load_inr_header(nfile,fopt,voxel_size);
+ 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 image from a EXR file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_exr(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_exr(): Specified filename is (null).",
+ cimg_instance);
+#if defined(cimg_use_openexr)
+ 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;
+ }
+#elif defined(cimg_use_tinexr)
+ float *res;
+ const char *err = 0;
+ int width = 0, height = 0;
+ const int ret = LoadEXR(&res,&width,&height,filename,&err);
+ if (ret) throw CImgIOException(_cimg_instance
+ "load_exr(): Unable to load EXR file '%s'.",
+ cimg_instance,filename);
+ CImg<floatT>(out,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this);
+ std::free(res);
+#else
+ return load_other(filename);
+#endif
+ return *this;
+ }
+
+ //! Load image from a EXR file \newinstance.
+ static CImg<T> get_load_exr(const char *const filename) {
+ return CImg<T>().load_exr(filename);
+ }
+
+ //! Load image from a PANDORE-5 file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_pandore(const char *const filename) {
+ return _load_pandore(0,filename);
+ }
+
+ //! Load image from a PANDORE-5 file \newinstance.
+ static CImg<T> get_load_pandore(const char *const filename) {
+ return CImg<T>().load_pandore(filename);
+ }
+
+ //! Load image from a PANDORE-5 file \overloading.
+ CImg<T>& load_pandore(std::FILE *const file) {
+ return _load_pandore(file,0);
+ }
+
+ //! Load image from a PANDORE-5 file \newinstance.
+ 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 size_t 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");
+ CImg<charT> header(32);
+ cimg::fread(header._data,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 };
+ int ptbuf[4] = { 0 };
+ cimg::fread(&imageid,1,nfile);
+ const bool endian = imageid>255;
+ if (endian) cimg::invert_endianness(imageid);
+ cimg::fread(header._data,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 size_t 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 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 13 : { // Region 3d
+ cimg::fread(dims,5,nfile);
+ if (endian) cimg::invert_endianness(dims,5);
+ assign(dims[3],dims[2],dims[1],1);
+ const size_t 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
+ cimg::fread(ptbuf,1,nfile);
+ if (endian) cimg::invert_endianness(ptbuf,1);
+ assign(1); (*this)(0) = (T)ptbuf[0];
+ } break;
+ case 35 : { // Points 2d
+ 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
+ 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 image from a PAR-REC (Philips) file.
+ /**
+ \param filename Filename, as a C-string.
+ \param axis Appending axis, if file contains multiple images. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignment.
+ **/
+ CImg<T>& load_parrec(const char *const filename, const char axis='c', const float align=0) {
+ CImgList<T> list;
+ list.load_parrec(filename);
+ if (list._width==1) return list[0].move_to(*this);
+ return assign(list.get_append(axis,align));
+ }
+
+ //! Load image from a PAR-REC (Philips) file \newinstance.
+ static CImg<T> get_load_parrec(const char *const filename, const char axis='c', const float align=0) {
+ return CImg<T>().load_parrec(filename,axis,align);
+ }
+
+ //! Load image from a raw binary file.
+ /**
+ \param filename Filename, as a C-string.
+ \param size_x Width of the image buffer.
+ \param size_y Height of the image buffer.
+ \param size_z Depth of the image buffer.
+ \param size_c Spectrum of the image buffer.
+ \param is_multiplexed Tells if the image values are multiplexed along the C-axis.
+ \param invert_endianness Tells if the endianness of the image buffer must be inverted.
+ \param offset Starting offset of the read in the specified file.
+ **/
+ CImg<T>& load_raw(const char *const filename,
+ const unsigned int size_x=0, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1,
+ const bool is_multiplexed=false, const bool invert_endianness=false,
+ const ulongT offset=0) {
+ return _load_raw(0,filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset);
+ }
+
+ //! Load image from a raw binary file \newinstance.
+ static CImg<T> get_load_raw(const char *const filename,
+ const unsigned int size_x=0, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1,
+ const bool is_multiplexed=false, const bool invert_endianness=false,
+ const ulongT offset=0) {
+ return CImg<T>().load_raw(filename,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset);
+ }
+
+ //! Load image from a raw binary file \overloading.
+ CImg<T>& load_raw(std::FILE *const file,
+ const unsigned int size_x=0, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1,
+ const bool is_multiplexed=false, const bool invert_endianness=false,
+ const ulongT offset=0) {
+ return _load_raw(file,0,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset);
+ }
+
+ //! Load image from a raw binary file \newinstance.
+ static CImg<T> get_load_raw(std::FILE *const file,
+ const unsigned int size_x=0, const unsigned int size_y=1,
+ const unsigned int size_z=1, const unsigned int size_c=1,
+ const bool is_multiplexed=false, const bool invert_endianness=false,
+ const ulongT offset=0) {
+ return CImg<T>().load_raw(file,size_x,size_y,size_z,size_c,is_multiplexed,invert_endianness,offset);
+ }
+
+ CImg<T>& _load_raw(std::FILE *const file, const char *const filename,
+ const unsigned int size_x, const unsigned int size_y,
+ const unsigned int size_z, const unsigned int size_c,
+ const bool is_multiplexed, const bool invert_endianness,
+ const ulongT offset) {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_raw(): Specified filename is (null).",
+ cimg_instance);
+ if (cimg::is_directory(filename))
+ throw CImgArgumentException(_cimg_instance
+ "load_raw(): Specified filename '%s' is a directory.",
+ cimg_instance,filename);
+
+ ulongT siz = (ulongT)size_x*size_y*size_z*size_c;
+ unsigned int
+ _size_x = size_x,
+ _size_y = size_y,
+ _size_z = size_z,
+ _size_c = size_c;
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ if (!siz) { // Retrieve file size.
+ const longT fpos = cimg::ftell(nfile);
+ if (fpos<0) throw CImgArgumentException(_cimg_instance
+ "load_raw(): Cannot determine size of input file '%s'.",
+ cimg_instance,filename?filename:"(FILE*)");
+ cimg::fseek(nfile,0,SEEK_END);
+ siz = cimg::ftell(nfile)/sizeof(T);
+ _size_y = (unsigned int)siz;
+ _size_x = _size_z = _size_c = 1;
+ cimg::fseek(nfile,fpos,SEEK_SET);
+ }
+ cimg::fseek(nfile,offset,SEEK_SET);
+ assign(_size_x,_size_y,_size_z,_size_c,0);
+ if (siz && (!is_multiplexed || size_c==1)) {
+ cimg::fread(_data,siz,nfile);
+ if (invert_endianness) cimg::invert_endianness(_data,siz);
+ } else if (siz) {
+ CImg<T> buf(1,1,1,_size_c);
+ cimg_forXYZ(*this,x,y,z) {
+ cimg::fread(buf._data,_size_c,nfile);
+ if (invert_endianness) cimg::invert_endianness(buf._data,_size_c);
+ set_vector_at(buf,x,y,z);
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load image sequence from a YUV file.
+ /**
+ \param filename Filename, as a C-string.
+ \param size_x Width of the frames.
+ \param size_y Height of the frames.
+ \param first_frame Index of the first frame to read.
+ \param last_frame Index of the last frame to read.
+ \param step_frame Step value for frame reading.
+ \param yuv2rgb Tells if the YUV to RGB transform must be applied.
+ \param axis Appending axis, if file contains multiple images. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ **/
+ CImg<T>& load_yuv(const char *const filename,
+ const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') {
+ return get_load_yuv(filename,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this);
+ }
+
+ //! Load image sequence from a YUV file \newinstance.
+ static CImg<T> get_load_yuv(const char *const filename,
+ const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') {
+ return CImgList<T>().load_yuv(filename,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis);
+ }
+
+ //! Load image sequence from a YUV file \overloading.
+ CImg<T>& load_yuv(std::FILE *const file,
+ const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') {
+ return get_load_yuv(file,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this);
+ }
+
+ //! Load image sequence from a YUV file \newinstance.
+ static CImg<T> get_load_yuv(std::FILE *const file,
+ const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') {
+ return CImgList<T>().load_yuv(file,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis);
+ }
+
+ //! Load 3d object from a .OFF file.
+ /**
+ \param[out] primitives Primitives data of the 3d object.
+ \param[out] colors Colors data of the 3d object.
+ \param filename Filename, as a C-string.
+ **/
+ template<typename tf, typename tc>
+ CImg<T>& load_off(CImgList<tf>& primitives, CImgList<tc>& colors, const char *const filename) {
+ return _load_off(primitives,colors,0,filename);
+ }
+
+ //! Load 3d object from a .OFF file \newinstance.
+ template<typename tf, typename tc>
+ static CImg<T> get_load_off(CImgList<tf>& primitives, CImgList<tc>& colors, const char *const filename) {
+ return CImg<T>().load_off(primitives,colors,filename);
+ }
+
+ //! Load 3d object from a .OFF file \overloading.
+ template<typename tf, typename tc>
+ CImg<T>& load_off(CImgList<tf>& primitives, CImgList<tc>& colors, std::FILE *const file) {
+ return _load_off(primitives,colors,file,0);
+ }
+
+ //! Load 3d object from a .OFF file \newinstance.
+ template<typename tf, typename tc>
+ static CImg<T> get_load_off(CImgList<tf>& primitives, CImgList<tc>& colors, std::FILE *const file) {
+ return CImg<T>().load_off(primitives,colors,file);
+ }
+
+ template<typename tf, typename tc>
+ CImg<T>& _load_off(CImgList<tf>& primitives, CImgList<tc>& colors,
+ std::FILE *const file, const char *const filename) {
+ 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;
+ CImg<charT> line(256); *line = 0;
+ int err;
+
+ // Skip comments, and read magic string OFF
+ do { err = std::fscanf(nfile,"%255[^\n] ",line._data); } 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._data); } while (!err || (err==1 && *line=='#'));
+ if ((err = cimg_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._data); } while (!err || (err==1 && *line=='#'));
+ if ((err = cimg_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 stop_flag = false;
+ while (!stop_flag) {
+ 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) stop_flag = true;
+ else {
+ ++nb_read;
+ switch (prim) {
+ case 1 : {
+ if ((err = std::fscanf(nfile,"%u%255[^\n] ",&i0,line._data))<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 = cimg_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._data))<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 = cimg_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._data))<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 = cimg_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._data))<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 = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+ CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)).move_to(colors);
+ }
+ } break;
+ case 5 : {
+ if ((err = std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line._data))<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 = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+ CImg<tf>::vector(i0,i4,i3).move_to(primitives);
+ colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+ ++nb_primitives;
+ }
+ } break;
+ case 6 : {
+ if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line._data))<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 = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+ CImg<tf>::vector(i0,i5,i4,i3).move_to(primitives);
+ colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+ ++nb_primitives;
+ }
+ } break;
+ case 7 : {
+ if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line._data))<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 = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ CImg<tf>::vector(i0,i4,i3,i1).move_to(primitives);
+ CImg<tf>::vector(i0,i6,i5,i4).move_to(primitives);
+ CImg<tf>::vector(i3,i2,i1).move_to(primitives);
+ colors.insert(3,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+ ++(++nb_primitives);
+ }
+ } break;
+ case 8 : {
+ if ((err = std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line._data))<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 = cimg_sscanf(line,"%f%f%f",&c0,&c1,&c2);
+ CImg<tf>::vector(i0,i3,i2,i1).move_to(primitives);
+ CImg<tf>::vector(i0,i5,i4,i3).move_to(primitives);
+ CImg<tf>::vector(i0,i7,i6,i5).move_to(primitives);
+ colors.insert(3,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
+ ++(++nb_primitives);
+ }
+ } break;
+ default :
+ cimg::warn(_cimg_instance
+ "load_off(): Failed to read primitive %u/%u (%u vertices) from file '%s'.",
+ cimg_instance,
+ nb_read,nb_primitives,prim,filename?filename:"(FILE*)");
+
+ err = std::fscanf(nfile,"%*[^\n] ");
+ }
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ if (primitives._width!=nb_primitives)
+ cimg::warn(_cimg_instance
+ "load_off(): Only %u/%u primitives read from file '%s'.",
+ cimg_instance,
+ primitives._width,nb_primitives,filename?filename:"(FILE*)");
+ return *this;
+ }
+
+ //! Load image sequence from a video file, using OpenCV library.
+ /**
+ \param filename Filename, as a C-string.
+ \param first_frame Index of the first frame to read.
+ \param last_frame Index of the last frame to read.
+ \param step_frame Step value for frame reading.
+ **/
+ CImg<T>& load_video(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1,
+ const char axis='z', const float align=0) {
+ return get_load_video(filename,first_frame,last_frame,step_frame,axis,align).move_to(*this);
+ }
+
+ //! Load image sequence from a video file, using OpenCV library \newinstance.
+ static CImg<T> get_load_video(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1,
+ const char axis='z', const float align=0) {
+ return CImgList<T>().load_video(filename,first_frame,last_frame,step_frame).get_append(axis,align);
+ }
+
+ //! Load image sequence using FFMPEG's external tool 'ffmpeg'.
+ /**
+ \param filename Filename, as a C-string.
+ \param axis Appending axis, if file contains multiple images. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignment.
+ **/
+ CImg<T>& load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) {
+ return get_load_ffmpeg_external(filename,axis,align).move_to(*this);
+ }
+
+ //! Load image sequence using FFMPEG's external tool 'ffmpeg' \newinstance.
+ static CImg<T> get_load_ffmpeg_external(const char *const filename, const char axis='z', const float align=0) {
+ return CImgList<T>().load_ffmpeg_external(filename).get_append(axis,align);
+ }
+
+ //! Load gif file, using Imagemagick or GraphicsMagicks's external tools.
+ /**
+ \param filename Filename, as a C-string.
+ \param use_graphicsmagick Tells if GraphicsMagick's tool 'gm' is used instead of ImageMagick's tool 'convert'.
+ \param axis Appending axis, if file contains multiple images. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignment.
+ **/
+ CImg<T>& load_gif_external(const char *const filename,
+ const char axis='z', const float align=0) {
+ return get_load_gif_external(filename,axis,align).move_to(*this);
+ }
+
+ //! Load gif file, using ImageMagick or GraphicsMagick's external tool 'convert' \newinstance.
+ static CImg<T> get_load_gif_external(const char *const filename,
+ const char axis='z', const float align=0) {
+ return CImgList<T>().load_gif_external(filename).get_append(axis,align);
+ }
+
+ //! Load image using GraphicsMagick's external tool 'gm'.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_graphicsmagick_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_graphicsmagick_external(): Specified filename is (null).",
+ cimg_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ CImg<charT> command(1024), filename_tmp(256);
+ std::FILE *file = 0;
+ const CImg<charT> s_filename = CImg<charT>::string(filename)._system_strescape();
+#if cimg_OS==1
+ cimg_snprintf(command,command._width,"%s convert \"%s\" pnm:-",
+ cimg::graphicsmagick_path(),s_filename.data());
+ file = popen(command,"r");
+ if (file) {
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ try { load_pnm(file); } catch (...) {
+ pclose(file);
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimg_instance
+ "load_graphicsmagick_external(): Failed to load file '%s' with external command 'gm'.",
+ cimg_instance,
+ filename);
+ }
+ pclose(file);
+ return *this;
+ }
+#endif
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimg_snprintf(command,command._width,"%s convert \"%s\" \"%s\"",
+ cimg::graphicsmagick_path(),s_filename.data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+ cimg::system(command,cimg::graphicsmagick_path());
+ if (!(file = std_fopen(filename_tmp,"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(filename_tmp);
+ std::remove(filename_tmp);
+ return *this;
+ }
+
+ //! Load image using GraphicsMagick's external tool 'gm' \newinstance.
+ static CImg<T> get_load_graphicsmagick_external(const char *const filename) {
+ return CImg<T>().load_graphicsmagick_external(filename);
+ }
+
+ //! Load gzipped image file, using external tool 'gunzip'.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_gzip_external(const char *const filename) {
+ if (!filename)
+ throw CImgIOException(_cimg_instance
+ "load_gzip_external(): Specified filename is (null).",
+ cimg_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ CImg<charT> command(1024), filename_tmp(256), body(256);
+ 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(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ } else {
+ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ }
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"",
+ cimg::gunzip_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+ cimg::system(command);
+ if (!(file = std_fopen(filename_tmp,"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(filename_tmp);
+ std::remove(filename_tmp);
+ return *this;
+ }
+
+ //! Load gzipped image file, using external tool 'gunzip' \newinstance.
+ static CImg<T> get_load_gzip_external(const char *const filename) {
+ return CImg<T>().load_gzip_external(filename);
+ }
+
+ //! Load image using ImageMagick's external tool 'convert'.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_imagemagick_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_imagemagick_external(): Specified filename is (null).",
+ cimg_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ CImg<charT> command(1024), filename_tmp(256);
+ std::FILE *file = 0;
+ const CImg<charT> s_filename = CImg<charT>::string(filename)._system_strescape();
+#if cimg_OS==1
+ cimg_snprintf(command,command._width,"%s%s \"%s\" pnm:-",
+ cimg::imagemagick_path(),
+ !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"",
+ s_filename.data());
+ file = popen(command,"r");
+ if (file) {
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ try { load_pnm(file); } catch (...) {
+ pclose(file);
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimg_instance
+ "load_imagemagick_external(): Failed to load file '%s' with "
+ "external command 'convert'.",
+ cimg_instance,
+ filename);
+ }
+ pclose(file);
+ return *this;
+ }
+#endif
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.pnm",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimg_snprintf(command,command._width,"%s%s \"%s\" \"%s\"",
+ cimg::imagemagick_path(),
+ !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"",
+ s_filename.data(),CImg<charT>::string(filename_tmp)._system_strescape().data());
+ cimg::system(command,cimg::imagemagick_path());
+ if (!(file = std_fopen(filename_tmp,"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(filename_tmp);
+ std::remove(filename_tmp);
+ return *this;
+ }
+
+ //! Load image using ImageMagick's external tool 'convert' \newinstance.
+ static CImg<T> get_load_imagemagick_external(const char *const filename) {
+ return CImg<T>().load_imagemagick_external(filename);
+ }
+
+ //! Load image from a DICOM file, using XMedcon's external tool 'medcon'.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_medcon_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_medcon_external(): Specified filename is (null).",
+ cimg_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ CImg<charT> command(1024), filename_tmp(256), body(256);
+ cimg::fclose(cimg::fopen(filename,"r"));
+ std::FILE *file = 0;
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand());
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimg_snprintf(command,command._width,"%s -w -c anlz -o \"%s\" -f \"%s\"",
+ cimg::medcon_path(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data(),
+ CImg<charT>::string(filename)._system_strescape().data());
+ cimg::system(command);
+ cimg::split_filename(filename_tmp,body);
+
+ cimg_snprintf(command,command._width,"%s.hdr",body._data);
+ file = std_fopen(command,"rb");
+ if (!file) {
+ cimg_snprintf(command,command._width,"m000-%s.hdr",body._data);
+ file = std_fopen(command,"rb");
+ if (!file) {
+ throw CImgIOException(_cimg_instance
+ "load_medcon_external(): Failed to load file '%s' with external command 'medcon'.",
+ cimg_instance,
+ filename);
+ }
+ }
+ cimg::fclose(file);
+ load_analyze(command);
+ std::remove(command);
+ cimg::split_filename(command,body);
+ cimg_snprintf(command,command._width,"%s.img",body._data);
+ std::remove(command);
+ return *this;
+ }
+
+ //! Load image from a DICOM file, using XMedcon's external tool 'medcon' \newinstance.
+ static CImg<T> get_load_medcon_external(const char *const filename) {
+ return CImg<T>().load_medcon_external(filename);
+ }
+
+ //! Load image from a RAW Color Camera file, using external tool 'dcraw'.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_dcraw_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_dcraw_external(): Specified filename is (null).",
+ cimg_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ CImg<charT> command(1024), filename_tmp(256);
+ std::FILE *file = 0;
+ const CImg<charT> s_filename = CImg<charT>::string(filename)._system_strescape();
+#if cimg_OS==1
+ cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\"",
+ cimg::dcraw_path(),s_filename.data());
+ file = popen(command,"r");
+ if (file) {
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ try { load_pnm(file); } catch (...) {
+ pclose(file);
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimg_instance
+ "load_dcraw_external(): Failed to load file '%s' with external command 'dcraw'.",
+ cimg_instance,
+ filename);
+ }
+ pclose(file);
+ return *this;
+ }
+#endif
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.ppm",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\" > \"%s\"",
+ cimg::dcraw_path(),s_filename.data(),CImg<charT>::string(filename_tmp)._system_strescape().data());
+ cimg::system(command,cimg::dcraw_path());
+ if (!(file = std_fopen(filename_tmp,"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(filename_tmp);
+ std::remove(filename_tmp);
+ return *this;
+ }
+
+ //! Load image from a RAW Color Camera file, using external tool 'dcraw' \newinstance.
+ static CImg<T> get_load_dcraw_external(const char *const filename) {
+ return CImg<T>().load_dcraw_external(filename);
+ }
+
+ //! Load image from a camera stream, using OpenCV.
+ /**
+ \param camera_index Index of the camera to capture images from.
+ \param skip_frames Number of frames to skip before the capture.
+ \param release_camera Tells if the camera ressource must be released at the end of the method.
+ **/
+ CImg<T>& load_camera(const unsigned int camera_index=0, const unsigned int skip_frames=0,
+ const bool release_camera=true, const unsigned int capture_width=0,
+ const unsigned int capture_height=0) {
+#ifdef cimg_use_opencv
+ if (camera_index>99)
+ throw CImgArgumentException(_cimg_instance
+ "load_camera(): Invalid request for camera #%u "
+ "(no more than 100 cameras can be managed simultaneously).",
+ cimg_instance,
+ camera_index);
+ static CvCapture *capture[100] = { 0 };
+ static unsigned int capture_w[100], capture_h[100];
+ if (release_camera) {
+ cimg::mutex(9);
+ if (capture[camera_index]) cvReleaseCapture(&(capture[camera_index]));
+ capture[camera_index] = 0;
+ capture_w[camera_index] = capture_h[camera_index] = 0;
+ cimg::mutex(9,0);
+ return *this;
+ }
+ if (!capture[camera_index]) {
+ cimg::mutex(9);
+ capture[camera_index] = cvCreateCameraCapture(camera_index);
+ capture_w[camera_index] = 0;
+ capture_h[camera_index] = 0;
+ cimg::mutex(9,0);
+ if (!capture[camera_index]) {
+ throw CImgIOException(_cimg_instance
+ "load_camera(): Failed to initialize camera #%u.",
+ cimg_instance,
+ camera_index);
+ }
+ }
+ cimg::mutex(9);
+ if (capture_width!=capture_w[camera_index]) {
+ cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_WIDTH,capture_width);
+ capture_w[camera_index] = capture_width;
+ }
+ if (capture_height!=capture_h[camera_index]) {
+ cvSetCaptureProperty(capture[camera_index],CV_CAP_PROP_FRAME_HEIGHT,capture_height);
+ capture_h[camera_index] = capture_height;
+ }
+ const IplImage *img = 0;
+ for (unsigned int i = 0; i<skip_frames; ++i) img = cvQueryFrame(capture[camera_index]);
+ img = cvQueryFrame(capture[camera_index]);
+ if (img) {
+ const int step = (int)(img->widthStep - 3*img->width);
+ assign(img->width,img->height,1,3);
+ const unsigned char* ptrs = (unsigned char*)img->imageData;
+ T *ptr_r = data(0,0,0,0), *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2);
+ if (step>0) cimg_forY(*this,y) {
+ cimg_forX(*this,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); }
+ ptrs+=step;
+ } else for (ulongT siz = (ulongT)img->width*img->height; siz; --siz) {
+ *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++);
+ }
+ }
+ cimg::mutex(9,0);
+ return *this;
+#else
+ cimg::unused(camera_index,skip_frames,release_camera,capture_width,capture_height);
+ throw CImgIOException(_cimg_instance
+ "load_camera(): This function requires the OpenCV library to run "
+ "(macro 'cimg_use_opencv' must be defined).",
+ cimg_instance);
+#endif
+ }
+
+ //! Load image from a camera stream, using OpenCV \newinstance.
+ static CImg<T> get_load_camera(const unsigned int camera_index=0, const unsigned int skip_frames=0,
+ const bool release_camera=true,
+ const unsigned int capture_width=0, const unsigned int capture_height=0) {
+ return CImg<T>().load_camera(camera_index,skip_frames,release_camera,capture_width,capture_height);
+ }
+
+ //! Load image using various non-native ways.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ CImg<T>& load_other(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "load_other(): Specified filename is (null).",
+ cimg_instance);
+
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ try { load_magick(filename); }
+ catch (CImgException&) {
+ try { load_imagemagick_external(filename); }
+ catch (CImgException&) {
+ try { load_graphicsmagick_external(filename); }
+ catch (CImgException&) {
+ try { load_cimg(filename); }
+ catch (CImgException&) {
+ try {
+ std::fclose(cimg::fopen(filename,"rb"));
+ } catch (CImgException&) {
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimg_instance
+ "load_other(): Failed to open file '%s'.",
+ cimg_instance,
+ filename);
+ }
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimg_instance
+ "load_other(): Failed to recognize format of file '%s'.",
+ cimg_instance,
+ filename);
+ }
+ }
+ }
+ }
+ cimg::exception_mode(omode);
+ return *this;
+ }
+
+ //! Load image using various non-native ways \newinstance.
+ static CImg<T> get_load_other(const char *const filename) {
+ return CImg<T>().load_other(filename);
+ }
+
+ //@}
+ //---------------------------
+ //
+ //! \name Data Output
+ //@{
+ //---------------------------
+
+ //! Display information about the image data.
+ /**
+ \param title Name for the considered image.
+ \param display_stats Tells to compute and display image statistics.
+ **/
+ 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;
+ 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 ulongT siz = size(), msiz = siz*sizeof(T), siz1 = siz - 1,
+ mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U, width1 = _width - 1;
+
+ CImg<charT> _title(64);
+ if (!title) cimg_snprintf(_title,_title._width,"CImg<%s>",pixel_type());
+
+ std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = (%u,%u,%u,%u) [%lu %s], %sdata%s = (%s*)%p",
+ cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal,
+ cimg::t_bold,cimg::t_normal,(void*)this,
+ cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum,
+ (unsigned long)(mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20))),
+ mdisp==0?"b":(mdisp==1?"Kio":"Mio"),
+ cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin());
+ if (_data)
+ std::fprintf(cimg::output(),"..%p (%s) = [ ",(void*)((char*)end() - 1),_is_shared?"shared":"non-shared");
+ else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared");
+
+ if (!is_empty()) cimg_foroff(*this,off) {
+ std::fprintf(cimg::output(),"%g",(double)_data[off]);
+ if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" ");
+ if (off==7 && siz>16) { off = siz1 - 8; std::fprintf(cimg::output(),"... "); }
+ }
+ if (!is_empty() && display_stats)
+ std::fprintf(cimg::output(),
+ " ], %smin%s = %g, %smax%s = %g, %smean%s = %g, %sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), "
+ "%scoords_max%s = (%u,%u,%u,%u).\n",
+ cimg::t_bold,cimg::t_normal,st[0],
+ cimg::t_bold,cimg::t_normal,st[1],
+ cimg::t_bold,cimg::t_normal,st[2],
+ cimg::t_bold,cimg::t_normal,std::sqrt(st[3]),
+ cimg::t_bold,cimg::t_normal,xm,ym,zm,vm,
+ cimg::t_bold,cimg::t_normal,xM,yM,zM,vM);
+ else std::fprintf(cimg::output(),"%s].\n",is_empty()?"":" ");
+ std::fflush(cimg::output());
+ return *this;
+ }
+
+ //! Display image into a CImgDisplay window.
+ /**
+ \param disp Display window.
+ **/
+ const CImg<T>& display(CImgDisplay& disp) const {
+ disp.display(*this);
+ return *this;
+ }
+
+ //! Display image into a CImgDisplay window, in an interactive way.
+ /**
+ \param disp Display window.
+ \param display_info Tells if image information are displayed on the standard output.
+ **/
+ const CImg<T>& display(CImgDisplay &disp, const bool display_info, unsigned int *const XYZ=0,
+ const bool exit_on_anykey=false) const {
+ return _display(disp,0,display_info,XYZ,exit_on_anykey,false);
+ }
+
+ //! Display image into an interactive window.
+ /**
+ \param title Window title
+ \param display_info Tells if image information are displayed on the standard output.
+ **/
+ const CImg<T>& display(const char *const title=0, const bool display_info=true, unsigned int *const XYZ=0,
+ const bool exit_on_anykey=false) const {
+ CImgDisplay disp;
+ return _display(disp,title,display_info,XYZ,exit_on_anykey,false);
+ }
+
+ const CImg<T>& _display(CImgDisplay &disp, const char *const title, const bool display_info,
+ unsigned int *const XYZ, const bool exit_on_anykey,
+ const bool exit_on_simpleclick) const {
+ unsigned int oldw = 0, oldh = 0, _XYZ[3] = { 0 }, key = 0;
+ int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1,
+ old_mouse_x = -1, old_mouse_y = -1;
+
+ if (!disp) {
+ disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,1);
+ if (!title) disp.set_title("CImg<%s> (%ux%ux%ux%u)",pixel_type(),_width,_height,_depth,_spectrum);
+ else disp.set_title("%s",title);
+ } else if (title) disp.set_title("%s",title);
+ disp.show().flush();
+
+ const CImg<char> dtitle = CImg<char>::string(disp.title());
+ if (display_info) print(dtitle);
+
+ CImg<T> zoom;
+ for (bool reset_view = true, resize_disp = false, is_first_select = true; !key && !disp.is_closed(); ) {
+ if (reset_view) {
+ if (XYZ) { _XYZ[0] = XYZ[0]; _XYZ[1] = XYZ[1]; _XYZ[2] = XYZ[2]; }
+ else {
+ _XYZ[0] = (unsigned int)(x0 + x1)/2;
+ _XYZ[1] = (unsigned int)(y0 + y1)/2;
+ _XYZ[2] = (unsigned int)(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) {
+ if (is_empty()) zoom.assign(1,1,1,1,(T)0); else zoom.assign();
+ } else zoom = get_crop(x0,y0,z0,x1,y1,z1);
+
+ const CImg<T>& visu = zoom?zoom:*this;
+ const unsigned int
+ dx = 1U + x1 - x0, dy = 1U + y1 - y0, dz = 1U + z1 - z0,
+ tw = dx + (dz>1?dz:0U), th = dy + (dz>1?dz:0U);
+ if (!is_empty() && !disp.is_fullscreen() && resize_disp) {
+ const unsigned int
+ ttw = tw*disp.width()/oldw, tth = th*disp.height()/oldh,
+ dM = std::max(ttw,tth), diM = (unsigned int)std::max(disp.width(),disp.height()),
+ imgw = std::max(16U,ttw*diM/dM), imgh = std::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;
+
+ disp.set_title("%s",dtitle._data);
+ if (_width>1 && visu._width==1) disp.set_title("%s | x=%u",disp._title,x0);
+ if (_height>1 && visu._height==1) disp.set_title("%s | y=%u",disp._title,y0);
+ if (_depth>1 && visu._depth==1) disp.set_title("%s | z=%u",disp._title,z0);
+
+ if (!is_first_select) {
+ _XYZ[0] = (unsigned int)(x1 - x0)/2;
+ _XYZ[1] = (unsigned int)(y1 - y0)/2;
+ _XYZ[2] = (unsigned int)(z1 - z0)/2;
+ }
+
+ disp._mouse_x = old_mouse_x; disp._mouse_y = old_mouse_y;
+ const CImg<intT> selection = visu._select(disp,0,2,_XYZ,x0,y0,z0,true,is_first_select,_depth>1);
+ old_mouse_x = disp._mouse_x; old_mouse_y = disp._mouse_y;
+ is_first_select = false;
+
+ if (disp.wheel()) {
+ if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ go_down = !(go_up = disp.wheel()>0);
+ } else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) {
+ go_left = !(go_right = disp.wheel()>0);
+ }
+ else if (disp.is_keyALT() || disp.is_keyALTGR() || _depth==1) {
+ go_out = !(go_in = disp.wheel()>0); go_in_center = false;
+ }
+ disp.set_wheel();
+ }
+
+ const int
+ sx0 = selection(0), sy0 = selection(1), sz0 = selection(2),
+ sx1 = selection(3), sy1 = selection(4), sz1 = selection(5);
+ if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) {
+ x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1;
+ x0+=sx0; y0+=sy0; z0+=sz0;
+ if (sx0==sx1 && sy0==sy1 && sz0==sz1) {
+ if (exit_on_simpleclick && (!zoom || is_empty())) break; else reset_view = true;
+ }
+ resize_disp = true;
+ } else switch (key = disp.key()) {
+#if cimg_OS!=2
+ case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT :
+#endif
+ case 0 : case cimg::keyCTRLLEFT : case cimg::keyPAD5 : case cimg::keySHIFTLEFT :
+#if cimg_OS!=2
+ case cimg::keyALTGR :
+#endif
+ case cimg::keyALT : key = 0; break;
+ case cimg::keyP : if (visu._depth>1 && (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT())) {
+ // Special mode: play stack of frames
+ const unsigned int
+ w1 = visu._width*disp.width()/(visu._width + (visu._depth>1?visu._depth:0)),
+ h1 = visu._height*disp.height()/(visu._height + (visu._depth>1?visu._depth:0));
+ float frame_timing = 5;
+ bool is_stopped = false;
+ disp.set_key(key,false).set_wheel().resize(cimg_fitscreen(w1,h1,1),false); key = 0;
+ for (unsigned int timer = 0; !key && !disp.is_closed() && !disp.button(); ) {
+ if (disp.is_resized()) disp.resize(false);
+ if (!timer) {
+ visu.get_slice((int)_XYZ[2]).display(disp.set_title("%s | z=%d",dtitle.data(),_XYZ[2]));
+ (++_XYZ[2])%=visu._depth;
+ }
+ if (!is_stopped) { if (++timer>(unsigned int)frame_timing) timer = 0; } else timer = ~0U;
+ if (disp.wheel()) { frame_timing-=disp.wheel()/3.0f; disp.set_wheel(); }
+ switch (key = disp.key()) {
+#if cimg_OS!=2
+ case cimg::keyCTRLRIGHT :
+#endif
+ case cimg::keyCTRLLEFT : key = 0; break;
+ case cimg::keyPAGEUP : frame_timing-=0.3f; key = 0; break;
+ case cimg::keyPAGEDOWN : frame_timing+=0.3f; key = 0; break;
+ case cimg::keySPACE : is_stopped = !is_stopped; disp.set_key(key,false); key = 0; break;
+ case cimg::keyARROWLEFT : case cimg::keyARROWUP : is_stopped = true; timer = 0; key = 0; break;
+ case cimg::keyARROWRIGHT : case cimg::keyARROWDOWN : is_stopped = true;
+ (_XYZ[2]+=visu._depth - 2)%=visu._depth; timer = 0; key = 0; break;
+ case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
+ CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false);
+ disp.set_key(key,false); key = 0;
+ } break;
+ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false).set_key(key,false); key = 0;
+ } break;
+ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(cimg_fitscreen(_width,_height,_depth),false).set_key(key,false); key = 0;
+ } break;
+ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.resize(disp.screen_width(),disp.screen_height(),false).
+ toggle_fullscreen().set_key(key,false); key = 0;
+ } break;
+ }
+ frame_timing = frame_timing<1?1:(frame_timing>39?39:frame_timing);
+ disp.wait(20);
+ }
+ const unsigned int
+ w2 = (visu._width + (visu._depth>1?visu._depth:0))*disp.width()/visu._width,
+ h2 = (visu._height + (visu._depth>1?visu._depth:0))*disp.height()/visu._height;
+ disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(dtitle.data()).set_key().set_button().set_wheel();
+ key = 0;
+ } break;
+ case cimg::keyHOME : reset_view = resize_disp = true; key = 0; break;
+ case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break;
+ case cimg::keyPADSUB : go_out = true; key = 0; break;
+ case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break;
+ case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break;
+ case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break;
+ case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break;
+ case cimg::keyPAD7 : go_up = go_left = true; key = 0; break;
+ case cimg::keyPAD9 : go_up = go_right = true; key = 0; break;
+ case cimg::keyPAD1 : go_down = go_left = true; key = 0; break;
+ case cimg::keyPAD3 : go_down = go_right = true; key = 0; break;
+ case cimg::keyPAGEUP : go_inc = true; key = 0; break;
+ case cimg::keyPAGEDOWN : go_dec = true; key = 0; break;
+ }
+ if (go_in) {
+ const int
+ mx = go_in_center?disp.width()/2:disp.mouse_x(),
+ my = go_in_center?disp.height()/2:disp.mouse_y(),
+ mX = mx*(width() + (depth()>1?depth():0))/disp.width(),
+ mY = my*(height() + (depth()>1?depth():0))/disp.height();
+ int X = (int)_XYZ[0], Y = (int)_XYZ[1], Z = (int)_XYZ[2];
+ if (mX<width() && mY<height()) {
+ X = x0 + mX*(1 + x1 - x0)/width(); Y = y0 + mY*(1 + y1 - y0)/height(); Z = (int)_XYZ[2];
+ }
+ if (mX<width() && mY>=height()) {
+ X = x0 + mX*(1 + x1 - x0)/width(); Z = z0 + (mY - height())*(1 + z1 - z0)/depth(); Y = (int)_XYZ[1];
+ }
+ if (mX>=width() && mY<height()) {
+ Y = y0 + mY*(1 + y1 - y0)/height(); Z = z0 + (mX - width())*(1 + z1 - z0)/depth(); X = (int)_XYZ[0];
+ }
+ if (x1 - x0>4) { x0 = X - 3*(X - x0)/4; x1 = X + 3*(x1 - X)/4; }
+ if (y1 - y0>4) { y0 = Y - 3*(Y - y0)/4; y1 = Y + 3*(y1 - Y)/4; }
+ if (z1 - z0>4) { z0 = Z - 3*(Z - z0)/4; z1 = Z + 3*(z1 - Z)/4; }
+ }
+ if (go_out) {
+ const int
+ delta_x = (x1 - x0)/8, delta_y = (y1 - y0)/8, delta_z = (z1 - z0)/8,
+ ndelta_x = delta_x?delta_x:(_width>1),
+ ndelta_y = delta_y?delta_y:(_height>1),
+ ndelta_z = delta_z?delta_z:(_depth>1);
+ x0-=ndelta_x; y0-=ndelta_y; z0-=ndelta_z;
+ x1+=ndelta_x; y1+=ndelta_y; z1+=ndelta_z;
+ 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; }
+ const float
+ ratio = (float)(x1-x0)/(y1-y0),
+ ratiow = (float)disp._width/disp._height,
+ sub = std::min(cimg::abs(ratio - ratiow),cimg::abs(1/ratio-1/ratiow));
+ if (sub>0.01) resize_disp = true;
+ }
+ if (go_left) {
+ const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1);
+ if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; }
+ else { x1-=x0; x0 = 0; }
+ }
+ if (go_right) {
+ const int delta = (x1 - x0)/4, ndelta = delta?delta:(_width>1);
+ if (x1+ndelta<width()) { x0+=ndelta; x1+=ndelta; }
+ else { x0+=(width() - 1 - x1); x1 = width() - 1; }
+ }
+ if (go_up) {
+ const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1);
+ if (y0 - ndelta>=0) { y0-=ndelta; y1-=ndelta; }
+ else { y1-=y0; y0 = 0; }
+ }
+ if (go_down) {
+ const int delta = (y1 - y0)/4, ndelta = delta?delta:(_height>1);
+ if (y1+ndelta<height()) { y0+=ndelta; y1+=ndelta; }
+ else { y0+=(height() - 1 - y1); y1 = height() - 1; }
+ }
+ if (go_inc) {
+ const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1);
+ if (z0 - ndelta>=0) { z0-=ndelta; z1-=ndelta; }
+ else { z1-=z0; z0 = 0; }
+ }
+ if (go_dec) {
+ const int delta = (z1 - z0)/4, ndelta = delta?delta:(_depth>1);
+ if (z1+ndelta<depth()) { z0+=ndelta; z1+=ndelta; }
+ else { z0+=(depth() - 1 - z1); z1 = depth() - 1; }
+ }
+ disp.wait(100);
+ if (!exit_on_anykey && key && key!=cimg::keyESC &&
+ (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
+ key = 0;
+ }
+ }
+ disp.set_key(key);
+ if (XYZ) { XYZ[0] = _XYZ[0]; XYZ[1] = _XYZ[1]; XYZ[2] = _XYZ[2]; }
+ return *this;
+ }
+
+ //! Display object 3d in an interactive window.
+ /**
+ \param disp Display window.
+ \param vertices Vertices data of the 3d object.
+ \param primitives Primitives data of the 3d object.
+ \param colors Colors data of the 3d object.
+ \param opacities Opacities data of the 3d object.
+ \param centering Tells if the 3d object must be centered for the display.
+ \param render_static Rendering mode.
+ \param render_motion Rendering mode, when the 3d object is moved.
+ \param is_double_sided Tells if the object primitives are double-sided.
+ \param focale Focale
+ \param light_x X-coordinate of the light source.
+ \param light_y Y-coordinate of the light source.
+ \param light_z Z-coordinate of the light source.
+ \param specular_lightness Amount of specular light.
+ \param specular_shininess Shininess of the object material.
+ \param display_axes Tells if the 3d axes are displayed.
+ \param pose_matrix Pointer to 12 values, defining a 3d pose (as a 4x3 matrix).
+ **/
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ return _display_object3d(disp,0,vertices,primitives,colors,opacities,centering,render_static,
+ render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+ //! Display object 3d in an interactive window \simplification.
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ CImgDisplay disp;
+ return _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,render_static,
+ render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+ //! Display object 3d in an interactive window \simplification.
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ return display_object3d(disp,vertices,primitives,colors,CImgList<floatT>(),centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+ //! Display object 3d in an interactive window \simplification.
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ return display_object3d(title,vertices,primitives,colors,CImgList<floatT>(),centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+ //! Display object 3d in an interactive window \simplification.
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ return display_object3d(disp,vertices,primitives,CImgList<T>(),centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+
+ //! Display object 3d in an interactive window \simplification.
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ return display_object3d(title,vertices,primitives,CImgList<T>(),centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+ //! Display object 3d in an interactive window \simplification.
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ return display_object3d(disp,vertices,CImgList<uintT>(),centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+ //! Display object 3d in an interactive window \simplification.
+ 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 is_double_sided=true, const float focale=700,
+ const float light_x=0, const float light_y=0, const float light_z=-5e8f,
+ const float specular_lightness=0.2f, const float specular_shininess=0.1f,
+ const bool display_axes=true, float *const pose_matrix=0,
+ const bool exit_on_anykey=false) const {
+ return display_object3d(title,vertices,CImgList<uintT>(),centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+
+ 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 is_double_sided, const float focale,
+ const float light_x, const float light_y, const float light_z,
+ const float specular_lightness, const float specular_shininess,
+ const bool display_axes, float *const pose_matrix,
+ const bool exit_on_anykey) 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,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ else return CImg<T>(1,2,1,1,64,128).resize(cimg_fitscreen(CImgDisplay::screen_width()/2,
+ CImgDisplay::screen_height()/2,1),
+ 1,(colors && colors[0].size()==1)?1:3,3).
+ _display_object3d(disp,title,vertices,primitives,colors,opacities,centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ } else { if (disp) disp.resize(*this,false); }
+ CImg<charT> error_message(1024);
+ 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.data());
+ if (vertices._width && !primitives) {
+ CImgList<tf> nprimitives(vertices._width,1,1,1,1);
+ cimglist_for(nprimitives,l) nprimitives(l,0) = (tf)l;
+ return _display_object3d(disp,title,vertices,nprimitives,colors,opacities,centering,
+ render_static,render_motion,is_double_sided,focale,
+ light_x,light_y,light_z,specular_lightness,specular_shininess,
+ display_axes,pose_matrix,exit_on_anykey);
+ }
+ if (!disp) {
+ disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3);
+ if (!title) disp.set_title("CImg<%s> (%u vertices, %u primitives)",
+ pixel_type(),vertices._width,primitives._width);
+ } else if (title) disp.set_title("%s",title);
+
+ // Init 3d objects and compute object statistics
+ CImg<floatT>
+ pose,
+ rotated_vertices(vertices._width,3),
+ bbox_vertices, rotated_bbox_vertices,
+ axes_vertices, rotated_axes_vertices,
+ bbox_opacities, axes_opacities;
+ CImgList<uintT> bbox_primitives, axes_primitives;
+ CImgList<tf> reverse_primitives;
+ CImgList<T> bbox_colors, bbox_colors2, axes_colors;
+ unsigned int ns_width = 0, ns_height = 0;
+ int _is_double_sided = (int)is_double_sided;
+ bool ndisplay_axes = display_axes;
+ const CImg<T>
+ background_color(1,1,1,_spectrum,0),
+ foreground_color(1,1,1,_spectrum,255);
+ float
+ Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1,
+ xm = 0, xM = vertices?vertices.get_shared_row(0).max_min(xm):0,
+ ym = 0, yM = vertices?vertices.get_shared_row(1).max_min(ym):0,
+ zm = 0, zM = vertices?vertices.get_shared_row(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*std::min(disp.width(),disp.height())/(3.0f*delta)):1,
+ dx = (xM + xm)/2, dy = (yM + ym)/2, dz = (zM + zm)/2;
+ if (centering)
+ CImg<floatT>(4,3,1,1, ratio,0.,0.,-ratio*dx, 0.,ratio,0.,-ratio*dy, 0.,0.,ratio,-ratio*dz).move_to(pose);
+ else CImg<floatT>(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0).move_to(pose);
+ if (pose_matrix) {
+ CImg<floatT> pose0(pose_matrix,4,3,1,1,false);
+ pose0.resize(4,4,1,1,0); pose.resize(4,4,1,1,0);
+ pose0(3,3) = pose(3,3) = 1;
+ (pose0*pose).get_crop(0,0,3,2).move_to(pose);
+ Xoff = pose_matrix[12]; Yoff = pose_matrix[13]; Zoff = pose_matrix[14]; sprite_scale = pose_matrix[15];
+ } else { Xoff = Yoff = Zoff = 0; sprite_scale = 1; }
+ init_pose = false;
+ redraw = true;
+ }
+
+ // Rotate and draw 3d object
+ if (redraw) {
+ const float
+ r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0),
+ r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1),
+ r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2);
+ if ((clicked && nrender_motion>=0) || (!clicked && nrender_static>=0))
+ cimg_forX(vertices,l) {
+ const float x = (float)vertices(l,0), y = (float)vertices(l,1), z = (float)vertices(l,2);
+ rotated_vertices(l,0) = r00*x + r10*y + r20*z + r30;
+ rotated_vertices(l,1) = r01*x + r11*y + r21*z + r31;
+ rotated_vertices(l,2) = r02*x + r12*y + r22*z + r32;
+ }
+ else cimg_forX(bbox_vertices,l) {
+ const float x = bbox_vertices(l,0), y = bbox_vertices(l,1), z = bbox_vertices(l,2);
+ rotated_bbox_vertices(l,0) = r00*x + r10*y + r20*z + r30;
+ rotated_bbox_vertices(l,1) = r01*x + r11*y + r21*z + r31;
+ rotated_bbox_vertices(l,2) = r02*x + r12*y + r22*z + r32;
+ }
+
+ // Draw objects
+ const bool render_with_zbuffer = !clicked && nrender_static>0;
+ 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,render_with_zbuffer?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,_is_double_sided==1,focale,
+ width()/2.0f + light_x,height()/2.0f + light_y,light_z + Zoff,
+ specular_lightness,specular_shininess,sprite_scale);
+ // Draw axes
+ if (ndisplay_axes) {
+ const float
+ n = 1e-8f + cimg::hypot(r00,r01,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*std::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 = cimg::hypot(u0,v0),
+ n1 = cimg::hypot(u1,v1),
+ nu0 = n0>R?(u0*R/n0):u0,
+ nv0 = n0>R?(v0*R/n0):v0,
+ nw0 = (float)std::sqrt(std::max(0.0f,R2 - nu0*nu0 - nv0*nv0)),
+ nu1 = n1>R?(u1*R/n1):u1,
+ nv1 = n1>R?(v1*R/n1):v1,
+ nw1 = (float)std::sqrt(std::max(0.0f,R2 - nu1*nu1 - nv1*nv1)),
+ u = nv0*nw1 - nw0*nv1,
+ v = nw0*nu1 - nu0*nw1,
+ w = nv0*nu1 - nu0*nv1,
+ n = cimg::hypot(u,v,w),
+ alpha = (float)std::asin(n/R2)*180/cimg::PI;
+ (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; }
+
+ CImg<charT> filename(32);
+ 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 = 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 (--_is_double_sided==-2) _is_double_sided = 1;
+ if (_is_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;
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++);
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+visu).draw_text(0,0," Saving snapshot... ",
+ foreground_color._data,background_color._data,0.7f,13).display(disp);
+ visu.save(filename);
+ (+visu).draw_text(0,0," Snapshot '%s' saved. ",
+ foreground_color._data,background_color._data,0.7f,13,filename._data).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;
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.off",snap_number++);
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+visu).draw_text(0,0," Saving object... ",
+ foreground_color._data,background_color._data,0.7f,13).display(disp);
+ vertices.save_off(reverse_primitives?reverse_primitives:primitives,colors,filename);
+ (+visu).draw_text(0,0," Object '%s' saved. ",
+ foreground_color._data,background_color._data,0.7f,13,filename._data).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;
+ std::FILE *file;
+ do {
+#ifdef cimg_use_zlib
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+visu).draw_text(0,0," Saving object... ",
+ foreground_color._data,background_color._data,0.7f,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,0.7f,13,filename._data).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;
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.eps",snap_number++);
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+visu).draw_text(0,0," Saving EPS snapshot... ",
+ foreground_color._data,background_color._data,0.7f,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,
+ _is_double_sided==1,focale,
+ visu.width()/2.0f + light_x,visu.height()/2.0f + light_y,light_z + Zoff,
+ specular_lightness,specular_shininess,
+ sprite_scale);
+ board.saveEPS(filename);
+ (+visu).draw_text(0,0," Object '%s' saved. ",
+ foreground_color._data,background_color._data,0.7f,13,filename._data).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;
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.svg",snap_number++);
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+visu).draw_text(0,0," Saving SVG snapshot... ",
+ foreground_color._data,background_color._data,0.7f,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,
+ _is_double_sided==1,focale,
+ visu.width()/2.0f + light_x,visu.height()/2.0f + light_y,light_z + Zoff,
+ specular_lightness,specular_shininess,
+ sprite_scale);
+ board.saveSVG(filename);
+ (+visu).draw_text(0,0," Object '%s' saved. ",
+ foreground_color._data,background_color._data,0.7f,13,filename._data).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 (!exit_on_anykey && key && key!=cimg::keyESC &&
+ (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
+ key = 0;
+ }
+ }
+ 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;
+ }
+
+ //! Display 1d graph in an interactive window.
+ /**
+ \param disp Display window.
+ \param plot_type Plot type. Can be <tt>{ 0=points | 1=segments | 2=splines | 3=bars }</tt>.
+ \param vertex_type Vertex type.
+ \param labelx Title for the horizontal axis, as a C-string.
+ \param xmin Minimum value along the X-axis.
+ \param xmax Maximum value along the X-axis.
+ \param labely Title for the vertical axis, as a C-string.
+ \param ymin Minimum value along the X-axis.
+ \param ymax Maximum value along the X-axis.
+ **/
+ 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 bool exit_on_anykey=false) const {
+ return _display_graph(disp,0,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey);
+ }
+
+ //! Display 1d graph in an interactive window \overloading.
+ 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 bool exit_on_anykey=false) const {
+ CImgDisplay disp;
+ return _display_graph(disp,title,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax,exit_on_anykey);
+ }
+
+ const CImg<T>& _display_graph(CImgDisplay &disp, 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 bool exit_on_anykey=false) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimg_instance
+ "display_graph(): Empty instance.",
+ cimg_instance);
+ if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0).
+ set_title(title?"%s":"CImg<%s>",title?title:pixel_type());
+ const ulongT siz = (ulongT)_width*_height*_depth, siz1 = std::max((ulongT)1,siz - 1);
+ const unsigned int old_normalization = disp.normalization();
+ disp.show().flush()._normalization = 0;
+
+ double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax;
+ if (nxmin==nxmax) { nxmin = 0; nxmax = siz1; }
+ int x0 = 0, x1 = width()*height()*depth() - 1, key = 0;
+
+ for (bool reset_view = true; !key && !disp.is_closed(); ) {
+ if (reset_view) { x0 = 0; x1 = width()*height()*depth() - 1; y0 = ymin; y1 = ymax; reset_view = false; }
+ CImg<T> zoom(x1 - x0 + 1,1,1,spectrum());
+ cimg_forC(*this,c) zoom.get_shared_channel(c) = CImg<T>(data(x0,0,0,c),x1 - x0 + 1,1,1,1,true);
+ if (y0==y1) { y0 = zoom.min_max(y1); const double dy = y1 - y0; y0-=dy/20; y1+=dy/20; }
+ if (y0==y1) { --y0; ++y1; }
+
+ const CImg<intT> selection = zoom.get_select_graph(disp,plot_type,vertex_type,
+ labelx,
+ nxmin + x0*(nxmax - nxmin)/siz1,
+ nxmin + x1*(nxmax - nxmin)/siz1,
+ labely,y0,y1,true);
+ const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y();
+ if (selection[0]>=0) {
+ if (selection[2]<0) reset_view = true;
+ else {
+ x1 = x0 + selection[2]; x0+=selection[0];
+ if (selection[1]>=0 && selection[3]>=0) {
+ y0 = y1 - selection[3]*(y1 - y0)/(disp.height() - 32);
+ y1-=selection[1]*(y1 - y0)/(disp.height() - 32);
+ }
+ }
+ } else {
+ bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false;
+ switch (key = (int)disp.key()) {
+ case cimg::keyHOME : reset_view = 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_up = !(go_down = disp.wheel()<0);
+ else if (disp.is_keySHIFTLEFT() || disp.is_keySHIFTRIGHT()) go_left = !(go_right = disp.wheel()>0);
+ else go_out = !(go_in = disp.wheel()>0);
+ key = 0;
+ }
+
+ if (go_in) {
+ const int
+ xsiz = x1 - x0,
+ mx = (mouse_x - 16)*xsiz/(disp.width() - 32),
+ cx = x0 + cimg::cut(mx,0,xsiz);
+ 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 - cimg::cut(my,0.0,ysiz);
+ y0 = cy - 7*(cy - y0)/8; y1 = cy + 7*(y1 - cy)/8;
+ } else y0 = y1 = 0;
+ }
+ }
+ if (go_out) {
+ if (x0>0 || x1<(int)siz1) {
+ const int delta_x = (x1 - x0)/8, ndelta_x = delta_x?delta_x:(siz>1);
+ const double ndelta_y = (y1 - y0)/8;
+ x0-=ndelta_x; x1+=ndelta_x;
+ y0-=ndelta_y; y1+=ndelta_y;
+ if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz1; }
+ if (x1>=(int)siz) { x0-=(x1 - siz1); x1 = (int)siz1; if (x0<0) x0 = 0; }
+ }
+ }
+ if (go_left) {
+ const int delta = (x1 - x0)/5, ndelta = delta?delta:1;
+ if (x0 - ndelta>=0) { x0-=ndelta; x1-=ndelta; }
+ else { x1-=x0; x0 = 0; }
+ go_left = false;
+ }
+ if (go_right) {
+ const int delta = (x1 - x0)/5, ndelta = delta?delta:1;
+ if (x1 + ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; }
+ else { x0+=(siz1 - x1); x1 = (int)siz1; }
+ go_right = false;
+ }
+ if (go_up) {
+ const double delta = (y1 - y0)/10, ndelta = delta?delta:1;
+ y0+=ndelta; y1+=ndelta;
+ go_up = false;
+ }
+ if (go_down) {
+ const double delta = (y1 - y0)/10, ndelta = delta?delta:1;
+ y0-=ndelta; y1-=ndelta;
+ go_down = false;
+ }
+ }
+ if (!exit_on_anykey && key && key!=(int)cimg::keyESC &&
+ (key!=(int)cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
+ disp.set_key(key,false);
+ key = 0;
+ }
+ }
+ disp._normalization = old_normalization;
+ return *this;
+ }
+
+ //! Save image as a file.
+ /**
+ \param filename Filename, as a C-string.
+ \param number When positive, represents an index added to the filename. Otherwise, no number is added.
+ \param digits Number of digits used for adding the number to the filename.
+ \note
+ - 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 unsigned int digits=6) 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 bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.');
+ const char *const ext = cimg::split_filename(filename);
+ CImg<charT> nfilename(1024);
+ const char *const fn = is_stdout?filename:(number>=0)?cimg::number_filename(filename,number,digits,nfilename):
+ filename;
+
+#ifdef cimg_save_plugin
+ cimg_save_plugin(fn);
+#endif
+#ifdef cimg_save_plugin1
+ cimg_save_plugin1(fn);
+#endif
+#ifdef cimg_save_plugin2
+ cimg_save_plugin2(fn);
+#endif
+#ifdef cimg_save_plugin3
+ cimg_save_plugin3(fn);
+#endif
+#ifdef cimg_save_plugin4
+ cimg_save_plugin4(fn);
+#endif
+#ifdef cimg_save_plugin5
+ cimg_save_plugin5(fn);
+#endif
+#ifdef cimg_save_plugin6
+ cimg_save_plugin6(fn);
+#endif
+#ifdef cimg_save_plugin7
+ cimg_save_plugin7(fn);
+#endif
+#ifdef cimg_save_plugin8
+ cimg_save_plugin8(fn);
+#endif
+ // Ascii formats
+ if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn);
+ else if (!cimg::strcasecmp(ext,"dlm") ||
+ !cimg::strcasecmp(ext,"txt")) return save_dlm(fn);
+ else if (!cimg::strcasecmp(ext,"cpp") ||
+ !cimg::strcasecmp(ext,"hpp") ||
+ !cimg::strcasecmp(ext,"h") ||
+ !cimg::strcasecmp(ext,"c")) return save_cpp(fn);
+
+ // 2d binary formats
+ else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn);
+ else if (!cimg::strcasecmp(ext,"jpg") ||
+ !cimg::strcasecmp(ext,"jpeg") ||
+ !cimg::strcasecmp(ext,"jpe") ||
+ !cimg::strcasecmp(ext,"jfif") ||
+ !cimg::strcasecmp(ext,"jif")) return save_jpeg(fn);
+ else if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn);
+ else if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn);
+ else if (!cimg::strcasecmp(ext,"png")) return save_png(fn);
+ else if (!cimg::strcasecmp(ext,"pgm") ||
+ !cimg::strcasecmp(ext,"ppm") ||
+ !cimg::strcasecmp(ext,"pnm")) return save_pnm(fn);
+ else if (!cimg::strcasecmp(ext,"pnk")) return save_pnk(fn);
+ else if (!cimg::strcasecmp(ext,"pfm")) return save_pfm(fn);
+ else if (!cimg::strcasecmp(ext,"exr")) return save_exr(fn);
+ else if (!cimg::strcasecmp(ext,"tif") ||
+ !cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
+
+ // 3d binary formats
+ else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
+ else if (!cimg::strcasecmp(ext,"cimg") || !*ext) return save_cimg(fn,false);
+ else if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn);
+ else if (!cimg::strcasecmp(ext,"hdr") ||
+ !cimg::strcasecmp(ext,"nii")) return save_analyze(fn);
+ else if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn);
+ else if (!cimg::strcasecmp(ext,"mnc")) return save_minc2(fn);
+ else if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn);
+ else if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn);
+
+ // Archive files
+ else if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
+
+ // Image sequences
+ else if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
+ else if (!cimg::strcasecmp(ext,"avi") ||
+ !cimg::strcasecmp(ext,"mov") ||
+ !cimg::strcasecmp(ext,"asf") ||
+ !cimg::strcasecmp(ext,"divx") ||
+ !cimg::strcasecmp(ext,"flv") ||
+ !cimg::strcasecmp(ext,"mpg") ||
+ !cimg::strcasecmp(ext,"m1v") ||
+ !cimg::strcasecmp(ext,"m2v") ||
+ !cimg::strcasecmp(ext,"m4v") ||
+ !cimg::strcasecmp(ext,"mjp") ||
+ !cimg::strcasecmp(ext,"mp4") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"ogg") ||
+ !cimg::strcasecmp(ext,"ogv") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) return save_video(fn);
+ return save_other(fn);
+ }
+
+ //! Save image as an ascii file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_ascii(const char *const filename) const {
+ return _save_ascii(0,filename);
+ }
+
+ //! Save image as an ascii file \overloading.
+ const CImg<T>& save_ascii(std::FILE *const file) const {
+ return _save_ascii(file,0);
+ }
+
+ 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);
+ 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,"%.17g ",(double)*(ptrs++));
+ std::fputc('\n',nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save image as a .cpp source file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_cpp(const char *const filename) const {
+ return _save_cpp(0,filename);
+ }
+
+ //! Save image as a .cpp source file \overloading.
+ const CImg<T>& save_cpp(std::FILE *const file) const {
+ return _save_cpp(file,0);
+ }
+
+ 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);
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+ CImg<charT> varname(1024); *varname = 0;
+ if (filename) cimg_sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname._data);
+ if (!*varname) cimg_snprintf(varname,varname._width,"unnamed");
+ std::fprintf(nfile,
+ "/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n"
+ "%s data_%s[] = { %s\n ",
+ varname._data,_width,_height,_depth,_spectrum,pixel_type(),pixel_type(),varname._data,
+ is_empty()?"};":"");
+ if (!is_empty()) for (ulongT 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 image as a DLM file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_dlm(const char *const filename) const {
+ return _save_dlm(0,filename);
+ }
+
+ //! Save image as a DLM file \overloading.
+ const CImg<T>& save_dlm(std::FILE *const file) const {
+ return _save_dlm(file,0);
+ }
+
+ 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()) { cimg::fempty(file,filename); return *this; }
+ 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,"%.17g%s",(double)*(ptrs++),(x==width() - 1)?"":",");
+ std::fputc('\n',nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save image as a BMP file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_bmp(const char *const filename) const {
+ return _save_bmp(0,filename);
+ }
+
+ //! Save image as a BMP file \overloading.
+ const CImg<T>& save_bmp(std::FILE *const file) const {
+ return _save_bmp(file,0);
+ }
+
+ 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()) { cimg::fempty(file,filename); return *this; }
+ 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");
+ CImg<ucharT> header(54,1,1,1,0);
+ unsigned char 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._data,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 image as a JPEG file.
+ /**
+ \param filename Filename, as a C-string.
+ \param quality Image quality (in %)
+ **/
+ const CImg<T>& save_jpeg(const char *const filename, const unsigned int quality=100) const {
+ return _save_jpeg(0,filename,quality);
+ }
+
+ //! Save image as a JPEG file \overloading.
+ const CImg<T>& save_jpeg(std::FILE *const file, const unsigned int quality=100) const {
+ return _save_jpeg(file,0,quality);
+ }
+
+ 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()) { cimg::fempty(file,filename); return *this; }
+ 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 image, using built-in ImageMagick++ library.
+ /**
+ \param filename Filename, as a C-string.
+ \param bytes_per_pixel Force the number of bytes per pixel for the saving, when possible.
+ **/
+ 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()) { cimg::fempty(0,filename); return *this; }
+
+#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 (ulongT off = (ulongT)_width*_height; off; --off) {
+ pixels->red = pixels->green = pixels->blue = (Magick::Quantum)*(ptr_r++);
+ ++pixels;
+ }
+ break;
+ case 2 : // RG images
+ for (ulongT off = (ulongT)_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 (ulongT off = (ulongT)_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);
+ return *this;
+#else
+ cimg::unused(bytes_per_pixel);
+ throw CImgIOException(_cimg_instance
+ "save_magick(): Unable to save file '%s' unless libMagick++ is enabled.",
+ cimg_instance,
+ filename);
+#endif
+ }
+
+ //! Save image as a PNG file.
+ /**
+ \param filename Filename, as a C-string.
+ \param bytes_per_pixel Force the number of bytes per pixels for the saving, when possible.
+ **/
+ 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 image as a PNG file \overloading.
+ 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);
+ }
+
+ const CImg<T>& _save_png(std::FILE *const file, const char *const filename,
+ const unsigned int bytes_per_pixel=0) const {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_png(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(file,filename); return *this; }
+
+#ifndef cimg_use_png
+ cimg::unused(bytes_per_pixel);
+ if (!file) return save_other(filename);
+ else throw CImgIOException(_cimg_instance
+ "save_png(): Unable to save data in '(*FILE)' unless libpng is enabled.",
+ cimg_instance);
+#else
+
+#if defined __GNUC__
+ const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning.
+ std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb");
+ volatile double stmin, stmax = (double)max_min(stmin);
+#else
+ const char *nfilename = filename;
+ std::FILE *nfile = file?file:cimg::fopen(nfilename,"wb");
+ double stmin, stmax = (double)max_min(stmin);
+#endif
+
+ if (_depth>1)
+ cimg::warn(_cimg_instance
+ "save_png(): Instance is volumetric, only the first slice will be saved in file '%s'.",
+ cimg_instance,
+ filename);
+
+ if (_spectrum>4)
+ cimg::warn(_cimg_instance
+ "save_png(): Instance is multispectral, only the three first channels will be saved in file '%s'.",
+ cimg_instance,
+ filename);
+
+ if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
+ cimg::warn(_cimg_instance
+ "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
+ cimg_instance,
+ 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 image as a PNM file.
+ /**
+ \param filename Filename, as a C-string.
+ \param bytes_per_pixel Force the number of bytes per pixels for the saving.
+ **/
+ 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 image as a PNM file \overloading.
+ 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);
+ }
+
+ 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()) { cimg::fempty(file,filename); return *this; }
+
+ 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 ulongT buf_size = std::min((ulongT)(1024*1024),(ulongT)(_width*_height*(_spectrum==1?1UL:3UL)));
+
+ 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((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size);
+ unsigned char *ptrd = buf._data;
+ for (ulongT i = 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((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size);
+ unsigned short *ptrd = buf._data;
+ for (ulongT i = 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((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size/3);
+ unsigned char *ptrd = buf._data;
+ for (ulongT i = 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((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size/3);
+ unsigned short *ptrd = buf._data;
+ for (ulongT i = 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((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size/3);
+ unsigned char *ptrd = buf._data;
+ for (ulongT i = 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((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size/3);
+ unsigned short *ptrd = buf._data;
+ for (ulongT i = 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 image as a PNK file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_pnk(const char *const filename) const {
+ return _save_pnk(0,filename);
+ }
+
+ //! Save image as a PNK file \overloading.
+ const CImg<T>& save_pnk(std::FILE *const file) const {
+ return _save_pnk(file,0);
+ }
+
+ const CImg<T>& _save_pnk(std::FILE *const file, const char *const filename) const {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_pnk(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(file,filename); return *this; }
+ if (_spectrum>1)
+ cimg::warn(_cimg_instance
+ "save_pnk(): Instance is multispectral, only the first channel will be saved in file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+
+ const ulongT buf_size = std::min((ulongT)1024*1024,(ulongT)_width*_height*_depth);
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const T *ptr = data(0,0,0,0);
+
+ if (!cimg::type<T>::is_float() && sizeof(T)==1 && _depth<2) // Can be saved as regular PNM file.
+ _save_pnm(file,filename,0);
+ else if (!cimg::type<T>::is_float() && sizeof(T)==1) { // Save as extended P5 file: Binary byte-valued 3d.
+ std::fprintf(nfile,"P5\n%u %u %u\n255\n",_width,_height,_depth);
+ CImg<ucharT> buf((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size);
+ unsigned char *ptrd = buf._data;
+ for (ulongT i = N; i>0; --i) *(ptrd++) = (unsigned char)*(ptr++);
+ cimg::fwrite(buf._data,N,nfile);
+ to_write-=N;
+ }
+ } else if (!cimg::type<T>::is_float()) { // Save as P8: Binary int32-valued 3d.
+ if (_depth>1) std::fprintf(nfile,"P8\n%u %u %u\n%d\n",_width,_height,_depth,(int)max());
+ else std::fprintf(nfile,"P8\n%u %u\n%d\n",_width,_height,(int)max());
+ CImg<intT> buf((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size);
+ int *ptrd = buf._data;
+ for (ulongT i = N; i>0; --i) *(ptrd++) = (int)*(ptr++);
+ cimg::fwrite(buf._data,N,nfile);
+ to_write-=N;
+ }
+ } else { // Save as P9: Binary float-valued 3d.
+ if (_depth>1) std::fprintf(nfile,"P9\n%u %u %u\n%g\n",_width,_height,_depth,(double)max());
+ else std::fprintf(nfile,"P9\n%u %u\n%g\n",_width,_height,(double)max());
+ CImg<floatT> buf((unsigned int)buf_size);
+ for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,buf_size);
+ float *ptrd = buf._data;
+ for (ulongT i = N; i>0; --i) *(ptrd++) = (float)*(ptr++);
+ cimg::fwrite(buf._data,N,nfile);
+ to_write-=N;
+ }
+ }
+
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save image as a PFM file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_pfm(const char *const filename) const {
+ get_mirror('y')._save_pfm(0,filename);
+ return *this;
+ }
+
+ //! Save image as a PFM file \overloading.
+ const CImg<T>& save_pfm(std::FILE *const file) const {
+ get_mirror('y')._save_pfm(file,0);
+ return *this;
+ }
+
+ 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()) { cimg::fempty(file,filename); return *this; }
+ if (_depth>1)
+ cimg::warn(_cimg_instance
+ "save_pfm(): Instance is volumetric, only the first slice will be saved in file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+ if (_spectrum>3)
+ cimg::warn(_cimg_instance
+ "save_pfm(): image instance is multispectral, only the three first channels will be saved "
+ "in file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const T
+ *ptr_r = data(0,0,0,0),
+ *ptr_g = (_spectrum>=2)?data(0,0,0,1):0,
+ *ptr_b = (_spectrum>=3)?data(0,0,0,2):0;
+ const unsigned int buf_size = std::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 (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const ulongT N = std::min((ulongT)to_write,(ulongT)buf_size);
+ float *ptrd = buf._data;
+ for (ulongT i = 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 (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const unsigned int N = std::min((unsigned int)to_write,buf_size/3);
+ float *ptrd = buf._data;
+ for (ulongT i = 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 (longT to_write = (longT)width()*height(); to_write>0; ) {
+ const unsigned int N = std::min((unsigned int)to_write,buf_size/3);
+ float *ptrd = buf._data;
+ for (ulongT i = 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 image as a RGB file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_rgb(const char *const filename) const {
+ return _save_rgb(0,filename);
+ }
+
+ //! Save image as a RGB file \overloading.
+ const CImg<T>& save_rgb(std::FILE *const file) const {
+ return _save_rgb(file,0);
+ }
+
+ 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()) { cimg::fempty(file,filename); return *this; }
+ if (_spectrum!=3)
+ cimg::warn(_cimg_instance
+ "save_rgb(): image instance has not exactly 3 channels, for file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const ulongT wh = (ulongT)_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 (ulongT k = 0; k<wh; ++k) {
+ const unsigned char val = (unsigned char)*(ptr1++);
+ *(nbuffer++) = val;
+ *(nbuffer++) = val;
+ *(nbuffer++) = val;
+ }
+ } break;
+ case 2 : { // RG image
+ for (ulongT k = 0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = 0;
+ }
+ } break;
+ default : { // RGB image
+ for (ulongT 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 image as a RGBA file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ const CImg<T>& save_rgba(const char *const filename) const {
+ return _save_rgba(0,filename);
+ }
+
+ //! Save image as a RGBA file \overloading.
+ const CImg<T>& save_rgba(std::FILE *const file) const {
+ return _save_rgba(file,0);
+ }
+
+ 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()) { cimg::fempty(file,filename); return *this; }
+ if (_spectrum!=4)
+ cimg::warn(_cimg_instance
+ "save_rgba(): image instance has not exactly 4 channels, for file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const ulongT wh = (ulongT)_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 (ulongT 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 (ulongT k = 0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = 0;
+ *(nbuffer++) = 255;
+ }
+ } break;
+ case 3 : { // RGB images
+ for (ulongT k = 0; k<wh; ++k) {
+ *(nbuffer++) = (unsigned char)(*(ptr1++));
+ *(nbuffer++) = (unsigned char)(*(ptr2++));
+ *(nbuffer++) = (unsigned char)(*(ptr3++));
+ *(nbuffer++) = 255;
+ }
+ } break;
+ default : { // RGBA images
+ for (ulongT 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 image as a TIFF file.
+ /**
+ \param filename Filename, as a C-string.
+ \param compression_type Type of data compression. Can be <tt>{ 0=None | 1=LZW | 2=JPEG }</tt>.
+ \note
+ - libtiff support is enabled by defining the precompilation
+ directive \c cimg_use_tif.
+ - When libtiff is enabled, 2D and 3D (multipage) several
+ channel per pixel are supported for
+ <tt>char,uchar,short,ushort,float</tt> and \c double pixel types.
+ - If \c cimg_use_tif is not defined at compile time the
+ function uses CImg<T>&save_other(const char*).
+ **/
+ const CImg<T>& save_tiff(const char *const filename, const unsigned int compression_type=0,
+ const float *const voxel_size=0, const char *const description=0,
+ const bool use_bigtiff=true) const {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_tiff(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(0,filename); return *this; }
+
+#ifdef cimg_use_tiff
+ const bool
+ _use_bigtiff = use_bigtiff && sizeof(ulongT)>=8 && size()*sizeof(T)>=1UL<<31; // No bigtiff for small images.
+ TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4");
+ if (tif) {
+ cimg_forZ(*this,z) _save_tiff(tif,z,z,compression_type,voxel_size,description);
+ TIFFClose(tif);
+ } else throw CImgIOException(_cimg_instance
+ "save_tiff(): Failed to open file '%s' for writing.",
+ cimg_instance,
+ filename);
+ return *this;
+#else
+ cimg::unused(compression_type,voxel_size,description,use_bigtiff);
+ return save_other(filename);
+#endif
+ }
+
+#ifdef cimg_use_tiff
+
+#define _cimg_save_tiff(types,typed,compression_type) if (!std::strcmp(types,pixel_type())) { \
+ const typed foo = (typed)0; return _save_tiff(tif,directory,z,foo,compression_type,voxel_size,description); }
+
+ // [internal] Save a plane into a tiff file
+ template<typename t>
+ const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z, const t& pixel_t,
+ const unsigned int compression_type, const float *const voxel_size,
+ const char *const description) const {
+ if (is_empty() || !tif || pixel_t) return *this;
+ const char *const filename = TIFFFileName(tif);
+ uint32 rowsperstrip = (uint32)-1;
+ uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric;
+ if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB;
+ else photometric = PHOTOMETRIC_MINISBLACK;
+ TIFFSetDirectory(tif,directory);
+ TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,_width);
+ TIFFSetField(tif,TIFFTAG_IMAGELENGTH,_height);
+ if (voxel_size) {
+ const float vx = voxel_size[0], vy = voxel_size[1], vz = voxel_size[2];
+ TIFFSetField(tif,TIFFTAG_RESOLUTIONUNIT,RESUNIT_NONE);
+ TIFFSetField(tif,TIFFTAG_XRESOLUTION,1.0f/vx);
+ TIFFSetField(tif,TIFFTAG_YRESOLUTION,1.0f/vy);
+ CImg<charT> s_description(256);
+ cimg_snprintf(s_description,s_description._width,"VX=%g VY=%g VZ=%g spacing=%g",vx,vy,vz,vz);
+ TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,s_description.data());
+ }
+ if (description) TIFFSetField(tif,TIFFTAG_IMAGEDESCRIPTION,description);
+ 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_type==2?COMPRESSION_JPEG:
+ compression_type==1?COMPRESSION_LZW:COMPRESSION_NONE);
+ rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip);
+ TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip);
+ TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB);
+ TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg");
+ t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif));
+ if (buf) {
+ for (unsigned int row = 0; row<_height; row+=rowsperstrip) {
+ uint32 nrow = (row + rowsperstrip>_height?_height - row:rowsperstrip);
+ tstrip_t strip = TIFFComputeStrip(tif,row,0);
+ tsize_t i = 0;
+ for (unsigned int rr = 0; rr<nrow; ++rr)
+ for (unsigned int cc = 0; cc<_width; ++cc)
+ for (unsigned int vv = 0; vv<spp; ++vv)
+ buf[i++] = (t)(*this)(cc,row + rr,z,vv);
+ if (TIFFWriteEncodedStrip(tif,strip,buf,i*sizeof(t))<0)
+ throw CImgIOException(_cimg_instance
+ "save_tiff(): Invalid strip writing when saving file '%s'.",
+ cimg_instance,
+ filename?filename:"(FILE*)");
+ }
+ _TIFFfree(buf);
+ }
+ TIFFWriteDirectory(tif);
+ return (*this);
+ }
+
+ const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory, const unsigned int z,
+ const unsigned int compression_type, const float *const voxel_size,
+ const char *const description) const {
+ _cimg_save_tiff("bool",unsigned char,compression_type);
+ _cimg_save_tiff("unsigned char",unsigned char,compression_type);
+ _cimg_save_tiff("char",char,compression_type);
+ _cimg_save_tiff("unsigned short",unsigned short,compression_type);
+ _cimg_save_tiff("short",short,compression_type);
+ _cimg_save_tiff("unsigned int",unsigned int,compression_type);
+ _cimg_save_tiff("int",int,compression_type);
+ _cimg_save_tiff("unsigned int64",unsigned int,compression_type);
+ _cimg_save_tiff("int64",int,compression_type);
+ _cimg_save_tiff("float",float,compression_type);
+ _cimg_save_tiff("double",float,compression_type);
+ const char *const filename = TIFFFileName(tif);
+ throw CImgInstanceException(_cimg_instance
+ "save_tiff(): Unsupported pixel type '%s' for file '%s'.",
+ cimg_instance,
+ pixel_type(),filename?filename:"(FILE*)");
+ return *this;
+ }
+#endif
+
+ //! Save image as a MINC2 file.
+ /**
+ \param filename Filename, as a C-string.
+ \param imitate_file If non-zero, reference filename, as a C-string, to borrow header from.
+ **/
+ const CImg<T>& save_minc2(const char *const filename,
+ const char *const imitate_file=0) const {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_minc2(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(0,filename); return *this; }
+
+#ifndef cimg_use_minc2
+ cimg::unused(imitate_file);
+ return save_other(filename);
+#else
+ minc::minc_1_writer wtr;
+ if (imitate_file)
+ wtr.open(filename, imitate_file);
+ else {
+ minc::minc_info di;
+ if (width()) di.push_back(minc::dim_info(width(),width()*0.5,-1,minc::dim_info::DIM_X));
+ if (height()) di.push_back(minc::dim_info(height(),height()*0.5,-1,minc::dim_info::DIM_Y));
+ if (depth()) di.push_back(minc::dim_info(depth(),depth()*0.5,-1,minc::dim_info::DIM_Z));
+ if (spectrum()) di.push_back(minc::dim_info(spectrum(),spectrum()*0.5,-1,minc::dim_info::DIM_TIME));
+ wtr.open(filename,di,1,NC_FLOAT,0);
+ }
+ if (typeid(T)==typeid(unsigned char))
+ wtr.setup_write_byte();
+ else if (typeid(T)==typeid(int))
+ wtr.setup_write_int();
+ else if (typeid(T)==typeid(double))
+ wtr.setup_write_double();
+ else
+ wtr.setup_write_float();
+ minc::save_standard_volume(wtr, this->_data);
+ return *this;
+#endif
+ }
+
+ //! Save image as an ANALYZE7.5 or NIFTI file.
+ /**
+ \param filename Filename, as a C-string.
+ \param voxel_size Pointer to 3 consecutive values that tell about the voxel sizes along the X,Y and Z dimensions.
+ **/
+ const CImg<T>& save_analyze(const char *const filename, const float *const voxel_size=0) const {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_analyze(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(0,filename); return *this; }
+
+ std::FILE *file;
+ CImg<charT> hname(1024), iname(1024);
+ const char *const ext = cimg::split_filename(filename);
+ short datatype = -1;
+ if (!*ext) {
+ cimg_snprintf(hname,hname._width,"%s.hdr",filename);
+ cimg_snprintf(iname,iname._width,"%s.img",filename);
+ }
+ if (!cimg::strncasecmp(ext,"hdr",3)) {
+ std::strcpy(hname,filename);
+ std::strncpy(iname,filename,iname._width - 1);
+ cimg_sprintf(iname._data + std::strlen(iname) - 3,"img");
+ }
+ if (!cimg::strncasecmp(ext,"img",3)) {
+ std::strcpy(hname,filename);
+ std::strncpy(iname,filename,iname._width - 1);
+ cimg_sprintf(hname._data + std::strlen(iname) - 3,"hdr");
+ }
+ if (!cimg::strncasecmp(ext,"nii",3)) {
+ std::strncpy(hname,filename,hname._width - 1); *iname = 0;
+ }
+
+ CImg<charT> header(*iname?348:352,1,1,1,0);
+ int *const iheader = (int*)header._data;
+ *iheader = 348;
+ std::strcpy(header._data + 4,"CImg");
+ std::strcpy(header._data + 14," ");
+ ((short*)&(header[36]))[0] = 4096;
+ ((char*)&(header[38]))[0] = 114;
+ ((short*)&(header[40]))[0] = 4;
+ ((short*)&(header[40]))[1] = (short)_width;
+ ((short*)&(header[40]))[2] = (short)_height;
+ ((short*)&(header[40]))[3] = (short)_depth;
+ ((short*)&(header[40]))[4] = (short)_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 int64")) datatype = 8;
+ if (!cimg::strcasecmp(pixel_type(),"int64")) 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[108]))[0] = (float)(*iname?0:header.width());
+ ((float*)&(header[112]))[0] = 1;
+ ((float*)&(header[76]))[0] = 0;
+ if (voxel_size) {
+ ((float*)&(header[76]))[1] = voxel_size[0];
+ ((float*)&(header[76]))[2] = voxel_size[1];
+ ((float*)&(header[76]))[3] = voxel_size[2];
+ } else ((float*)&(header[76]))[1] = ((float*)&(header[76]))[2] = ((float*)&(header[76]))[3] = 1;
+ file = cimg::fopen(hname,"wb");
+ cimg::fwrite(header._data,header.width(),file);
+ if (*iname) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); }
+ cimg::fwrite(_data,size(),file);
+ cimg::fclose(file);
+ return *this;
+ }
+
+ //! Save image as a .cimg file.
+ /**
+ \param filename Filename, as a C-string.
+ \param is_compressed Tells if the file contains compressed image data.
+ **/
+ const CImg<T>& save_cimg(const char *const filename, const bool is_compressed=false) const {
+ CImgList<T>(*this,true).save_cimg(filename,is_compressed);
+ return *this;
+ }
+
+ //! Save image as a .cimg file \overloading.
+ const CImg<T>& save_cimg(std::FILE *const file, const bool is_compressed=false) const {
+ CImgList<T>(*this,true).save_cimg(file,is_compressed);
+ return *this;
+ }
+
+ //! Save image as a sub-image into an existing .cimg file.
+ /**
+ \param filename Filename, as a C-string.
+ \param n0 Index of the image inside the file.
+ \param x0 X-coordinate of the sub-image location.
+ \param y0 Y-coordinate of the sub-image location.
+ \param z0 Z-coordinate of the sub-image location.
+ \param c0 C-coordinate of the sub-image location.
+ **/
+ 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;
+ }
+
+ //! Save image as a sub-image into an existing .cimg file \overloading.
+ 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 blank image as a .cimg file.
+ /**
+ \param filename Filename, as a C-string.
+ \param dx Width of the image.
+ \param dy Height of the image.
+ \param dz Depth of the image.
+ \param dc Number of channels of the image.
+ \note
+ - All pixel values of the saved image are set to \c 0.
+ - Use this method to save large images without having to instanciate and allocate them.
+ **/
+ 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 blank image as a .cimg file \overloading.
+ /**
+ Same as save_empty_cimg(const char *,unsigned int,unsigned int,unsigned int,unsigned int)
+ with a file stream argument instead of a filename string.
+ **/
+ 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 image as an INRIMAGE-4 file.
+ /**
+ \param filename Filename, as a C-string.
+ \param voxel_size Pointer to 3 values specifying the voxel sizes along the X,Y and Z dimensions.
+ **/
+ const CImg<T>& save_inr(const char *const filename, const float *const voxel_size=0) const {
+ return _save_inr(0,filename,voxel_size);
+ }
+
+ //! Save image as an INRIMAGE-4 file \overloading.
+ const CImg<T>& save_inr(std::FILE *const file, const float *const voxel_size=0) const {
+ return _save_inr(file,0,voxel_size);
+ }
+
+ const CImg<T>& _save_inr(std::FILE *const file, const char *const filename, const float *const voxel_size) const {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_inr(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(file,filename); return *this; }
+
+ 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");
+ CImg<charT> header(257);
+ int err = cimg_snprintf(header,header._width,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",
+ _width,_height,_depth,_spectrum);
+ if (voxel_size) err+=cimg_sprintf(header._data + err,"VX=%g\nVY=%g\nVZ=%g\n",
+ voxel_size[0],voxel_size[1],voxel_size[2]);
+ err+=cimg_sprintf(header._data + err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm");
+ std::memset(header._data + err,'\n',252 - err);
+ std::memcpy(header._data + 252,"##}\n",4);
+ cimg::fwrite(header._data,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 image as an OpenEXR file.
+ /**
+ \param filename Filename, as a C-string.
+ \note The OpenEXR file format is <a href="http://en.wikipedia.org/wiki/OpenEXR">described here</a>.
+ **/
+ 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()) { cimg::fempty(0,filename); return *this; }
+ 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[(size_t)_width*_height], *ptrd = ptrd0, rgba;
+ switch (_spectrum) {
+ case 1 : { // Grayscale image.
+ for (const T *ptr_r = data(), *const ptr_e = ptr_r + (ulongT)_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 + (ulongT)_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 + (ulongT)_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 + (ulongT)_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 image as a Pandore-5 file.
+ /**
+ \param filename Filename, as a C-string.
+ \param colorspace Colorspace data field in output file
+ (see <a href="http://www.greyc.ensicaen.fr/~regis/Pandore">Pandore file specifications</a>
+ for more information).
+ **/
+ const CImg<T>& save_pandore(const char *const filename, const unsigned int colorspace=0) const {
+ return _save_pandore(0,filename,colorspace);
+ }
+
+ //! Save image as a Pandore-5 file \overloading.
+ /**
+ Same as save_pandore(const char *,unsigned int) const
+ with a file stream argument instead of a filename string.
+ **/
+ const CImg<T>& save_pandore(std::FILE *const file, const unsigned int colorspace=0) const {
+ return _save_pandore(file,0,colorspace);
+ }
+
+ 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) { CImg<ulongT> ndims(5); \
+ for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \
+ else if (sizeof(unsigned int)==4) { CImg<uintT> ndims(5); \
+ for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \
+ else if (sizeof(unsigned short)==4) { CImg<ushortT> ndims(5); \
+ for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims._data,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()) { cimg::fempty(file,filename); return *this; }
+
+ 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,"unsigned short",3);
+ _cimg_save_pandore_case(1,1,1,"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 int64",3);
+ _cimg_save_pandore_case(1,1,1,"int64",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,"unsigned short",6);
+ _cimg_save_pandore_case(0,1,1,"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 int64",6);
+ _cimg_save_pandore_case(0,1,1,"int64",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,"unsigned short",9);
+ _cimg_save_pandore_case(0,0,1,"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 int64",9);
+ _cimg_save_pandore_case(0,0,1,"int64",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,"unsigned short",17);
+ _cimg_save_pandore_case(0,1,3,"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 int64",17);
+ _cimg_save_pandore_case(0,1,3,"int64",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,"unsigned short",20);
+ _cimg_save_pandore_case(0,0,3,"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 int64",20);
+ _cimg_save_pandore_case(0,0,3,"int64",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,"unsigned short",23);
+ _cimg_save_pandore_case(1,1,0,"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 int64",23);
+ _cimg_save_pandore_case(1,1,0,"int64",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,"unsigned short",27);
+ _cimg_save_pandore_case(0,1,0,"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 int64",27);
+ _cimg_save_pandore_case(0,1,0,"int64",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,"unsigned short",31);
+ _cimg_save_pandore_case(0,0,0,"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 int64",31);
+ _cimg_save_pandore_case(0,0,0,"int64",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 image as a raw data file.
+ /**
+ \param filename Filename, as a C-string.
+ \param is_multiplexed Tells if the image channels are stored in a multiplexed way (\c true) or not (\c false).
+ \note The .raw format does not store the image dimensions in the output file,
+ so you have to keep track of them somewhere to be able to read the file correctly afterwards.
+ **/
+ const CImg<T>& save_raw(const char *const filename, const bool is_multiplexed=false) const {
+ return _save_raw(0,filename,is_multiplexed);
+ }
+
+ //! Save image as a raw data file \overloading.
+ /**
+ Same as save_raw(const char *,bool) const
+ with a file stream argument instead of a filename string.
+ **/
+ const CImg<T>& save_raw(std::FILE *const file, const bool is_multiplexed=false) const {
+ return _save_raw(file,0,is_multiplexed);
+ }
+
+ const CImg<T>& _save_raw(std::FILE *const file, const char *const filename, const bool is_multiplexed) const {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_raw(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(file,filename); return *this; }
+
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ if (!is_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 image as a .yuv video file.
+ /**
+ \param filename Filename, as a C-string.
+ \param is_rgb Tells if pixel values of the instance image are RGB-coded (\c true) or YUV-coded (\c false).
+ \note Each slice of the instance image is considered to be a single frame of the output video file.
+ **/
+ const CImg<T>& save_yuv(const char *const filename, const bool is_rgb=true) const {
+ get_split('z').save_yuv(filename,is_rgb);
+ return *this;
+ }
+
+ //! Save image as a .yuv video file \overloading.
+ /**
+ Same as save_yuv(const char*,bool) const
+ with a file stream argument instead of a filename string.
+ **/
+ const CImg<T>& save_yuv(std::FILE *const file, const bool is_rgb=true) const {
+ get_split('z').save_yuv(file,is_rgb);
+ return *this;
+ }
+
+ //! Save 3d object as an Object File Format (.off) file.
+ /**
+ \param filename Filename, as a C-string.
+ \param primitives List of 3d object primitives.
+ \param colors List of 3d object colors.
+ \note
+ - Instance image contains the vertices data of the 3d object.
+ - Textured, transparent or sphere-shaped primitives cannot be managed by the .off file format.
+ Such primitives will be lost or simplified during file saving.
+ - The .off file format is <a href="http://people.sc.fsu.edu/~jburkardt/html/off_format.html">described here</a>.
+ **/
+ template<typename tf, typename tc>
+ const CImg<T>& save_off(const CImgList<tf>& primitives, const CImgList<tc>& colors,
+ const char *const filename) const {
+ return _save_off(primitives,colors,0,filename);
+ }
+
+ //! Save 3d object as an Object File Format (.off) file \overloading.
+ /**
+ Same as save_off(const CImgList<tf>&,const CImgList<tc>&,const char*) const
+ with a file stream argument instead of a filename string.
+ **/
+ template<typename tf, typename tc>
+ const CImg<T>& save_off(const CImgList<tf>& primitives, const CImgList<tc>& colors,
+ std::FILE *const file) const {
+ return _save_off(primitives,colors,file,0);
+ }
+
+ template<typename tf, typename tc>
+ const CImg<T>& _save_off(const CImgList<tf>& primitives, const CImgList<tc>& colors,
+ std::FILE *const file, const char *const filename) 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;
+ CImg<charT> error_message(1024);
+ 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.data());
+
+ const CImg<tc> default_color(1,3,1,1,200);
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
+ unsigned int supported_primitives = 0;
+ cimglist_for(primitives,l) if (primitives[l].size()!=5) ++supported_primitives;
+ std::fprintf(nfile,"OFF\n%u %u %u\n",_width,supported_primitives,3*primitives._width);
+ cimg_forX(*this,i) std::fprintf(nfile,"%f %f %f\n",
+ (float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2)));
+ cimglist_for(primitives,l) {
+ const CImg<tc>& color = l<colors.width()?colors[l]:default_color;
+ const unsigned int psiz = primitives[l].size(), csiz = color.size();
+ const float r = color[0]/255.0f, g = (csiz>1?color[1]:r)/255.0f, b = (csiz>2?color[2]:g)/255.0f;
+ switch (psiz) {
+ case 1 : std::fprintf(nfile,"1 %u %f %f %f\n",
+ (unsigned int)primitives(l,0),r,g,b); break;
+ case 2 : std::fprintf(nfile,"2 %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break;
+ case 3 : std::fprintf(nfile,"3 %u %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,2),
+ (unsigned int)primitives(l,1),r,g,b); break;
+ case 4 : std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,3),
+ (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b); break;
+ case 5 : std::fprintf(nfile,"2 %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b); break;
+ case 6 : {
+ const unsigned int xt = (unsigned int)primitives(l,2), yt = (unsigned int)primitives(l,3);
+ const float
+ rt = color.atXY(xt,yt,0)/255.0f,
+ gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f,
+ bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+ std::fprintf(nfile,"2 %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,1),rt,gt,bt);
+ } break;
+ case 9 : {
+ const unsigned int xt = (unsigned int)primitives(l,3), yt = (unsigned int)primitives(l,4);
+ const float
+ rt = color.atXY(xt,yt,0)/255.0f,
+ gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f,
+ bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+ std::fprintf(nfile,"3 %u %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,2),
+ (unsigned int)primitives(l,1),rt,gt,bt);
+ } break;
+ case 12 : {
+ const unsigned int xt = (unsigned int)primitives(l,4), yt = (unsigned int)primitives(l,5);
+ const float
+ rt = color.atXY(xt,yt,0)/255.0f,
+ gt = (csiz>1?color.atXY(xt,yt,1):r)/255.0f,
+ bt = (csiz>2?color.atXY(xt,yt,2):g)/255.0f;
+ std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
+ (unsigned int)primitives(l,0),(unsigned int)primitives(l,3),
+ (unsigned int)primitives(l,2),(unsigned int)primitives(l,1),rt,gt,bt);
+ } break;
+ }
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save volumetric image as a video, using the OpenCV library.
+ /**
+ \param filename Filename to write data to.
+ \param fps Number of frames per second.
+ \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs).
+ \param keep_open Tells if the video writer associated to the specified filename
+ must be kept open or not (to allow frames to be added in the same file afterwards).
+ **/
+ const CImg<T>& save_video(const char *const filename, const unsigned int fps=25,
+ const char *codec=0, const bool keep_open=false) const {
+ if (is_empty()) { CImgList<T>().save_video(filename,fps,codec,keep_open); return *this; }
+ CImgList<T> list;
+ get_split('z').move_to(list);
+ list.save_video(filename,fps,codec,keep_open);
+ return *this;
+ }
+
+ //! Save volumetric image as a video, using ffmpeg external binary.
+ /**
+ \param filename Filename, as a C-string.
+ \param fps Video framerate.
+ \param codec Video codec, as a C-string.
+ \param bitrate Video bitrate.
+ \note
+ - Each slice of the instance image is considered to be a single frame of the output video file.
+ - This method uses \c ffmpeg, an external executable binary provided by
+ <a href="http://www.ffmpeg.org">FFmpeg</a>.
+ It must be installed for the method to succeed.
+ **/
+ const CImg<T>& save_ffmpeg_external(const char *const filename, const unsigned int fps=25,
+ const char *const codec=0, const unsigned int bitrate=2048) const {
+ if (!filename)
+ throw CImgArgumentException(_cimg_instance
+ "save_ffmpeg_external(): Specified filename is (null).",
+ cimg_instance);
+ if (is_empty()) { cimg::fempty(0,filename); return *this; }
+
+ CImgList<T> list;
+ get_split('z').move_to(list);
+ list.save_ffmpeg_external(filename,fps,codec,bitrate);
+ return *this;
+ }
+
+ //! Save image using gzip external binary.
+ /**
+ \param filename Filename, as a C-string.
+ \note This method uses \c gzip, an external executable binary provided by
+ <a href="//http://www.gzip.org">gzip</a>.
+ It must be installed for the method to succeed.
+ **/
+ 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()) { cimg::fempty(0,filename); return *this; }
+
+ CImg<charT> command(1024), filename_tmp(256), body(256);
+ 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(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ } else {
+ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ }
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ save(filename_tmp);
+ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"",
+ cimg::gzip_path(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data(),
+ CImg<charT>::string(filename)._system_strescape().data());
+ 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(filename_tmp);
+ return *this;
+ }
+
+ //! Save image using GraphicsMagick's external binary.
+ /**
+ \param filename Filename, as a C-string.
+ \param quality Image quality (expressed in percent), when the file format supports it.
+ \note This method uses \c gm, an external executable binary provided by
+ <a href="http://www.graphicsmagick.org">GraphicsMagick</a>.
+ It must be installed for the method to succeed.
+ **/
+ 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()) { cimg::fempty(0,filename); return *this; }
+ if (_depth>1)
+ cimg::warn(_cimg_instance
+ "save_other(): File '%s', saving a volumetric image with an external call to "
+ "GraphicsMagick only writes the first image slice.",
+ cimg_instance,filename);
+
+#ifdef cimg_use_png
+#define _cimg_sge_ext1 "png"
+#define _cimg_sge_ext2 "png"
+#else
+#define _cimg_sge_ext1 "pgm"
+#define _cimg_sge_ext2 "ppm"
+#endif
+ CImg<charT> command(1024), filename_tmp(256);
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),
+ _spectrum==1?_cimg_sge_ext1:_cimg_sge_ext2);
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+#ifdef cimg_use_png
+ save_png(filename_tmp);
+#else
+ save_pnm(filename_tmp);
+#endif
+ cimg_snprintf(command,command._width,"%s convert -quality %u \"%s\" \"%s\"",
+ cimg::graphicsmagick_path(),quality,
+ CImg<charT>::string(filename_tmp)._system_strescape().data(),
+ CImg<charT>::string(filename)._system_strescape().data());
+ 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(filename_tmp);
+ return *this;
+ }
+
+ //! Save image using ImageMagick's external binary.
+ /**
+ \param filename Filename, as a C-string.
+ \param quality Image quality (expressed in percent), when the file format supports it.
+ \note This method uses \c convert, an external executable binary provided by
+ <a href="http://www.imagemagick.org">ImageMagick</a>.
+ It must be installed for the method to succeed.
+ **/
+ 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()) { cimg::fempty(0,filename); return *this; }
+ if (_depth>1)
+ cimg::warn(_cimg_instance
+ "save_other(): File '%s', saving a volumetric image with an external call to "
+ "ImageMagick only writes the first image slice.",
+ cimg_instance,filename);
+#ifdef cimg_use_png
+#define _cimg_sie_ext1 "png"
+#define _cimg_sie_ext2 "png"
+#else
+#define _cimg_sie_ext1 "pgm"
+#define _cimg_sie_ext2 "ppm"
+#endif
+ CImg<charT> command(1024), filename_tmp(256);
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",cimg::temporary_path(),
+ cimg_file_separator,cimg::filenamerand(),_spectrum==1?_cimg_sie_ext1:_cimg_sie_ext2);
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+#ifdef cimg_use_png
+ save_png(filename_tmp);
+#else
+ save_pnm(filename_tmp);
+#endif
+ cimg_snprintf(command,command._width,"%s -quality %u \"%s\" \"%s\"",
+ cimg::imagemagick_path(),quality,
+ CImg<charT>::string(filename_tmp)._system_strescape().data(),
+ CImg<charT>::string(filename)._system_strescape().data());
+ 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(filename_tmp);
+ return *this;
+ }
+
+ //! Save image as a Dicom file.
+ /**
+ \param filename Filename, as a C-string.
+ \note This method uses \c medcon, an external executable binary provided by
+ <a href="http://xmedcon.sourceforge.net">(X)Medcon</a>.
+ It must be installed for the method to succeed.
+ **/
+ 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()) { cimg::fempty(0,filename); return *this; }
+
+ CImg<charT> command(1024), filename_tmp(256), body(256);
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s.hdr",cimg::filenamerand());
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ save_analyze(filename_tmp);
+ cimg_snprintf(command,command._width,"%s -w -c dicom -o \"%s\" -f \"%s\"",
+ cimg::medcon_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+ cimg::system(command);
+ std::remove(filename_tmp);
+ cimg::split_filename(filename_tmp,body);
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s.img",body._data);
+ std::remove(filename_tmp);
+
+ file = std_fopen(filename,"rb");
+ if (!file) {
+ cimg_snprintf(command,command._width,"m000-%s",filename);
+ file = std_fopen(command,"rb");
+ if (!file) {
+ cimg::fclose(cimg::fopen(filename,"r"));
+ throw CImgIOException(_cimg_instance
+ "save_medcon_external(): Failed to save file '%s' with external command 'medcon'.",
+ cimg_instance,
+ filename);
+ }
+ }
+ cimg::fclose(file);
+ std::rename(command,filename);
+ return *this;
+ }
+
+ // Save image for non natively supported formats.
+ /**
+ \param filename Filename, as a C-string.
+ \param quality Image quality (expressed in percent), when the file format supports it.
+ \note
+ - The filename extension tells about the desired file format.
+ - This method tries to save the instance image as a file, using external tools from
+ <a href="http://www.imagemagick.org">ImageMagick</a> or
+ <a href="http://www.graphicsmagick.org">GraphicsMagick</a>.
+ At least one of these tool must be installed for the method to succeed.
+ - It is recommended to use the generic method save(const char*, int) const instead,
+ as it can handle some file formats natively.
+ **/
+ 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()) { cimg::fempty(0,filename); return *this; }
+ if (_depth>1)
+ cimg::warn(_cimg_instance
+ "save_other(): File '%s', saving a volumetric image with an external call to "
+ "ImageMagick or GraphicsMagick only writes the first image slice.",
+ 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;
+ }
+
+ //! Serialize a CImg<T> instance into a raw CImg<unsigned char> buffer.
+ /**
+ \param is_compressed tells if zlib compression must be used for serialization
+ (this requires 'cimg_use_zlib' been enabled).
+ **/
+ CImg<ucharT> get_serialize(const bool is_compressed=false) const {
+ return CImgList<T>(*this,true).get_serialize(is_compressed);
+ }
+
+ // [internal] Return a 40x38 color logo of a 'danger' item.
+ static CImg<T> _logo40x38() {
+ CImg<T> res(40,38,1,3);
+ 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 (ulongT off = 0; off<(ulongT)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; }
+ }
+ return res;
+ }
+
+ //@}
+ };
+
+ /*
+ #-----------------------------------------
+ #
+ #
+ #
+ # Definition of the CImgList<T> structure
+ #
+ #
+ #
+ #------------------------------------------
+ */
+ //! Represent a list of images CImg<T>.
+ template<typename T>
+ struct CImgList {
+ unsigned int _width, _allocated_width;
+ CImg<T> *_data;
+
+ //! Simple iterator type, to loop through each image of a list.
+ /**
+ \note
+ - The \c CImgList<T>::iterator type is defined as a <tt>CImg<T>*</tt>.
+ - You may use it like this:
+ \code
+ CImgList<> list; // Assuming this image list is not empty.
+ for (CImgList<>::iterator it = list.begin(); it<list.end(); ++it) (*it).mirror('x');
+ \endcode
+ - Using the loop macro \c cimglist_for is another (more concise) alternative:
+ \code
+ cimglist_for(list,l) list[l].mirror('x');
+ \endcode
+ **/
+ typedef CImg<T>* iterator;
+
+ //! Simple const iterator type, to loop through each image of a \c const list instance.
+ /**
+ \note
+ - The \c CImgList<T>::const_iterator type is defined to be a <tt>const CImg<T>*</tt>.
+ - Similar to CImgList<T>::iterator, but for constant list instances.
+ **/
+ typedef const CImg<T>* const_iterator;
+
+ //! Pixel value type.
+ /**
+ Refer to the pixels value type of the images in the list.
+ \note
+ - The \c CImgList<T>::value_type type of a \c CImgList<T> is defined to be a \c T.
+ It is then similar to CImg<T>::value_type.
+ - \c CImgList<T>::value_type is actually not used in %CImg methods. It has been mainly defined for
+ compatibility with STL naming conventions.
+ **/
+ typedef T value_type;
+
+ // Define common types related to template type T.
+ typedef typename cimg::superset<T,bool>::type Tbool;
+ typedef typename cimg::superset<T,unsigned char>::type Tuchar;
+ typedef typename cimg::superset<T,char>::type Tchar;
+ typedef typename cimg::superset<T,unsigned short>::type Tushort;
+ typedef typename cimg::superset<T,short>::type Tshort;
+ typedef typename cimg::superset<T,unsigned int>::type Tuint;
+ typedef typename cimg::superset<T,int>::type Tint;
+ typedef typename cimg::superset<T,cimg_ulong>::type Tulong;
+ typedef typename cimg::superset<T,cimg_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,cimg_ulong>::type ulongT;
+ typedef typename cimg::last<T,cimg_long>::type longT;
+ typedef typename cimg::last<T,cimg_uint64>::type uint64T;
+ typedef typename cimg::last<T,cimg_int64>::type int64T;
+ typedef typename cimg::last<T,float>::type floatT;
+ typedef typename cimg::last<T,double>::type doubleT;
+
+ //@}
+ //---------------------------
+ //
+ //! \name Plugins
+ //@{
+ //---------------------------
+#ifdef cimglist_plugin
+#include cimglist_plugin
+#endif
+#ifdef cimglist_plugin1
+#include cimglist_plugin1
+#endif
+#ifdef cimglist_plugin2
+#include cimglist_plugin2
+#endif
+#ifdef cimglist_plugin3
+#include cimglist_plugin3
+#endif
+#ifdef cimglist_plugin4
+#include cimglist_plugin4
+#endif
+#ifdef cimglist_plugin5
+#include cimglist_plugin5
+#endif
+#ifdef cimglist_plugin6
+#include cimglist_plugin6
+#endif
+#ifdef cimglist_plugin7
+#include cimglist_plugin7
+#endif
+#ifdef cimglist_plugin8
+#include cimglist_plugin8
+#endif
+
+ //@}
+ //--------------------------------------------------------
+ //
+ //! \name Constructors / Destructor / Instance Management
+ //@{
+ //--------------------------------------------------------
+
+ //! Destructor.
+ /**
+ Destroy current list instance.
+ \note
+ - Any allocated buffer is deallocated.
+ - Destroying an empty list does nothing actually.
+ **/
+ ~CImgList() {
+ delete[] _data;
+ }
+
+ //! Default constructor.
+ /**
+ Construct a new empty list instance.
+ \note
+ - An empty list has no pixel data and its dimension width() is set to \c 0, as well as its
+ image buffer pointer data().
+ - An empty list may be reassigned afterwards, with the family of the assign() methods.
+ In all cases, the type of pixels stays \c T.
+ **/
+ CImgList():
+ _width(0),_allocated_width(0),_data(0) {}
+
+ //! Construct list containing empty images.
+ /**
+ \param n Number of empty images.
+ \note Useful when you know by advance the number of images you want to manage, as
+ it will allocate the right amount of memory for the list, without needs for reallocation
+ (that may occur when starting from an empty list and inserting several images in it).
+ **/
+ explicit CImgList(const unsigned int n):_width(n) {
+ if (n) _data = new CImg<T>[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))];
+ else { _allocated_width = 0; _data = 0; }
+ }
+
+ //! Construct list containing images of specified size.
+ /**
+ \param n Number of images.
+ \param width Width of images.
+ \param height Height of images.
+ \param depth Depth of images.
+ \param spectrum Number of channels of images.
+ \note Pixel values are not initialized and may probably contain garbage.
+ **/
+ 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 list containing images of specified size, and initialize pixel values.
+ /**
+ \param n Number of images.
+ \param width Width of images.
+ \param height Height of images.
+ \param depth Depth of images.
+ \param spectrum Number of channels of images.
+ \param val Initialization value for images pixels.
+ **/
+ 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 list containing images of specified size, and initialize pixel values from a sequence of integers.
+ /**
+ \param n Number of images.
+ \param width Width of images.
+ \param height Height of images.
+ \param depth Depth of images.
+ \param spectrum Number of channels of images.
+ \param val0 First value of the initializing integers sequence.
+ \param val1 Second value of the initializing integers sequence.
+ \warning You must specify at least <tt>width*height*depth*spectrum</tt> values in your argument list,
+ or you will probably segfault.
+ **/
+ 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 ulongT siz = (ulongT)width*height*depth*spectrum, nsiz = siz*n; \
+ T *ptrd = _data->_data; \
+ va_list ap; \
+ va_start(ap,val1); \
+ for (ulongT 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 list containing images of specified size, and initialize pixel values from a sequence of doubles.
+ /**
+ \param n Number of images.
+ \param width Width of images.
+ \param height Height of images.
+ \param depth Depth of images.
+ \param spectrum Number of channels of images.
+ \param val0 First value of the initializing doubles sequence.
+ \param val1 Second value of the initializing doubles sequence.
+ \warning You must specify at least <tt>width*height*depth*spectrum</tt> values in your argument list,
+ or you will probably segfault.
+ **/
+ 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 list containing copies of an input image.
+ /**
+ \param n Number of images.
+ \param img Input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of \c img.
+ **/
+ template<typename t>
+ CImgList(const unsigned int n, const CImg<t>& img, const bool is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(n);
+ cimglist_apply(*this,assign)(img,is_shared);
+ }
+
+ //! Construct list from one image.
+ /**
+ \param img Input image to copy in the constructed list.
+ \param is_shared Tells if the element of the list is a shared or non-shared copy of \c img.
+ **/
+ template<typename t>
+ explicit CImgList(const CImg<t>& img, const bool is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(1);
+ _data[0].assign(img,is_shared);
+ }
+
+ //! Construct list from two images.
+ /**
+ \param img1 First input image to copy in the constructed list.
+ \param img2 Second input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images.
+ **/
+ template<typename t1, typename t2>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const bool is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(2);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared);
+ }
+
+ //! Construct list from three images.
+ /**
+ \param img1 First input image to copy in the constructed list.
+ \param img2 Second input image to copy in the constructed list.
+ \param img3 Third input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images.
+ **/
+ template<typename t1, typename t2, typename t3>
+ CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(3);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ }
+
+ //! Construct list from four images.
+ /**
+ \param img1 First input image to copy in the constructed list.
+ \param img2 Second input image to copy in the constructed list.
+ \param img3 Third input image to copy in the constructed list.
+ \param img4 Fourth input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input 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 is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(4);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared);
+ }
+
+ //! Construct list from five images.
+ /**
+ \param img1 First input image to copy in the constructed list.
+ \param img2 Second input image to copy in the constructed list.
+ \param img3 Third input image to copy in the constructed list.
+ \param img4 Fourth input image to copy in the constructed list.
+ \param img5 Fifth input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input 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 is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(5);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared);
+ }
+
+ //! Construct list from six images.
+ /**
+ \param img1 First input image to copy in the constructed list.
+ \param img2 Second input image to copy in the constructed list.
+ \param img3 Third input image to copy in the constructed list.
+ \param img4 Fourth input image to copy in the constructed list.
+ \param img5 Fifth input image to copy in the constructed list.
+ \param img6 Sixth input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input 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 is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(6);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+ }
+
+ //! Construct list from seven images.
+ /**
+ \param img1 First input image to copy in the constructed list.
+ \param img2 Second input image to copy in the constructed list.
+ \param img3 Third input image to copy in the constructed list.
+ \param img4 Fourth input image to copy in the constructed list.
+ \param img5 Fifth input image to copy in the constructed list.
+ \param img6 Sixth input image to copy in the constructed list.
+ \param img7 Seventh input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input 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 is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(7);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+ _data[6].assign(img7,is_shared);
+ }
+
+ //! Construct list from eight images.
+ /**
+ \param img1 First input image to copy in the constructed list.
+ \param img2 Second input image to copy in the constructed list.
+ \param img3 Third input image to copy in the constructed list.
+ \param img4 Fourth input image to copy in the constructed list.
+ \param img5 Fifth input image to copy in the constructed list.
+ \param img6 Sixth input image to copy in the constructed list.
+ \param img7 Seventh input image to copy in the constructed list.
+ \param img8 Eighth input image to copy in the constructed list.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input 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 is_shared=false):
+ _width(0),_allocated_width(0),_data(0) {
+ assign(8);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+ _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared);
+ }
+
+ //! Construct list copy.
+ /**
+ \param list Input list to copy.
+ \note The shared state of each element of the constructed list is kept the same as in \c list.
+ **/
+ 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);
+ }
+
+ //! Construct list copy \specialization.
+ CImgList(const CImgList<T>& list):_width(0),_allocated_width(0),_data(0) {
+ assign(list._width);
+ cimglist_for(*this,l) _data[l].assign(list[l],list[l]._is_shared);
+ }
+
+ //! Construct list copy, and force the shared state of the list elements.
+ /**
+ \param list Input list to copy.
+ \param is_shared Tells if the elements of the list are shared or non-shared copies of input images.
+ **/
+ template<typename t>
+ CImgList(const CImgList<t>& list, const bool is_shared):_width(0),_allocated_width(0),_data(0) {
+ assign(list._width);
+ cimglist_for(*this,l) _data[l].assign(list[l],is_shared);
+ }
+
+ //! Construct list by reading the content of a file.
+ /**
+ \param filename Filename, as a C-string.
+ **/
+ explicit CImgList(const char *const filename):_width(0),_allocated_width(0),_data(0) {
+ assign(filename);
+ }
+
+ //! Construct list from the content of a display window.
+ /**
+ \param disp Display window to get content from.
+ \note Constructed list contains a single image only.
+ **/
+ explicit CImgList(const CImgDisplay& disp):_width(0),_allocated_width(0),_data(0) {
+ assign(disp);
+ }
+
+ //! Return a list with elements being shared copies of images in the list instance.
+ /**
+ \note <tt>list2 = list1.get_shared()</tt> is equivalent to <tt>list2.assign(list1,true)</tt>.
+ **/
+ CImgList<T> get_shared() {
+ CImgList<T> res(_width);
+ cimglist_for(*this,l) res[l].assign(_data[l],true);
+ return res;
+ }
+
+ //! Return a list with elements being shared copies of images in the list instance \const.
+ const CImgList<T> get_shared() const {
+ CImgList<T> res(_width);
+ cimglist_for(*this,l) res[l].assign(_data[l],true);
+ return res;
+ }
+
+ //! Destructor \inplace.
+ /**
+ \see CImgList().
+ **/
+ CImgList<T>& assign() {
+ delete[] _data;
+ _width = _allocated_width = 0;
+ _data = 0;
+ return *this;
+ }
+
+ //! Destructor \inplace.
+ /**
+ Equivalent to assign().
+ \note Only here for compatibility with STL naming conventions.
+ **/
+ CImgList<T>& clear() {
+ return assign();
+ }
+
+ //! Construct list containing empty images \inplace.
+ /**
+ \see CImgList(unsigned int).
+ **/
+ CImgList<T>& assign(const unsigned int n) {
+ if (!n) return assign();
+ if (_allocated_width<n || _allocated_width>(n<<2)) {
+ delete[] _data;
+ _data = new CImg<T>[_allocated_width = std::max(16U,(unsigned int)cimg::nearest_pow2(n))];
+ }
+ _width = n;
+ return *this;
+ }
+
+ //! Construct list containing images of specified size \inplace.
+ /**
+ \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int).
+ **/
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height=1,
+ const unsigned int depth=1, const unsigned int spectrum=1) {
+ assign(n);
+ cimglist_apply(*this,assign)(width,height,depth,spectrum);
+ return *this;
+ }
+
+ //! Construct list containing images of specified size, and initialize pixel values \inplace.
+ /**
+ \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const T).
+ **/
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int spectrum, const T& val) {
+ assign(n);
+ cimglist_apply(*this,assign)(width,height,depth,spectrum,val);
+ return *this;
+ }
+
+ //! Construct list with images of specified size, and initialize pixel values from a sequence of integers \inplace.
+ /**
+ \see CImgList(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, const int, const int, ...).
+ **/
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int spectrum, const int val0, const int val1, ...) {
+ _CImgList_stdarg(int);
+ return *this;
+ }
+
+ //! Construct list with images of specified size, and initialize pixel values from a sequence of doubles \inplace.
+ /**
+ \see CImgList(unsigned int,unsigned int,unsigned int,unsigned int,unsigned int,const double,const double,...).
+ **/
+ CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
+ const unsigned int depth, const unsigned int spectrum,
+ const double val0, const double val1, ...) {
+ _CImgList_stdarg(double);
+ return *this;
+ }
+
+ //! Construct list containing copies of an input image \inplace.
+ /**
+ \see CImgList(unsigned int, const CImg<t>&, bool).
+ **/
+ template<typename t>
+ CImgList<T>& assign(const unsigned int n, const CImg<t>& img, const bool is_shared=false) {
+ assign(n);
+ cimglist_apply(*this,assign)(img,is_shared);
+ return *this;
+ }
+
+ //! Construct list from one image \inplace.
+ /**
+ \see CImgList(const CImg<t>&, bool).
+ **/
+ template<typename t>
+ CImgList<T>& assign(const CImg<t>& img, const bool is_shared=false) {
+ assign(1);
+ _data[0].assign(img,is_shared);
+ return *this;
+ }
+
+ //! Construct list from two images \inplace.
+ /**
+ \see CImgList(const CImg<t>&, const CImg<t>&, bool).
+ **/
+ template<typename t1, typename t2>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const bool is_shared=false) {
+ assign(2);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared);
+ return *this;
+ }
+
+ //! Construct list from three images \inplace.
+ /**
+ \see CImgList(const CImg<t>&, const CImg<t>&, const CImg<t>&, bool).
+ **/
+ template<typename t1, typename t2, typename t3>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool is_shared=false) {
+ assign(3);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ return *this;
+ }
+
+ //! Construct list from four images \inplace.
+ /**
+ \see CImgList(const CImg<t>&, const CImg<t>&, const CImg<t>&, const CImg<t>&, bool).
+ **/
+ template<typename t1, typename t2, typename t3, typename t4>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const bool is_shared=false) {
+ assign(4);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared);
+ return *this;
+ }
+
+ //! Construct list from five images \inplace.
+ /**
+ \see CImgList(const CImg<t>&, const CImg<t>&, const CImg<t>&, const CImg<t>&, const CImg<t>&, bool).
+ **/
+ template<typename t1, typename t2, typename t3, typename t4, typename t5>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const bool is_shared=false) {
+ assign(5);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared);
+ return *this;
+ }
+
+ //! Construct list from six images \inplace.
+ /**
+ \see CImgList(const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&, bool).
+ **/
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const bool is_shared=false) {
+ assign(6);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+ return *this;
+ }
+
+ //! Construct list from seven images \inplace.
+ /**
+ \see CImgList(const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,
+ const CImg<t>&, bool).
+ **/
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool is_shared=false) {
+ assign(7);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+ _data[6].assign(img7,is_shared);
+ return *this;
+ }
+
+ //! Construct list from eight images \inplace.
+ /**
+ \see CImgList(const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,const CImg<t>&,
+ const CImg<t>&, const CImg<t>&, bool).
+ **/
+ template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
+ CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
+ const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8,
+ const bool is_shared=false) {
+ assign(8);
+ _data[0].assign(img1,is_shared); _data[1].assign(img2,is_shared); _data[2].assign(img3,is_shared);
+ _data[3].assign(img4,is_shared); _data[4].assign(img5,is_shared); _data[5].assign(img6,is_shared);
+ _data[6].assign(img7,is_shared); _data[7].assign(img8,is_shared);
+ return *this;
+ }
+
+ //! Construct list as a copy of an existing list and force the shared state of the list elements \inplace.
+ /**
+ \see CImgList(const CImgList<t>&, bool is_shared).
+ **/
+ template<typename t>
+ CImgList<T>& assign(const CImgList<t>& list, const bool is_shared=false) {
+ cimg::unused(is_shared);
+ assign(list._width);
+ cimglist_for(*this,l) _data[l].assign(list[l],false);
+ return *this;
+ }
+
+ //! Construct list as a copy of an existing list and force shared state of elements \inplace \specialization.
+ CImgList<T>& assign(const CImgList<T>& list, const bool is_shared=false) {
+ if (this==&list) return *this;
+ CImgList<T> res(list._width);
+ cimglist_for(res,l) res[l].assign(list[l],is_shared);
+ return res.move_to(*this);
+ }
+
+ //! Construct list by reading the content of a file \inplace.
+ /**
+ \see CImgList(const char *const).
+ **/
+ CImgList<T>& assign(const char *const filename) {
+ return load(filename);
+ }
+
+ //! Construct list from the content of a display window \inplace.
+ /**
+ \see CImgList(const CImgDisplay&).
+ **/
+ CImgList<T>& assign(const CImgDisplay &disp) {
+ return assign(CImg<T>(disp));
+ }
+
+ //! Transfer the content of the list instance to another list.
+ /**
+ \param list Destination list.
+ \note When returning, the current list instance is empty and the initial content of \c list is destroyed.
+ **/
+ template<typename t>
+ CImgList<t>& move_to(CImgList<t>& list) {
+ list.assign(_width);
+ bool is_one_shared_element = false;
+ cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared;
+ if (is_one_shared_element) cimglist_for(*this,l) list[l].assign(_data[l]);
+ else cimglist_for(*this,l) _data[l].move_to(list[l]);
+ assign();
+ return list;
+ }
+
+ //! Transfer the content of the list instance at a specified position in another list.
+ /**
+ \param list Destination list.
+ \param pos Index of the insertion in the list.
+ \note When returning, the list instance is empty and the initial content of \c list is preserved
+ (only images indexes may be modified).
+ **/
+ template<typename t>
+ CImgList<t>& move_to(CImgList<t>& list, const unsigned int pos) {
+ if (is_empty()) return list;
+ const unsigned int npos = pos>list._width?list._width:pos;
+ list.insert(_width,npos);
+ bool is_one_shared_element = false;
+ cimglist_for(*this,l) is_one_shared_element|=_data[l]._is_shared;
+ if (is_one_shared_element) cimglist_for(*this,l) list[npos + l].assign(_data[l]);
+ else cimglist_for(*this,l) _data[l].move_to(list[npos + l]);
+ assign();
+ return list;
+ }
+
+ //! Swap all fields between two list instances.
+ /**
+ \param list List to swap fields with.
+ \note Can be used to exchange the content of two lists in a fast way.
+ **/
+ CImgList<T>& swap(CImgList<T>& list) {
+ cimg::swap(_width,list._width,_allocated_width,list._allocated_width);
+ cimg::swap(_data,list._data);
+ return list;
+ }
+
+ //! Return a reference to an empty list.
+ /**
+ \note Can be used to define default values in a function taking a CImgList<T> as an argument.
+ \code
+ void f(const CImgList<char>& list=CImgList<char>::empty());
+ \endcode
+ **/
+ static CImgList<T>& empty() {
+ static CImgList<T> _empty;
+ return _empty.assign();
+ }
+
+ //! Return a reference to an empty list \const.
+ static const CImgList<T>& const_empty() {
+ static const CImgList<T> _empty;
+ return _empty;
+ }
+
+ //@}
+ //------------------------------------------
+ //
+ //! \name Overloaded Operators
+ //@{
+ //------------------------------------------
+
+ //! Return a reference to one image element of the list.
+ /**
+ \param pos Indice of the image element.
+ **/
+ CImg<T>& operator()(const unsigned int pos) {
+#if cimg_verbosity>=3
+ if (pos>=_width) {
+ cimg::warn(_cimglist_instance
+ "operator(): Invalid image request, at position [%u].",
+ cimglist_instance,
+ pos);
+ return *_data;
+ }
+#endif
+ return _data[pos];
+ }
+
+ //! Return a reference to one image of the list.
+ /**
+ \param pos Indice of the image element.
+ **/
+ const CImg<T>& operator()(const unsigned int pos) const {
+ return const_cast<CImgList<T>*>(this)->operator()(pos);
+ }
+
+ //! Return a reference to one pixel value of one image of the list.
+ /**
+ \param pos Indice of the image element.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note <tt>list(n,x,y,z,c)</tt> is equivalent to <tt>list[n](x,y,z,c)</tt>.
+ **/
+ T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
+ const unsigned int z=0, const unsigned int c=0) {
+ return (*this)[pos](x,y,z,c);
+ }
+
+ //! Return a reference to one pixel value of one image of the list \const.
+ const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
+ const unsigned int z=0, const unsigned int c=0) const {
+ return (*this)[pos](x,y,z,c);
+ }
+
+ //! Return pointer to the first image of the list.
+ /**
+ \note Images in a list are stored as a buffer of \c CImg<T>.
+ **/
+ operator CImg<T>*() {
+ return _data;
+ }
+
+ //! Return pointer to the first image of the list \const.
+ operator const CImg<T>*() const {
+ return _data;
+ }
+
+ //! Construct list from one image \inplace.
+ /**
+ \param img Input image to copy in the constructed list.
+ \note <tt>list = img;</tt> is equivalent to <tt>list.assign(img);</tt>.
+ **/
+ template<typename t>
+ CImgList<T>& operator=(const CImg<t>& img) {
+ return assign(img);
+ }
+
+ //! Construct list from another list.
+ /**
+ \param list Input list to copy.
+ \note <tt>list1 = list2</tt> is equivalent to <tt>list1.assign(list2);</tt>.
+ **/
+ template<typename t>
+ CImgList<T>& operator=(const CImgList<t>& list) {
+ return assign(list);
+ }
+
+ //! Construct list from another list \specialization.
+ CImgList<T>& operator=(const CImgList<T>& list) {
+ return assign(list);
+ }
+
+ //! Construct list by reading the content of a file \inplace.
+ /**
+ \see CImgList(const char *const).
+ **/
+ CImgList<T>& operator=(const char *const filename) {
+ return assign(filename);
+ }
+
+ //! Construct list from the content of a display window \inplace.
+ /**
+ \see CImgList(const CImgDisplay&).
+ **/
+ CImgList<T>& operator=(const CImgDisplay& disp) {
+ return assign(disp);
+ }
+
+ //! Return a non-shared copy of a list.
+ /**
+ \note <tt>+list</tt> is equivalent to <tt>CImgList<T>(list,false)</tt>.
+ It forces the copy to have non-shared elements.
+ **/
+ CImgList<T> operator+() const {
+ return CImgList<T>(*this,false);
+ }
+
+ //! Return a copy of the list instance, where image \c img has been inserted at the end.
+ /**
+ \param img Image inserted at the end of the instance copy.
+ \note Define a convenient way to create temporary lists of images, as in the following code:
+ \code
+ (img1,img2,img3,img4).display("My four images");
+ \endcode
+ **/
+ template<typename t>
+ CImgList<T>& operator,(const CImg<t>& img) {
+ return insert(img);
+ }
+
+ //! Return a copy of the list instance, where image \c img has been inserted at the end \const.
+ template<typename t>
+ CImgList<T> operator,(const CImg<t>& img) const {
+ return (+*this).insert(img);
+ }
+
+ //! Return a copy of the list instance, where all elements of input list \c list have been inserted at the end.
+ /**
+ \param list List inserted at the end of the instance copy.
+ **/
+ template<typename t>
+ CImgList<T>& operator,(const CImgList<t>& list) {
+ return insert(list);
+ }
+
+ //! Return a copy of the list instance, where all elements of input \c list have been inserted at the end \const.
+ template<typename t>
+ CImgList<T>& operator,(const CImgList<t>& list) const {
+ return (+*this).insert(list);
+ }
+
+ //! Return image corresponding to the appending of all images of the instance list along specified axis.
+ /**
+ \param axis Appending axis. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \note <tt>list>'x'</tt> is equivalent to <tt>list.get_append('x')</tt>.
+ **/
+ CImg<T> operator>(const char axis) const {
+ return get_append(axis,0);
+ }
+
+ //! Return list corresponding to the splitting of all images of the instance list along specified axis.
+ /**
+ \param axis Axis used for image splitting.
+ \note <tt>list<'x'</tt> is equivalent to <tt>list.get_split('x')</tt>.
+ **/
+ CImgList<T> operator<(const char axis) const {
+ return get_split(axis);
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Instance Characteristics
+ //@{
+ //-------------------------------------
+
+ //! Return the type of image pixel values as a C string.
+ /**
+ Return a \c char* string containing the usual type name of the image pixel values
+ (i.e. a stringified version of the template parameter \c T).
+ \note
+ - The returned string may contain spaces (as in \c "unsigned char").
+ - If the pixel type \c T does not correspond to a registered type, the string <tt>"unknown"</tt> is returned.
+ **/
+ static const char* pixel_type() {
+ return cimg::type<T>::string();
+ }
+
+ //! Return the size of the list, i.e. the number of images contained in it.
+ /**
+ \note Similar to size() but returns result as a (signed) integer.
+ **/
+ int width() const {
+ return (int)_width;
+ }
+
+ //! Return the size of the list, i.e. the number of images contained in it.
+ /**
+ \note Similar to width() but returns result as an unsigned integer.
+ **/
+ unsigned int size() const {
+ return _width;
+ }
+
+ //! Return pointer to the first image of the list.
+ /**
+ \note Images in a list are stored as a buffer of \c CImg<T>.
+ **/
+ CImg<T> *data() {
+ return _data;
+ }
+
+ //! Return pointer to the first image of the list \const.
+ const CImg<T> *data() const {
+ return _data;
+ }
+
+ //! Return pointer to the pos-th image of the list.
+ /**
+ \param pos Indice of the image element to access.
+ \note <tt>list.data(n);</tt> is equivalent to <tt>list.data + n;</tt>.
+ **/
+#if cimg_verbosity>=3
+ CImg<T> *data(const unsigned int pos) {
+ if (pos>=size())
+ cimg::warn(_cimglist_instance
+ "data(): Invalid pointer request, at position [%u].",
+ cimglist_instance,
+ pos);
+ return _data + pos;
+ }
+
+ const CImg<T> *data(const unsigned int l) const {
+ return const_cast<CImgList<T>*>(this)->data(l);
+ }
+#else
+ CImg<T> *data(const unsigned int l) {
+ return _data + l;
+ }
+
+ //! Return pointer to the pos-th image of the list \const.
+ const CImg<T> *data(const unsigned int l) const {
+ return _data + l;
+ }
+#endif
+
+ //! Return iterator to the first image of the list.
+ /**
+ **/
+ iterator begin() {
+ return _data;
+ }
+
+ //! Return iterator to the first image of the list \const.
+ const_iterator begin() const {
+ return _data;
+ }
+
+ //! Return iterator to one position after the last image of the list.
+ /**
+ **/
+ iterator end() {
+ return _data + _width;
+ }
+
+ //! Return iterator to one position after the last image of the list \const.
+ const_iterator end() const {
+ return _data + _width;
+ }
+
+ //! Return reference to the first image of the list.
+ /**
+ **/
+ CImg<T>& front() {
+ return *_data;
+ }
+
+ //! Return reference to the first image of the list \const.
+ const CImg<T>& front() const {
+ return *_data;
+ }
+
+ //! Return a reference to the last image of the list.
+ /**
+ **/
+ const CImg<T>& back() const {
+ return *(_data + _width - 1);
+ }
+
+ //! Return a reference to the last image of the list \const.
+ CImg<T>& back() {
+ return *(_data + _width - 1);
+ }
+
+ //! Return pos-th image of the list.
+ /**
+ \param pos Indice of the image element to access.
+ **/
+ CImg<T>& at(const int pos) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "at(): Empty instance.",
+ cimglist_instance);
+
+ return _data[cimg::cut(pos,0,width() - 1)];
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions.
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c offset is outside image bounds.
+ \note <tt>list.atNXYZC(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZC(x,y,z,c);</tt>.
+ **/
+ T& atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) {
+ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXYZC(x,y,z,c,out_value);
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions \const.
+ T atNXYZC(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZC(x,y,z,c,out_value);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions.
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note <tt>list.atNXYZC(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZC(x,y,z,c);</tt>.
+ **/
+ T& atNXYZC(const int pos, const int x, const int y, const int z, const int c) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNXYZC(): Empty instance.",
+ cimglist_instance);
+
+ return _atNXYZC(pos,x,y,z,c);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions \const.
+ T atNXYZC(const int pos, const int x, const int y, const int z, const int c) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNXYZC(): Empty instance.",
+ cimglist_instance);
+
+ return _atNXYZC(pos,x,y,z,c);
+ }
+
+ T& _atNXYZC(const int pos, const int x, const int y, const int z, const int c) {
+ return _data[cimg::cut(pos,0,width() - 1)].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[cimg::cut(pos,0,width() - 1)].atXYZC(x,y,z,c);
+ }
+
+ //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c offset is outside image bounds.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) {
+ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXYZ(x,y,z,c,out_value);
+ }
+
+ //! Access pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y,\c z) \const.
+ T atNXYZ(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXYZ(x,y,z,c,out_value);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNXYZ(): Empty instance.",
+ cimglist_instance);
+
+ return _atNXYZ(pos,x,y,z,c);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions for the 4 coordinates (\c pos, \c x,\c y,\c z) \const.
+ T atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNXYZ(): Empty instance.",
+ cimglist_instance);
+
+ return _atNXYZ(pos,x,y,z,c);
+ }
+
+ T& _atNXYZ(const int pos, const int x, const int y, const int z, const int c=0) {
+ return _data[cimg::cut(pos,0,width() - 1)].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[cimg::cut(pos,0,width() - 1)].atXYZ(x,y,z,c);
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c offset is outside image bounds.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) {
+ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atXY(x,y,z,c,out_value);
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const.
+ T atNXY(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atXY(x,y,z,c,out_value);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNXY(): Empty instance.",
+ cimglist_instance);
+
+ return _atNXY(pos,x,y,z,c);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions for the 3 coordinates (\c pos, \c x,\c y) \const.
+ T atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNXY(): Empty instance.",
+ cimglist_instance);
+
+ return _atNXY(pos,x,y,z,c);
+ }
+
+ T& _atNXY(const int pos, const int x, const int y, const int z=0, const int c=0) {
+ return _data[cimg::cut(pos,0,width() - 1)].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[cimg::cut(pos,0,width() - 1)].atXY(x,y,z,c);
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c offset is outside image bounds.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) {
+ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):_data[pos].atX(x,y,z,c,out_value);
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions for the 2 coordinates (\c pos,\c x) \const.
+ T atNX(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (pos<0 || pos>=(int)_width)?out_value:_data[pos].atX(x,y,z,c,out_value);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNX(): Empty instance.",
+ cimglist_instance);
+
+ return _atNX(pos,x,y,z,c);
+ }
+
+ //! Access to pixel value with Neumann boundary conditions for the 2 coordinates (\c pos, \c x) \const.
+ T atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atNX(): Empty instance.",
+ cimglist_instance);
+
+ return _atNX(pos,x,y,z,c);
+ }
+
+ T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int c=0) {
+ return _data[cimg::cut(pos,0,width() - 1)].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[cimg::cut(pos,0,width() - 1)].atX(x,y,z,c);
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \param out_value Default value returned if \c offset is outside image bounds.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) {
+ return (pos<0 || pos>=(int)_width)?(cimg::temporary(out_value)=out_value):(*this)(pos,x,y,z,c);
+ }
+
+ //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos) \const.
+ T atN(const int pos, const int x, const int y, const int z, const int c, const T& out_value) const {
+ return (pos<0 || pos>=(int)_width)?out_value:(*this)(pos,x,y,z,c);
+ }
+
+ //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos).
+ /**
+ \param pos Indice of the image element to access.
+ \param x X-coordinate of the pixel value.
+ \param y Y-coordinate of the pixel value.
+ \param z Z-coordinate of the pixel value.
+ \param c C-coordinate of the pixel value.
+ \note <tt>list.atNXYZ(p,x,y,z,c);</tt> is equivalent to <tt>list[p].atXYZ(x,y,z,c);</tt>.
+ **/
+ T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atN(): Empty instance.",
+ cimglist_instance);
+ return _atN(pos,x,y,z,c);
+ }
+
+ //! Return pixel value with Neumann boundary conditions for the coordinate (\c pos) \const.
+ T atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "atN(): Empty instance.",
+ cimglist_instance);
+ return _atN(pos,x,y,z,c);
+ }
+
+ T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int c=0) {
+ return _data[cimg::cut(pos,0,width() - 1)](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[cimg::cut(pos,0,width() - 1)](x,y,z,c);
+ }
+
+ //! Return a C-string containing the values of all images in the instance list.
+ /**
+ \param separator Character separator set between consecutive pixel values.
+ \param max_size Maximum size of the returned string.
+ \note The result is returne as a <tt>CImg<char></tt> image whose pixel buffer contains the desired C-string.
+ **/
+ CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
+ if (is_empty()) return CImg<ucharT>(1,1,1,1,0);
+ CImgList<charT> items;
+ for (unsigned int l = 0; l<_width - 1; ++l) {
+ CImg<charT> item = _data[l].value_string(separator,0);
+ item.back() = separator;
+ item.move_to(items);
+ }
+ _data[_width - 1].value_string(separator,0).move_to(items);
+ CImg<charT> res; (items>'x').move_to(res);
+ if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
+ return res;
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Instance Checking
+ //@{
+ //-------------------------------------
+
+ //! Return \c true if list is empty.
+ /**
+ **/
+ bool is_empty() const {
+ return (!_data || !_width);
+ }
+
+ //! Test if number of image elements is equal to specified value.
+ /**
+ \param size_n Number of image elements to test.
+ **/
+ bool is_sameN(const unsigned int size_n) const {
+ return _width==size_n;
+ }
+
+ //! Test if number of image elements is equal between two images lists.
+ /**
+ \param list Input list to compare with.
+ **/
+ template<typename t>
+ bool is_sameN(const CImgList<t>& list) const {
+ return is_sameN(list._width);
+ }
+
+ // Define useful functions to check list dimensions.
+ // (cannot be documented because macro-generated).
+#define _cimglist_def_is_same1(axis) \
+ bool is_same##axis(const unsigned int val) const { \
+ bool res = true; \
+ for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(val); return res; \
+ } \
+ bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \
+ return is_sameN(n) && is_same##axis(val); \
+ } \
+
+#define _cimglist_def_is_same2(axis1,axis2) \
+ bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \
+ bool res = true; \
+ for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2(val1,val2); return res; \
+ } \
+ bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \
+ return is_sameN(n) && is_same##axis1##axis2(val1,val2); \
+ } \
+
+#define _cimglist_def_is_same3(axis1,axis2,axis3) \
+ bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, \
+ const unsigned int val3) const { \
+ bool res = true; \
+ for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis1##axis2##axis3(val1,val2,val3); \
+ return res; \
+ } \
+ bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, \
+ const unsigned int val2, const unsigned int val3) const { \
+ return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \
+ } \
+
+#define _cimglist_def_is_same(axis) \
+ template<typename t> bool is_same##axis(const CImg<t>& img) const { \
+ bool res = true; for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(img); return res; \
+ } \
+ template<typename t> bool is_same##axis(const CImgList<t>& list) const { \
+ const unsigned int lmin = std::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)
+
+ //! Test if dimensions of each image of the list match specified arguments.
+ /**
+ \param dx Checked image width.
+ \param dy Checked image height.
+ \param dz Checked image depth.
+ \param dc Checked image spectrum.
+ **/
+ 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;
+ }
+
+ //! Test if list dimensions match specified arguments.
+ /**
+ \param n Number of images in the list.
+ \param dx Checked image width.
+ \param dy Checked image height.
+ \param dz Checked image depth.
+ \param dc Checked image spectrum.
+ **/
+ 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);
+ }
+
+ //! Test if list contains one particular pixel location.
+ /**
+ \param n Index of the image whom checked pixel value belong to.
+ \param x X-coordinate of the checked pixel value.
+ \param y Y-coordinate of the checked pixel value.
+ \param z Z-coordinate of the checked pixel value.
+ \param c C-coordinate of the checked pixel value.
+ **/
+ 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();
+ }
+
+ //! Test if list contains image with specified indice.
+ /**
+ \param n Index of the checked image.
+ **/
+ bool containsN(const int n) const {
+ if (is_empty()) return false;
+ return n>=0 && n<(int)_width;
+ }
+
+ //! Test if one image of the list contains the specified referenced value.
+ /**
+ \param pixel Reference to pixel value to test.
+ \param[out] n Index of image containing the pixel value, if test succeeds.
+ \param[out] x X-coordinate of the pixel value, if test succeeds.
+ \param[out] y Y-coordinate of the pixel value, if test succeeds.
+ \param[out] z Z-coordinate of the pixel value, if test succeeds.
+ \param[out] c C-coordinate of the pixel value, if test succeeds.
+ \note If true, set coordinates (n,x,y,z,c).
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& c) const {
+ if (is_empty()) return false;
+ cimglist_for(*this,l) if (_data[l].contains(pixel,x,y,z,c)) { n = (t)l; return true; }
+ return false;
+ }
+
+ //! Test if one of the image list contains the specified referenced value.
+ /**
+ \param pixel Reference to pixel value to test.
+ \param[out] n Index of image containing the pixel value, if test succeeds.
+ \param[out] x X-coordinate of the pixel value, if test succeeds.
+ \param[out] y Y-coordinate of the pixel value, if test succeeds.
+ \param[out] z Z-coordinate of the pixel value, if test succeeds.
+ \note If true, set coordinates (n,x,y,z).
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x, t&y, t& z) const {
+ t c;
+ return contains(pixel,n,x,y,z,c);
+ }
+
+ //! Test if one of the image list contains the specified referenced value.
+ /**
+ \param pixel Reference to pixel value to test.
+ \param[out] n Index of image containing the pixel value, if test succeeds.
+ \param[out] x X-coordinate of the pixel value, if test succeeds.
+ \param[out] y Y-coordinate of the pixel value, if test succeeds.
+ \note If true, set coordinates (n,x,y).
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x, t&y) const {
+ t z, c;
+ return contains(pixel,n,x,y,z,c);
+ }
+
+ //! Test if one of the image list contains the specified referenced value.
+ /**
+ \param pixel Reference to pixel value to test.
+ \param[out] n Index of image containing the pixel value, if test succeeds.
+ \param[out] x X-coordinate of the pixel value, if test succeeds.
+ \note If true, set coordinates (n,x).
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& n, t& x) const {
+ t y, z, c;
+ return contains(pixel,n,x,y,z,c);
+ }
+
+ //! Test if one of the image list contains the specified referenced value.
+ /**
+ \param pixel Reference to pixel value to test.
+ \param[out] n Index of image containing the pixel value, if test succeeds.
+ \note If true, set coordinates (n).
+ **/
+ template<typename t>
+ bool contains(const T& pixel, t& n) const {
+ t x, y, z, c;
+ return contains(pixel,n,x,y,z,c);
+ }
+
+ //! Test if one of the image list contains the specified referenced value.
+ /**
+ \param pixel Reference to pixel value to test.
+ **/
+ bool contains(const T& pixel) const {
+ unsigned int n, x, y, z, c;
+ return contains(pixel,n,x,y,z,c);
+ }
+
+ //! Test if the list contains the image 'img'.
+ /**
+ \param img Reference to image to test.
+ \param[out] n Index of image in the list, if test succeeds.
+ \note If true, returns the position (n) of the image in the list.
+ **/
+ template<typename t>
+ bool contains(const CImg<T>& img, t& n) const {
+ if (is_empty()) return false;
+ const CImg<T> *const ptr = &img;
+ cimglist_for(*this,i) if (_data + i==ptr) { n = (t)i; return true; }
+ return false;
+ }
+
+ //! Test if the list contains the image img.
+ /**
+ \param img Reference to image to test.
+ **/
+ bool contains(const CImg<T>& img) const {
+ unsigned int n;
+ return contains(img,n);
+ }
+
+ //@}
+ //-------------------------------------
+ //
+ //! \name Mathematical Functions
+ //@{
+ //-------------------------------------
+
+ //! Return a reference to the minimum pixel value of the instance list.
+ /**
+ **/
+ T& min() {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "min(): Empty instance.",
+ cimglist_instance);
+ T *ptr_min = _data->_data;
+ T min_value = *ptr_min;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+ }
+ return *ptr_min;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list \const.
+ const T& min() const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "min(): Empty instance.",
+ cimglist_instance);
+ const T *ptr_min = _data->_data;
+ T min_value = *ptr_min;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) if (*ptrs<min_value) min_value = *(ptr_min=ptrs);
+ }
+ return *ptr_min;
+ }
+
+ //! Return a reference to the maximum pixel value of the instance list.
+ /**
+ **/
+ T& max() {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "max(): Empty instance.",
+ cimglist_instance);
+ T *ptr_max = _data->_data;
+ T max_value = *ptr_max;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+ }
+ return *ptr_max;
+ }
+
+ //! Return a reference to the maximum pixel value of the instance list \const.
+ const T& max() const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "max(): Empty instance.",
+ cimglist_instance);
+ const T *ptr_max = _data->_data;
+ T max_value = *ptr_max;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) if (*ptrs>max_value) max_value = *(ptr_max=ptrs);
+ }
+ return *ptr_max;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well.
+ /**
+ \param[out] max_val Value of the maximum value found.
+ **/
+ template<typename t>
+ T& min_max(t& max_val) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "min_max(): Empty instance.",
+ cimglist_instance);
+ T *ptr_min = _data->_data;
+ T min_value = *ptr_min, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) {
+ const T val = *ptrs;
+ if (val<min_value) { min_value = val; ptr_min = ptrs; }
+ if (val>max_value) max_value = val;
+ }
+ }
+ max_val = (t)max_value;
+ return *ptr_min;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list and return the maximum vvalue as well \const.
+ /**
+ \param[out] max_val Value of the maximum value found.
+ **/
+ template<typename t>
+ const T& min_max(t& max_val) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "min_max(): Empty instance.",
+ cimglist_instance);
+ const T *ptr_min = _data->_data;
+ T min_value = *ptr_min, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) {
+ const T val = *ptrs;
+ if (val<min_value) { min_value = val; ptr_min = ptrs; }
+ if (val>max_value) max_value = val;
+ }
+ }
+ max_val = (t)max_value;
+ return *ptr_min;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well.
+ /**
+ \param[out] min_val Value of the minimum value found.
+ **/
+ template<typename t>
+ T& max_min(t& min_val) {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "max_min(): Empty instance.",
+ cimglist_instance);
+ T *ptr_max = _data->_data;
+ T min_value = *ptr_max, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) {
+ const T val = *ptrs;
+ if (val>max_value) { max_value = val; ptr_max = ptrs; }
+ if (val<min_value) min_value = val;
+ }
+ }
+ min_val = (t)min_value;
+ return *ptr_max;
+ }
+
+ //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well \const.
+ template<typename t>
+ const T& max_min(t& min_val) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "max_min(): Empty instance.",
+ cimglist_instance);
+ const T *ptr_max = _data->_data;
+ T min_value = *ptr_max, max_value = min_value;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_for(img,ptrs,T) {
+ const T val = *ptrs;
+ if (val>max_value) { max_value = val; ptr_max = ptrs; }
+ if (val<min_value) min_value = val;
+ }
+ }
+ min_val = (t)min_value;
+ return *ptr_max;
+ }
+
+ //@}
+ //---------------------------
+ //
+ //! \name List Manipulation
+ //@{
+ //---------------------------
+
+ //! Insert a copy of the image \c img into the current image list, at position \c pos.
+ /**
+ \param img Image to insert a copy to the list.
+ \param pos Index of the insertion.
+ \param is_shared Tells if the inserted image is a shared copy of \c img or not.
+ **/
+ template<typename t>
+ CImgList<T>& insert(const CImg<t>& img, const unsigned int pos=~0U, const bool is_shared=false) {
+ const unsigned int npos = pos==~0U?_width:pos;
+ if (npos>_width)
+ throw CImgArgumentException(_cimglist_instance
+ "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) "
+ "at position %u.",
+ cimglist_instance,
+ img._width,img._height,img._depth,img._spectrum,img._data,npos);
+ if (is_shared)
+ throw CImgArgumentException(_cimglist_instance
+ "insert(): Invalid insertion request of specified shared image "
+ "CImg<%s>(%u,%u,%u,%u,%p) at position %u (pixel types are different).",
+ cimglist_instance,
+ img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos);
+
+ CImg<T> *const new_data = (++_width>_allocated_width)?new CImg<T>[_allocated_width?(_allocated_width<<=1):
+ (_allocated_width=16)]:0;
+ if (!_data) { // Insert new element into empty list.
+ _data = new_data;
+ *_data = img;
+ } else {
+ if (new_data) { // Insert with re-allocation.
+ if (npos) std::memcpy(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) // Insert without re-allocation.
+ 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;
+ }
+
+ //! Insert a copy of the image \c img into the current image list, at position \c pos \specialization.
+ CImgList<T>& insert(const CImg<T>& img, const unsigned int pos=~0U, const bool is_shared=false) {
+ const unsigned int npos = pos==~0U?_width:pos;
+ if (npos>_width)
+ throw CImgArgumentException(_cimglist_instance
+ "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) "
+ "at position %u.",
+ cimglist_instance,
+ img._width,img._height,img._depth,img._spectrum,img._data,npos);
+ CImg<T> *const new_data = (++_width>_allocated_width)?new CImg<T>[_allocated_width?(_allocated_width<<=1):
+ (_allocated_width=16)]:0;
+ if (!_data) { // Insert new element into empty list.
+ _data = new_data;
+ if (is_shared && img) {
+ _data->_width = img._width;
+ _data->_height = img._height;
+ _data->_depth = img._depth;
+ _data->_spectrum = img._spectrum;
+ _data->_is_shared = true;
+ _data->_data = img._data;
+ } else *_data = img;
+ }
+ else {
+ if (new_data) { // Insert with re-allocation.
+ if (npos) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos);
+ if (npos!=_width - 1) std::memcpy(new_data + npos + 1,_data + npos,sizeof(CImg<T>)*(_width - 1 - npos));
+ if (is_shared && img) {
+ new_data[npos]._width = img._width;
+ new_data[npos]._height = img._height;
+ new_data[npos]._depth = img._depth;
+ new_data[npos]._spectrum = img._spectrum;
+ new_data[npos]._is_shared = true;
+ new_data[npos]._data = img._data;
+ } else {
+ new_data[npos]._width = new_data[npos]._height = new_data[npos]._depth = new_data[npos]._spectrum = 0;
+ new_data[npos]._data = 0;
+ new_data[npos] = img;
+ }
+ std::memset(_data,0,sizeof(CImg<T>)*(_width - 1));
+ delete[] _data;
+ _data = new_data;
+ } else { // Insert without re-allocation.
+ if (npos!=_width - 1) std::memmove(_data + npos + 1,_data + npos,sizeof(CImg<T>)*(_width - 1 - npos));
+ if (is_shared && img) {
+ _data[npos]._width = img._width;
+ _data[npos]._height = img._height;
+ _data[npos]._depth = img._depth;
+ _data[npos]._spectrum = img._spectrum;
+ _data[npos]._is_shared = true;
+ _data[npos]._data = img._data;
+ } else {
+ _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0;
+ _data[npos]._data = 0;
+ _data[npos] = img;
+ }
+ }
+ }
+ return *this;
+ }
+
+ //! Insert a copy of the image \c img into the current image list, at position \c pos \newinstance.
+ template<typename t>
+ CImgList<T> get_insert(const CImg<t>& img, const unsigned int pos=~0U, const bool is_shared=false) const {
+ return (+*this).insert(img,pos,is_shared);
+ }
+
+ //! Insert n empty images img into the current image list, at position \p pos.
+ /**
+ \param n Number of empty images to insert.
+ \param pos Index of the insertion.
+ **/
+ CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
+ CImg<T> empty;
+ if (!n) return *this;
+ const unsigned int npos = pos==~0U?_width:pos;
+ for (unsigned int i = 0; i<n; ++i) insert(empty,npos+i);
+ return *this;
+ }
+
+ //! Insert n empty images img into the current image list, at position \p pos \newinstance.
+ CImgList<T> get_insert(const unsigned int n, const unsigned int pos=~0U) const {
+ return (+*this).insert(n,pos);
+ }
+
+ //! Insert \c n copies of the image \c img into the current image list, at position \c pos.
+ /**
+ \param n Number of image copies to insert.
+ \param img Image to insert by copy.
+ \param pos Index of the insertion.
+ \param is_shared Tells if inserted images are shared copies of \c img or not.
+ **/
+ template<typename t>
+ CImgList<T>& insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U,
+ const bool is_shared=false) {
+ if (!n) return *this;
+ const unsigned int npos = pos==~0U?_width:pos;
+ insert(img,npos,is_shared);
+ for (unsigned int i = 1; i<n; ++i) insert(_data[npos],npos + i,is_shared);
+ return *this;
+ }
+
+ //! Insert \c n copies of the image \c img into the current image list, at position \c pos \newinstance.
+ template<typename t>
+ CImgList<T> get_insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U,
+ const bool is_shared=false) const {
+ return (+*this).insert(n,img,pos,is_shared);
+ }
+
+ //! Insert a copy of the image list \c list into the current image list, starting from position \c pos.
+ /**
+ \param list Image list to insert.
+ \param pos Index of the insertion.
+ \param is_shared Tells if inserted images are shared copies of images of \c list or not.
+ **/
+ template<typename t>
+ CImgList<T>& insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool is_shared=false) {
+ const unsigned int npos = pos==~0U?_width:pos;
+ if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos + l,is_shared);
+ else insert(CImgList<T>(list),npos,is_shared);
+ return *this;
+ }
+
+ //! Insert a copy of the image list \c list into the current image list, starting from position \c pos \newinstance.
+ template<typename t>
+ CImgList<T> get_insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool is_shared=false) const {
+ return (+*this).insert(list,pos,is_shared);
+ }
+
+ //! Insert n copies of the list \c list at position \c pos of the current list.
+ /**
+ \param n Number of list copies to insert.
+ \param list Image list to insert.
+ \param pos Index of the insertion.
+ \param is_shared Tells if inserted images are shared copies of images of \c list or not.
+ **/
+ template<typename t>
+ CImgList<T>& insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U,
+ const bool is_shared=false) {
+ if (!n) return *this;
+ const unsigned int npos = pos==~0U?_width:pos;
+ for (unsigned int i = 0; i<n; ++i) insert(list,npos,is_shared);
+ return *this;
+ }
+
+ //! Insert n copies of the list \c list at position \c pos of the current list \newinstance.
+ template<typename t>
+ CImgList<T> get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U,
+ const bool is_shared=false) const {
+ return (+*this).insert(n,list,pos,is_shared);
+ }
+
+ //! Remove all images between from indexes.
+ /**
+ \param pos1 Starting index of the removal.
+ \param pos2 Ending index of the removal.
+ **/
+ CImgList<T>& remove(const unsigned int pos1, const unsigned int pos2) {
+ const unsigned int
+ npos1 = pos1<pos2?pos1:pos2,
+ tpos2 = pos1<pos2?pos2:pos1,
+ npos2 = tpos2<_width?tpos2:_width - 1;
+ if (npos1>=_width)
+ throw CImgArgumentException(_cimglist_instance
+ "remove(): Invalid remove request at positions %u->%u.",
+ cimglist_instance,
+ npos1,tpos2);
+ else {
+ if (tpos2>=_width)
+ throw CImgArgumentException(_cimglist_instance
+ "remove(): Invalid remove request at positions %u->%u.",
+ cimglist_instance,
+ npos1,tpos2);
+
+ for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign();
+ const unsigned int nb = 1 + npos2 - npos1;
+ if (!(_width-=nb)) return assign();
+ if (_width>(_allocated_width>>2) || _allocated_width<=16) { // Removing items without reallocation.
+ if (npos1!=_width) std::memmove(_data + npos1,_data + npos2 + 1,sizeof(CImg<T>)*(_width - npos1));
+ std::memset(_data + _width,0,sizeof(CImg<T>)*nb);
+ } else { // Removing items with reallocation.
+ _allocated_width>>=2;
+ while (_allocated_width>16 && _width<(_allocated_width>>1)) _allocated_width>>=1;
+ CImg<T> *const new_data = new CImg<T>[_allocated_width];
+ if (npos1) std::memcpy(new_data,_data,sizeof(CImg<T>)*npos1);
+ if (npos1!=_width) std::memcpy(new_data + npos1,_data + npos2 + 1,sizeof(CImg<T>)*(_width - npos1));
+ if (_width!=_allocated_width) std::memset(new_data + _width,0,sizeof(CImg<T>)*(_allocated_width - _width));
+ std::memset(_data,0,sizeof(CImg<T>)*(_width + nb));
+ delete[] _data;
+ _data = new_data;
+ }
+ }
+ return *this;
+ }
+
+ //! Remove all images between from indexes \newinstance.
+ CImgList<T> get_remove(const unsigned int pos1, const unsigned int pos2) const {
+ return (+*this).remove(pos1,pos2);
+ }
+
+ //! Remove image at index \c pos from the image list.
+ /**
+ \param pos Index of the image to remove.
+ **/
+ CImgList<T>& remove(const unsigned int pos) {
+ return remove(pos,pos);
+ }
+
+ //! Remove image at index \c pos from the image list \newinstance.
+ CImgList<T> get_remove(const unsigned int pos) const {
+ return (+*this).remove(pos);
+ }
+
+ //! Remove last image.
+ /**
+ **/
+ CImgList<T>& remove() {
+ return remove(_width - 1);
+ }
+
+ //! Remove last image \newinstance.
+ CImgList<T> get_remove() const {
+ return (+*this).remove();
+ }
+
+ //! Reverse list order.
+ CImgList<T>& reverse() {
+ for (unsigned int l = 0; l<_width/2; ++l) (*this)[l].swap((*this)[_width - 1 - l]);
+ return *this;
+ }
+
+ //! Reverse list order \newinstance.
+ CImgList<T> get_reverse() const {
+ return (+*this).reverse();
+ }
+
+ //! Return a sublist.
+ /**
+ \param pos0 Starting index of the sublist.
+ \param pos1 Ending index of the sublist.
+ **/
+ CImgList<T>& images(const unsigned int pos0, const unsigned int pos1) {
+ return get_images(pos0,pos1).move_to(*this);
+ }
+
+ //! Return a sublist \newinstance.
+ CImgList<T> get_images(const unsigned int pos0, const unsigned int pos1) const {
+ if (pos0>pos1 || pos1>=_width)
+ throw CImgArgumentException(_cimglist_instance
+ "images(): Specified sub-list indices (%u->%u) are out of bounds.",
+ cimglist_instance,
+ pos0,pos1);
+ CImgList<T> res(pos1 - pos0 + 1);
+ cimglist_for(res,l) res[l].assign(_data[pos0 + l]);
+ return res;
+ }
+
+ //! Return a shared sublist.
+ /**
+ \param pos0 Starting index of the sublist.
+ \param pos1 Ending index of the sublist.
+ **/
+ CImgList<T> get_shared_images(const unsigned int pos0, const unsigned int pos1) {
+ if (pos0>pos1 || pos1>=_width)
+ throw CImgArgumentException(_cimglist_instance
+ "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.",
+ cimglist_instance,
+ pos0,pos1);
+ CImgList<T> res(pos1 - pos0 + 1);
+ cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false);
+ return res;
+ }
+
+ //! Return a shared sublist \newinstance.
+ const CImgList<T> get_shared_images(const unsigned int pos0, const unsigned int pos1) const {
+ if (pos0>pos1 || pos1>=_width)
+ throw CImgArgumentException(_cimglist_instance
+ "get_shared_images(): Specified sub-list indices (%u->%u) are out of bounds.",
+ cimglist_instance,
+ pos0,pos1);
+ CImgList<T> res(pos1 - pos0 + 1);
+ cimglist_for(res,l) res[l].assign(_data[pos0 + l],_data[pos0 + l]?true:false);
+ return res;
+ }
+
+ //! Return a single image which is the appending of all images of the current CImgList instance.
+ /**
+ \param axis Appending axis. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignment.
+ **/
+ CImg<T> get_append(const char axis, const float align=0) const {
+ if (is_empty()) return CImg<T>();
+ if (_width==1) return +((*this)[0]);
+ unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0;
+ CImg<T> res;
+ switch (cimg::lowercase(axis)) {
+ case 'x' : { // Along the X-axis.
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) {
+ dx+=img._width;
+ dy = std::max(dy,img._height);
+ dz = std::max(dz,img._depth);
+ dc = std::max(dc,img._spectrum);
+ }
+ }
+ res.assign(dx,dy,dz,dc,(T)0);
+ if (res) cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) res.draw_image(pos,
+ (int)(align*(dy - img._height)),
+ (int)(align*(dz - img._depth)),
+ (int)(align*(dc - img._spectrum)),
+ img);
+ pos+=img._width;
+ }
+ } break;
+ case 'y' : { // Along the Y-axis.
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) {
+ dx = std::max(dx,img._width);
+ dy+=img._height;
+ dz = std::max(dz,img._depth);
+ dc = std::max(dc,img._spectrum);
+ }
+ }
+ res.assign(dx,dy,dz,dc,(T)0);
+ if (res) cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) res.draw_image((int)(align*(dx - img._width)),
+ pos,
+ (int)(align*(dz - img._depth)),
+ (int)(align*(dc - img._spectrum)),
+ img);
+ pos+=img._height;
+ }
+ } break;
+ case 'z' : { // Along the Z-axis.
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) {
+ dx = std::max(dx,img._width);
+ dy = std::max(dy,img._height);
+ dz+=img._depth;
+ dc = std::max(dc,img._spectrum);
+ }
+ }
+ res.assign(dx,dy,dz,dc,(T)0);
+ if (res) cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) res.draw_image((int)(align*(dx - img._width)),
+ (int)(align*(dy - img._height)),
+ pos,
+ (int)(align*(dc - img._spectrum)),
+ img);
+ pos+=img._depth;
+ }
+ } break;
+ default : { // Along the C-axis.
+ cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) {
+ dx = std::max(dx,img._width);
+ dy = std::max(dy,img._height);
+ dz = std::max(dz,img._depth);
+ dc+=img._spectrum;
+ }
+ }
+ res.assign(dx,dy,dz,dc,(T)0);
+ if (res) cimglist_for(*this,l) {
+ const CImg<T>& img = (*this)[l];
+ if (img) res.draw_image((int)(align*(dx - img._width)),
+ (int)(align*(dy - img._height)),
+ (int)(align*(dz - img._depth)),
+ pos,
+ img);
+ pos+=img._spectrum;
+ }
+ }
+ }
+ return res;
+ }
+
+ //! Return a list where each image has been split along the specified axis.
+ /**
+ \param axis Axis to split images along.
+ \param nb Number of spliting parts for each image.
+ **/
+ CImgList<T>& split(const char axis, const int nb=-1) {
+ return get_split(axis,nb).move_to(*this);
+ }
+
+ //! Return a list where each image has been split along the specified axis \newinstance.
+ CImgList<T> get_split(const char axis, const int nb=-1) const {
+ CImgList<T> res;
+ cimglist_for(*this,l) _data[l].get_split(axis,nb).move_to(res,~0U);
+ return res;
+ }
+
+ //! Insert image at the end of the list.
+ /**
+ \param img Image to insert.
+ **/
+ template<typename t>
+ CImgList<T>& push_back(const CImg<t>& img) {
+ return insert(img);
+ }
+
+ //! Insert image at the front of the list.
+ /**
+ \param img Image to insert.
+ **/
+ template<typename t>
+ CImgList<T>& push_front(const CImg<t>& img) {
+ return insert(img,0);
+ }
+
+ //! Insert list at the end of the current list.
+ /**
+ \param list List to insert.
+ **/
+ template<typename t>
+ CImgList<T>& push_back(const CImgList<t>& list) {
+ return insert(list);
+ }
+
+ //! Insert list at the front of the current list.
+ /**
+ \param list List to insert.
+ **/
+ template<typename t>
+ CImgList<T>& push_front(const CImgList<t>& list) {
+ return insert(list,0);
+ }
+
+ //! Remove last image.
+ /**
+ **/
+ CImgList<T>& pop_back() {
+ return remove(_width - 1);
+ }
+
+ //! Remove first image.
+ /**
+ **/
+ CImgList<T>& pop_front() {
+ return remove(0);
+ }
+
+ //! Remove image pointed by iterator.
+ /**
+ \param iter Iterator pointing to the image to remove.
+ **/
+ CImgList<T>& erase(const iterator iter) {
+ return remove(iter - _data);
+ }
+
+ //@}
+ //----------------------------------
+ //
+ //! \name Data Input
+ //@{
+ //----------------------------------
+
+ //! Display a simple interactive interface to select images or sublists.
+ /**
+ \param disp Window instance to display selection and user interface.
+ \param feature_type Can be \c false to select a single image, or \c true to select a sublist.
+ \param axis Axis along whom images are appended for visualization.
+ \param align Alignment setting when images have not all the same size.
+ \return A one-column vector containing the selected image indexes.
+ **/
+ CImg<intT> get_select(CImgDisplay &disp, const bool feature_type=true,
+ const char axis='x', const float align=0,
+ const bool exit_on_anykey=false) const {
+ return _select(disp,0,feature_type,axis,align,exit_on_anykey,0,false,false,false);
+ }
+
+ //! Display a simple interactive interface to select images or sublists.
+ /**
+ \param title Title of a new window used to display selection and user interface.
+ \param feature_type Can be \c false to select a single image, or \c true to select a sublist.
+ \param axis Axis along whom images are appended for visualization.
+ \param align Alignment setting when images have not all the same size.
+ \return A one-column vector containing the selected image indexes.
+ **/
+ CImg<intT> get_select(const char *const title, const bool feature_type=true,
+ const char axis='x', const float align=0,
+ const bool exit_on_anykey=false) const {
+ CImgDisplay disp;
+ return _select(disp,title,feature_type,axis,align,exit_on_anykey,0,false,false,false);
+ }
+
+ CImg<intT> _select(CImgDisplay &disp, const char *const title, const bool feature_type,
+ const char axis, const float align, const bool exit_on_anykey,
+ const unsigned int orig, const bool resize_disp,
+ const bool exit_on_rightbutton, const bool exit_on_wheel) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "select(): Empty instance.",
+ cimglist_instance);
+
+ // Create image correspondence table and get list dimensions for visualization.
+ CImgList<uintT> _indices;
+ unsigned int max_width = 0, max_height = 0, sum_width = 0, sum_height = 0;
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ const unsigned int
+ w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
+ h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
+ if (w>max_width) max_width = w;
+ if (h>max_height) max_height = h;
+ sum_width+=w; sum_height+=h;
+ if (axis=='x') CImg<uintT>(w,1,1,1,(unsigned int)l).move_to(_indices);
+ else CImg<uintT>(h,1,1,1,(unsigned int)l).move_to(_indices);
+ }
+ const CImg<uintT> indices0 = _indices>'x';
+
+ // Create display window.
+ if (!disp) {
+ if (axis=='x') disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:0,1);
+ else disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:0,1);
+ if (!title) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width);
+ } else if (title) disp.set_title("%s",title);
+ if (resize_disp) {
+ if (axis=='x') disp.resize(cimg_fitscreen(sum_width,max_height,1),false);
+ else disp.resize(cimg_fitscreen(max_width,sum_height,1),false);
+ }
+
+ const unsigned int old_normalization = disp.normalization();
+ bool old_is_resized = disp.is_resized();
+ disp._normalization = 0;
+ disp.show().set_key(0);
+ static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 };
+
+ // Enter event loop.
+ CImg<ucharT> visu0, visu;
+ CImg<uintT> indices;
+ CImg<intT> positions(_width,4,1,1,-1);
+ int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1;
+ bool is_clicked = false, is_selected = false, text_down = false, update_display = true;
+ unsigned int key = 0;
+
+ while (!is_selected && !disp.is_closed() && !key) {
+
+ // Create background image.
+ if (!visu0) {
+ visu0.assign(disp._width,disp._height,1,3,0); visu.assign();
+ (indices0.get_resize(axis=='x'?visu0._width:visu0._height,1)).move_to(indices);
+ unsigned int ind = 0;
+ if (axis=='x') for (unsigned int x = 0; x<visu0._width; ) {
+ const unsigned int x0 = x;
+ ind = indices[x];
+ while (x<indices._width && indices[x++]==ind) {}
+ const CImg<T>
+ onexone(1,1,1,1,(T)0),
+ &src = _data[ind]?_data[ind]:onexone;
+ CImg<ucharT> res;
+ src.__get_select(disp,old_normalization,(src._width - 1)/2,(src._height - 1)/2,(src._depth - 1)/2).
+ move_to(res);
+ const unsigned int h = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,true);
+ res.resize(x - x0,std::max(32U,h*disp._height/max_height),1,res._spectrum==1?3:-100);
+ positions(ind,0) = positions(ind,2) = (int)x0;
+ positions(ind,1) = positions(ind,3) = (int)(align*(visu0.height() - res.height()));
+ positions(ind,2)+=res._width;
+ positions(ind,3)+=res._height - 1;
+ visu0.draw_image(positions(ind,0),positions(ind,1),res);
+ } else for (unsigned int y = 0; y<visu0._height; ) {
+ const unsigned int y0 = y;
+ ind = indices[y];
+ while (y<visu0._height && indices[++y]==ind) {}
+ const CImg<T> &src = _data[ind];
+ const CImg<Tuchar>
+ img2d = src._depth>1?src.get_projections2d((src._width - 1)/2,(src._height - 1)/2,(src._depth - 1)/2):
+ cimg::type<Tuchar>::string()==cimg::type<T>::string()?src.get_shared():src;
+ CImg<ucharT> res = old_normalization==1 ||
+ (old_normalization==3 && cimg::type<T>::string()!=cimg::type<unsigned char>::string())?
+ CImg<ucharT>(img2d.get_normalize((Tuchar)0,(Tuchar)255)):
+ CImg<ucharT>(img2d);
+ if (res._spectrum>3) res.channels(0,2);
+ const unsigned int w = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,false);
+ res.resize(std::max(32U,w*disp._width/max_width),y - y0,1,res._spectrum==1?3:-100);
+ positions(ind,0) = positions(ind,2) = (int)(align*(visu0.width() - res.width()));
+ positions(ind,1) = positions(ind,3) = (int)y0;
+ positions(ind,2)+=res._width - 1;
+ positions(ind,3)+=res._height;
+ visu0.draw_image(positions(ind,0),positions(ind,1),res);
+ }
+ if (axis=='x') --positions(ind,2); else --positions(ind,3);
+ update_display = true;
+ }
+
+ if (!visu || oindice0!=indice0 || oindice1!=indice1) {
+ if (indice0>=0 && indice1>=0) {
+ visu.assign(visu0,false);
+ const int indm = std::min(indice0,indice1), indM = std::max(indice0,indice1);
+ for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) {
+ visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3),
+ background_color,0.2f);
+ if ((axis=='x' && positions(ind,2) - positions(ind,0)>=8) ||
+ (axis!='x' && positions(ind,3) - positions(ind,1)>=8))
+ visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3),
+ foreground_color,0.9f,0xAAAAAAAA);
+ }
+ const int yt = (int)text_down?visu.height() - 13:0;
+ if (is_clicked) visu.draw_text(0,yt," Images #%u - #%u, Size = %u",
+ foreground_color,background_color,0.7f,13,
+ orig + indm,orig + indM,indM - indm + 1);
+ else visu.draw_text(0,yt," Image #%u (%u,%u,%u,%u)",foreground_color,background_color,0.7f,13,
+ orig + indice0,
+ _data[indice0]._width,
+ _data[indice0]._height,
+ _data[indice0]._depth,
+ _data[indice0]._spectrum);
+ update_display = true;
+ } else visu.assign();
+ }
+ if (!visu) { visu.assign(visu0,true); update_display = true; }
+ if (update_display) { visu.display(disp); update_display = false; }
+ disp.wait();
+
+ // Manage user events.
+ const int xm = disp.mouse_x(), ym = disp.mouse_y();
+ int indice = -1;
+
+ if (xm>=0) {
+ indice = (int)indices(axis=='x'?xm:ym);
+ if (disp.button()&1) {
+ if (!is_clicked) { is_clicked = true; oindice0 = indice0; indice0 = indice; }
+ oindice1 = indice1; indice1 = indice;
+ if (!feature_type) is_selected = true;
+ } else {
+ if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; }
+ else is_selected = true;
+ }
+ } else {
+ if (is_clicked) {
+ if (!(disp.button()&1)) { is_clicked = is_selected = false; indice0 = indice1 = -1; }
+ else indice1 = -1;
+ } else indice0 = indice1 = -1;
+ }
+
+ if (disp.button()&4) { is_clicked = is_selected = false; indice0 = indice1 = -1; }
+ if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; indice1 = indice0 = -1; }
+ if (disp.wheel() && exit_on_wheel) is_selected = true;
+
+ CImg<charT> filename(32);
+ switch (key = disp.key()) {
+#if cimg_OS!=2
+ case cimg::keyCTRLRIGHT :
+#endif
+ case 0 : case cimg::keyCTRLLEFT : key = 0; break;
+ case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
+ CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false).
+ _is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.set_fullscreen(false).
+ resize(cimg_fitscreen(axis=='x'?sum_width:max_width,axis=='x'?max_height:sum_height,1),false).
+ _is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
+ disp.set_key(key,false); key = 0; visu0.assign();
+ } break;
+ case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ static unsigned int snap_number = 0;
+ std::FILE *file;
+ do {
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++);
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ if (visu0) {
+ (+visu0).draw_text(0,0," Saving snapshot... ",
+ foreground_color,background_color,0.7f,13).display(disp);
+ visu0.save(filename);
+ (+visu0).draw_text(0,0," Snapshot '%s' saved. ",
+ foreground_color,background_color,0.7f,13,filename._data).display(disp);
+ }
+ disp.set_key(key,false).wait(); key = 0;
+ } break;
+ case cimg::keyO :
+ if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
+ static unsigned int snap_number = 0;
+ std::FILE *file;
+ do {
+#ifdef cimg_use_zlib
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++);
+#else
+ cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++);
+#endif
+ if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file);
+ } while (file);
+ (+visu0).draw_text(0,0," Saving instance... ",
+ foreground_color,background_color,0.7f,13).display(disp);
+ save(filename);
+ (+visu0).draw_text(0,0," Instance '%s' saved. ",
+ foreground_color,background_color,0.7f,13,filename._data).display(disp);
+ disp.set_key(key,false).wait(); key = 0;
+ } break;
+ }
+ if (disp.is_resized()) { disp.resize(false); visu0.assign(); }
+ if (ym>=0 && ym<13) { if (!text_down) { visu.assign(); text_down = true; }}
+ else if (ym>=visu.height() - 13) { if (text_down) { visu.assign(); text_down = false; }}
+ if (!exit_on_anykey && key && key!=cimg::keyESC &&
+ (key!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
+ key = 0;
+ }
+ }
+ CImg<intT> res(1,2,1,1,-1);
+ if (is_selected) {
+ if (feature_type) res.fill(std::min(indice0,indice1),std::max(indice0,indice1));
+ else res.fill(indice0);
+ }
+ if (!(disp.button()&2)) disp.set_button();
+ disp._normalization = old_normalization;
+ disp._is_resized = old_is_resized;
+ disp.set_key(key);
+ return res;
+ }
+
+ //! Load a list from a file.
+ /**
+ \param filename Filename to read data from.
+ **/
+ CImgList<T>& load(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "load(): Specified filename is (null).",
+ cimglist_instance);
+
+ if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) {
+ CImg<charT> filename_local(256);
+ load(cimg::load_network(filename,filename_local));
+ std::remove(filename_local);
+ return *this;
+ }
+
+ const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.');
+ const char *const ext = cimg::split_filename(filename);
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ bool is_loaded = true;
+ try {
+#ifdef cimglist_load_plugin
+ cimglist_load_plugin(filename);
+#endif
+#ifdef cimglist_load_plugin1
+ cimglist_load_plugin1(filename);
+#endif
+#ifdef cimglist_load_plugin2
+ cimglist_load_plugin2(filename);
+#endif
+#ifdef cimglist_load_plugin3
+ cimglist_load_plugin3(filename);
+#endif
+#ifdef cimglist_load_plugin4
+ cimglist_load_plugin4(filename);
+#endif
+#ifdef cimglist_load_plugin5
+ cimglist_load_plugin5(filename);
+#endif
+#ifdef cimglist_load_plugin6
+ cimglist_load_plugin6(filename);
+#endif
+#ifdef cimglist_load_plugin7
+ cimglist_load_plugin7(filename);
+#endif
+#ifdef cimglist_load_plugin8
+ cimglist_load_plugin8(filename);
+#endif
+ if (!cimg::strcasecmp(ext,"tif") ||
+ !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
+ else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename);
+ else if (!cimg::strcasecmp(ext,"cimg") ||
+ !cimg::strcasecmp(ext,"cimgz") ||
+ !*ext) load_cimg(filename);
+ else if (!cimg::strcasecmp(ext,"rec") ||
+ !cimg::strcasecmp(ext,"par")) load_parrec(filename);
+ else if (!cimg::strcasecmp(ext,"avi") ||
+ !cimg::strcasecmp(ext,"mov") ||
+ !cimg::strcasecmp(ext,"asf") ||
+ !cimg::strcasecmp(ext,"divx") ||
+ !cimg::strcasecmp(ext,"flv") ||
+ !cimg::strcasecmp(ext,"mpg") ||
+ !cimg::strcasecmp(ext,"m1v") ||
+ !cimg::strcasecmp(ext,"m2v") ||
+ !cimg::strcasecmp(ext,"m4v") ||
+ !cimg::strcasecmp(ext,"mjp") ||
+ !cimg::strcasecmp(ext,"mp4") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"ogg") ||
+ !cimg::strcasecmp(ext,"ogv") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) load_video(filename);
+ else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
+ else is_loaded = false;
+ } catch (CImgIOException&) { is_loaded = false; }
+
+ // If nothing loaded, try to guess file format from magic number in file.
+ if (!is_loaded && !is_stdin) {
+ std::FILE *const file = std_fopen(filename,"rb");
+ if (!file) {
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimglist_instance
+ "load(): Failed to open file '%s'.",
+ cimglist_instance,
+ filename);
+ }
+
+ const char *const f_type = cimg::ftype(file,filename);
+ std::fclose(file);
+ is_loaded = true;
+ try {
+ if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename);
+ else if (!cimg::strcasecmp(f_type,"tif")) load_tiff(filename);
+ else is_loaded = false;
+ } catch (CImgIOException&) { is_loaded = false; }
+ }
+
+ // If nothing loaded, try to load file as a single image.
+ if (!is_loaded) {
+ assign(1);
+ try {
+ _data->load(filename);
+ } catch (CImgIOException&) {
+ cimg::exception_mode(omode);
+ throw CImgIOException(_cimglist_instance
+ "load(): Failed to recognize format of file '%s'.",
+ cimglist_instance,
+ filename);
+ }
+ }
+ cimg::exception_mode(omode);
+ return *this;
+ }
+
+ //! Load a list from a file \newinstance.
+ static CImgList<T> get_load(const char *const filename) {
+ return CImgList<T>().load(filename);
+ }
+
+ //! Load a list from a .cimg file.
+ /**
+ \param filename Filename to read data from.
+ **/
+ CImgList<T>& load_cimg(const char *const filename) {
+ return _load_cimg(0,filename);
+ }
+
+ //! Load a list from a .cimg file \newinstance.
+ static CImgList<T> get_load_cimg(const char *const filename) {
+ return CImgList<T>().load_cimg(filename);
+ }
+
+ //! Load a list from a .cimg file.
+ /**
+ \param file File to read data from.
+ **/
+ CImgList<T>& load_cimg(std::FILE *const file) {
+ return _load_cimg(file,0);
+ }
+
+ //! Load a list from a .cimg file \newinstance.
+ static CImgList<T> get_load_cimg(std::FILE *const file) {
+ return CImgList<T>().load_cimg(file);
+ }
+
+ CImgList<T>& _load_cimg(std::FILE *const file, const char *const filename) {
+#ifdef cimg_use_zlib
+#define _cimgz_load_cimg_case(Tss) { \
+ Bytef *const cbuf = new Bytef[csiz]; \
+ cimg::fread(cbuf,csiz,nfile); \
+ raw.assign(W,H,D,C); \
+ uLongf destlen = (ulongT)raw.size()*sizeof(Tss); \
+ uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \
+ delete[] cbuf; \
+ if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \
+ raw.move_to(img); \
+}
+#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 && j<255) tmp[j++] = (char)i; tmp[j] = 0; \
+ W = H = D = C = 0; csiz = 0; \
+ if ((err = cimg_sscanf(tmp,"%u %u %u %u #%lu",&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]; \
+ if (err==5) _cimgz_load_cimg_case(Tss) \
+ else { \
+ img.assign(W,H,D,C); \
+ T *ptrd = img._data; \
+ for (ulongT to_read = img.size(); to_read; ) { \
+ raw.assign((unsigned int)std::min(to_read,cimg_iobuffer)); \
+ cimg::fread(raw._data,raw._width,nfile); \
+ if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \
+ const Tss *ptrs = raw._data; \
+ for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); \
+ to_read-=raw._width; \
+ } \
+ } \
+ } \
+ } \
+ loaded = true; \
+ }
+
+ if (!filename && !file)
+ throw CImgArgumentException(_cimglist_instance
+ "load_cimg(): Specified filename is (null).",
+ cimglist_instance);
+
+ const ulongT cimg_iobuffer = (ulongT)24*1024*1024;
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ bool loaded = false, endian = cimg::endianness();
+ CImg<charT> tmp(256), str_pixeltype(256), str_endian(256);
+ *tmp = *str_pixeltype = *str_endian = 0;
+ unsigned int j, N = 0, W, H, D, C;
+ unsigned long csiz;
+ int i, err;
+ do {
+ j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0 && j<255) tmp[j++] = (char)i; tmp[j] = 0;
+ } while (*tmp=='#' && i>=0);
+ err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",
+ &N,str_pixeltype._data,str_endian._data);
+ 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",ulongT);
+ _cimg_load_cimg_case("ulong",ulongT);
+ _cimg_load_cimg_case("long",longT);
+ _cimg_load_cimg_case("unsigned_int64",uint64T);
+ _cimg_load_cimg_case("uint64",uint64T);
+ _cimg_load_cimg_case("int64",int64T);
+ _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._data,filename?filename:"(FILE*)");
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load a sublist list from a (non compressed) .cimg file.
+ /**
+ \param filename Filename to read data from.
+ \param n0 Starting index of images to read (~0U for max).
+ \param n1 Ending index of images to read (~0U for max).
+ \param x0 Starting X-coordinates of image regions to read.
+ \param y0 Starting Y-coordinates of image regions to read.
+ \param z0 Starting Z-coordinates of image regions to read.
+ \param c0 Starting C-coordinates of image regions to read.
+ \param x1 Ending X-coordinates of image regions to read (~0U for max).
+ \param y1 Ending Y-coordinates of image regions to read (~0U for max).
+ \param z1 Ending Z-coordinates of image regions to read (~0U for max).
+ \param c1 Ending C-coordinates of image regions to read (~0U for max).
+ **/
+ CImgList<T>& load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1) {
+ return _load_cimg(0,filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+ }
+
+ //! Load a sublist list from a (non compressed) .cimg file \newinstance.
+ static CImgList<T> get_load_cimg(const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1) {
+ return CImgList<T>().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+ }
+
+ //! Load a sub-image list from a (non compressed) .cimg file \overloading.
+ CImgList<T>& load_cimg(std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1) {
+ return _load_cimg(file,0,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+ }
+
+ //! Load a sub-image list from a (non compressed) .cimg file \newinstance.
+ static CImgList<T> get_load_cimg(std::FILE *const file,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1) {
+ return CImgList<T>().load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
+ }
+
+ CImgList<T>& _load_cimg(std::FILE *const file, const char *const filename,
+ const unsigned int n0, const unsigned int n1,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0,
+ const unsigned int x1, const unsigned int y1,
+ const unsigned int z1, const unsigned int c1) {
+#define _cimg_load_cimg_case2(Ts,Tss) \
+ if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+ for (unsigned int l = 0; l<=nn1; ++l) { \
+ j = 0; while ((i=std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = 0; \
+ W = H = D = C = 0; \
+ if (cimg_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<nn0 || nx0>=W || ny0>=H || nz0>=D || nc0>=C) cimg::fseek(nfile,W*H*D*C*sizeof(Tss),SEEK_CUR); \
+ else { \
+ const unsigned int \
+ _nx1 = nx1==~0U?W - 1:nx1, \
+ _ny1 = ny1==~0U?H - 1:ny1, \
+ _nz1 = nz1==~0U?D - 1:nz1, \
+ _nc1 = nc1==~0U?C - 1:nc1; \
+ if (_nx1>=W || _ny1>=H || _nz1>=D || _nc1>=C) \
+ throw CImgArgumentException(_cimglist_instance \
+ "load_cimg(): Invalid specified coordinates " \
+ "[%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) " \
+ "because image [%u] in file '%s' has size (%u,%u,%u,%u).", \
+ cimglist_instance, \
+ n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,l,filename?filename:"(FILE*)",W,H,D,C); \
+ CImg<Tss> raw(1 + _nx1 - nx0); \
+ CImg<T> &img = _data[l - nn0]; \
+ img.assign(1 + _nx1 - nx0,1 + _ny1 - ny0,1 + _nz1 - nz0,1 + _nc1 - nc0); \
+ T *ptrd = img._data; \
+ ulongT skipvb = nc0*W*H*D*sizeof(Tss); \
+ if (skipvb) cimg::fseek(nfile,skipvb,SEEK_CUR); \
+ for (unsigned int c = 1 + _nc1 - nc0; c; --c) { \
+ const ulongT skipzb = nz0*W*H*sizeof(Tss); \
+ if (skipzb) cimg::fseek(nfile,skipzb,SEEK_CUR); \
+ for (unsigned int z = 1 + _nz1 - nz0; z; --z) { \
+ const ulongT skipyb = ny0*W*sizeof(Tss); \
+ if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \
+ for (unsigned int y = 1 + _ny1 - ny0; y; --y) { \
+ const ulongT skipxb = nx0*sizeof(Tss); \
+ if (skipxb) cimg::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 ulongT skipxe = (W - 1 - _nx1)*sizeof(Tss); \
+ if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \
+ } \
+ const ulongT skipye = (H - 1 - _ny1)*W*sizeof(Tss); \
+ if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \
+ } \
+ const ulongT skipze = (D - 1 - _nz1)*W*H*sizeof(Tss); \
+ if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \
+ } \
+ const ulongT skipve = (C - 1 - _nc1)*W*H*D*sizeof(Tss); \
+ if (skipve) cimg::fseek(nfile,skipve,SEEK_CUR); \
+ } \
+ } \
+ } \
+ loaded = true; \
+ }
+
+ if (!filename && !file)
+ throw CImgArgumentException(_cimglist_instance
+ "load_cimg(): Specified filename is (null).",
+ cimglist_instance);
+ unsigned int
+ nn0 = std::min(n0,n1), nn1 = std::max(n0,n1),
+ nx0 = std::min(x0,x1), nx1 = std::max(x0,x1),
+ ny0 = std::min(y0,y1), ny1 = std::max(y0,y1),
+ nz0 = std::min(z0,z1), nz1 = std::max(z0,z1),
+ nc0 = std::min(c0,c1), nc1 = std::max(c0,c1);
+
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ bool loaded = false, endian = cimg::endianness();
+ CImg<charT> tmp(256), str_pixeltype(256), str_endian(256);
+ *tmp = *str_pixeltype = *str_endian = 0;
+ unsigned int j, N, W, H, D, C;
+ int i, err;
+ j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+ err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",
+ &N,str_pixeltype._data,str_endian._data);
+ 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;
+ nn1 = n1==~0U?N - 1:n1;
+ if (nn1>=N)
+ throw CImgArgumentException(_cimglist_instance
+ "load_cimg(): Invalid specified coordinates [%u](%u,%u,%u,%u) -> [%u](%u,%u,%u,%u) "
+ "because file '%s' contains only %u images.",
+ cimglist_instance,
+ n0,x0,y0,z0,c0,n1,x1,y1,z1,c1,filename?filename:"(FILE*)",N);
+ 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",ulongT);
+ _cimg_load_cimg_case2("ulong",ulongT);
+ _cimg_load_cimg_case2("long",longT);
+ _cimg_load_cimg_case2("unsigned_int64",uint64T);
+ _cimg_load_cimg_case2("uint64",uint64T);
+ _cimg_load_cimg_case2("int64",int64T);
+ _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._data,filename?filename:"(FILE*)");
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Load a list from a PAR/REC (Philips) file.
+ /**
+ \param filename Filename to read data from.
+ **/
+ CImgList<T>& load_parrec(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "load_parrec(): Specified filename is (null).",
+ cimglist_instance);
+
+ CImg<charT> body(1024), filenamepar(1024), filenamerec(1024);
+ *body = *filenamepar = *filenamerec = 0;
+ const char *const ext = cimg::split_filename(filename,body);
+ if (!std::strcmp(ext,"par")) {
+ std::strncpy(filenamepar,filename,filenamepar._width - 1);
+ cimg_snprintf(filenamerec,filenamerec._width,"%s.rec",body._data);
+ }
+ if (!std::strcmp(ext,"PAR")) {
+ std::strncpy(filenamepar,filename,filenamepar._width - 1);
+ cimg_snprintf(filenamerec,filenamerec._width,"%s.REC",body._data);
+ }
+ if (!std::strcmp(ext,"rec")) {
+ std::strncpy(filenamerec,filename,filenamerec._width - 1);
+ cimg_snprintf(filenamepar,filenamepar._width,"%s.par",body._data);
+ }
+ if (!std::strcmp(ext,"REC")) {
+ std::strncpy(filenamerec,filename,filenamerec._width - 1);
+ cimg_snprintf(filenamepar,filenamepar._width,"%s.PAR",body._data);
+ }
+ std::FILE *file = cimg::fopen(filenamepar,"r");
+
+ // Parse header file
+ CImgList<floatT> st_slices;
+ CImgList<uintT> st_global;
+ CImg<charT> line(256); *line = 0;
+ int err;
+ do { err = std::fscanf(file,"%255[^\n]%*c",line._data); } while (err!=EOF && (*line=='#' || *line=='.'));
+ do {
+ unsigned int sn,size_x,size_y,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,&size_x,&size_y,&ri,&rs,&ss);
+ if (err==7) {
+ CImg<floatT>::vector((float)sn,(float)pixsize,(float)size_x,(float)size_y,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(size_x,size_y,sn).move_to(st_global);
+ else {
+ CImg<uintT> &vec = st_global[i];
+ if (size_x>vec[0]) vec[0] = size_x;
+ if (size_y>vec[1]) vec[1] = size_y;
+ 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],
+ size_x = (unsigned int)vec[2],
+ size_y = (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(size_x,size_y);
+ cimg::fread(buf._data,size_x*size_y,file2);
+ if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y);
+ 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(size_x,size_y);
+ cimg::fread(buf._data,size_x*size_y,file2);
+ if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y);
+ 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(size_x,size_y);
+ cimg::fread(buf._data,size_x*size_y,file2);
+ if (cimg::endianness()) cimg::invert_endianness(buf._data,size_x*size_y);
+ CImg<T>& img = (*this)[imn];
+ cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
+ } break;
+ default :
+ cimg::fclose(file);
+ cimg::fclose(file2);
+ throw CImgIOException(_cimglist_instance
+ "load_parrec(): Unsupported %d-bits pixel type for file '%s'.",
+ cimglist_instance,
+ pixsize,filename);
+ }
+ }
+ cimg::fclose(file);
+ cimg::fclose(file2);
+ if (!_width)
+ throw CImgIOException(_cimglist_instance
+ "load_parrec(): Failed to recognize valid PAR-REC data in file '%s'.",
+ cimglist_instance,
+ filename);
+ return *this;
+ }
+
+ //! Load a list from a PAR/REC (Philips) file \newinstance.
+ static CImgList<T> get_load_parrec(const char *const filename) {
+ return CImgList<T>().load_parrec(filename);
+ }
+
+ //! Load a list from a YUV image sequence file.
+ /**
+ \param filename Filename to read data from.
+ \param size_x Width of the images.
+ \param size_y Height of the images.
+ \param first_frame Index of first image frame to read.
+ \param last_frame Index of last image frame to read.
+ \param step_frame Step applied between each frame.
+ \param yuv2rgb Apply YUV to RGB transformation during reading.
+ **/
+ CImgList<T>& load_yuv(const char *const filename,
+ const unsigned int size_x, const unsigned int size_y,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return _load_yuv(0,filename,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ //! Load a list from a YUV image sequence file \newinstance.
+ static CImgList<T> get_load_yuv(const char *const filename,
+ const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return CImgList<T>().load_yuv(filename,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ //! Load a list from an image sequence YUV file \overloading.
+ CImgList<T>& load_yuv(std::FILE *const file,
+ const unsigned int size_x, const unsigned int size_y,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return _load_yuv(file,0,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ //! Load a list from an image sequence YUV file \newinstance.
+ static CImgList<T> get_load_yuv(std::FILE *const file,
+ const unsigned int size_x, const unsigned int size_y=1,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1, const bool yuv2rgb=true) {
+ return CImgList<T>().load_yuv(file,size_x,size_y,first_frame,last_frame,step_frame,yuv2rgb);
+ }
+
+ CImgList<T>& _load_yuv(std::FILE *const file, const char *const filename,
+ const unsigned int size_x, const unsigned int size_y,
+ const unsigned int first_frame, const unsigned int last_frame,
+ const unsigned int step_frame, const bool yuv2rgb) {
+ if (!filename && !file)
+ throw CImgArgumentException(_cimglist_instance
+ "load_yuv(): Specified filename is (null).",
+ cimglist_instance);
+ if (size_x%2 || size_y%2)
+ throw CImgArgumentException(_cimglist_instance
+ "load_yuv(): Invalid odd XY dimensions %ux%u in file '%s'.",
+ cimglist_instance,
+ size_x,size_y,filename?filename:"(FILE*)");
+ if (!size_x || !size_y)
+ throw CImgArgumentException(_cimglist_instance
+ "load_yuv(): Invalid sequence size (%u,%u) in file '%s'.",
+ cimglist_instance,
+ size_x,size_y,filename?filename:"(FILE*)");
+
+ const unsigned int
+ nfirst_frame = first_frame<last_frame?first_frame:last_frame,
+ nlast_frame = first_frame<last_frame?last_frame:first_frame,
+ nstep_frame = step_frame?step_frame:1;
+
+ CImg<ucharT> tmp(size_x,size_y,1,3), UV(size_x/2,size_y/2,1,2);
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
+ bool stop_flag = false;
+ int err;
+ if (nfirst_frame) {
+ err = cimg::fseek(nfile,nfirst_frame*(size_x*size_y + size_x*size_y/2),SEEK_CUR);
+ if (err) {
+ if (!file) cimg::fclose(nfile);
+ throw CImgIOException(_cimglist_instance
+ "load_yuv(): File '%s' doesn't contain frame number %u.",
+ cimglist_instance,
+ filename?filename:"(FILE*)",nfirst_frame);
+ }
+ }
+ unsigned int frame;
+ for (frame = nfirst_frame; !stop_flag && 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,(ulongT)tmp._width*tmp._height,nfile);
+ if (err!=(int)(tmp._width*tmp._height)) {
+ stop_flag = true;
+ if (err>0)
+ cimg::warn(_cimglist_instance
+ "load_yuv(): File '%s' contains incomplete data or given image dimensions "
+ "(%u,%u) are incorrect.",
+ cimglist_instance,
+ filename?filename:"(FILE*)",size_x,size_y);
+ } else {
+ UV.fill(0);
+ // *TRY* to read the luminance part, do not replace by cimg::fread!
+ err = (int)std::fread((void*)(UV._data),1,(size_t)(UV.size()),nfile);
+ if (err!=(int)(UV.size())) {
+ stop_flag = true;
+ if (err>0)
+ cimg::warn(_cimglist_instance
+ "load_yuv(): File '%s' contains incomplete data or given image dimensions (%u,%u) "
+ "are incorrect.",
+ cimglist_instance,
+ filename?filename:"(FILE*)",size_x,size_y);
+ } else {
+ cimg_forXY(UV,x,y) {
+ const int x2 = x*2, y2 = y*2;
+ tmp(x2,y2,1) = tmp(x2 + 1,y2,1) = tmp(x2,y2 + 1,1) = tmp(x2 + 1,y2 + 1,1) = UV(x,y,0);
+ tmp(x2,y2,2) = tmp(x2 + 1,y2,2) = tmp(x2,y2 + 1,2) = tmp(x2 + 1,y2 + 1,2) = UV(x,y,1);
+ }
+ if (yuv2rgb) tmp.YCbCrtoRGB();
+ insert(tmp);
+ if (nstep_frame>1) cimg::fseek(nfile,(nstep_frame - 1)*(size_x*size_y + size_x*size_y/2),SEEK_CUR);
+ }
+ }
+ }
+ if (stop_flag && 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 OpenCV library.
+ /**
+ \param filename Filename, as a C-string.
+ \param first_frame Index of the first frame to read.
+ \param last_frame Index of the last frame to read.
+ \param step_frame Step value for frame reading.
+ \note If step_frame==0, the current video stream is forced to be released (without any frames read).
+ **/
+ CImgList<T>& load_video(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1) {
+#ifndef cimg_use_opencv
+ if (first_frame || last_frame!=~0U || step_frame>1)
+ throw CImgArgumentException(_cimglist_instance
+ "load_video() : File '%s', arguments 'first_frame', 'last_frame' "
+ "and 'step_frame' can be only set when using OpenCV "
+ "(-Dcimg_use_opencv must be enabled).",
+ cimglist_instance,filename);
+ return load_ffmpeg_external(filename);
+#else
+ static CvCapture *captures[32] = { 0 };
+ static CImgList<charT> filenames(32);
+ static CImg<uintT> positions(32,1,1,1,0);
+ static int last_used_index = -1;
+
+ // Detect if a video capture already exists for the specified filename.
+ cimg::mutex(9);
+ int index = -1;
+ if (filename) {
+ if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) {
+ index = last_used_index;
+ } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) {
+ index = l; break;
+ }
+ } else index = last_used_index;
+ cimg::mutex(9,0);
+
+ // Release stream if needed.
+ if (!step_frame || (index>=0 && positions[index]>first_frame)) {
+ if (index>=0) {
+ cimg::mutex(9);
+ cvReleaseCapture(&captures[index]);
+ captures[index] = 0; filenames[index].assign(); positions[index] = 0;
+ if (last_used_index==index) last_used_index = -1;
+ index = -1;
+ cimg::mutex(9,0);
+ } else
+ if (filename)
+ cimg::warn(_cimglist_instance
+ "load_video() : File '%s', no opened video stream associated with filename found.",
+ cimglist_instance,filename);
+ else
+ cimg::warn(_cimglist_instance
+ "load_video() : No opened video stream found.",
+ cimglist_instance,filename);
+ if (!step_frame) return *this;
+ }
+
+ // Find empty slot for capturing video stream.
+ if (index<0) {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "load_video(): No already open video reader found. You must specify a "
+ "non-(null) filename argument for the first call.",
+ cimglist_instance);
+ else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); }
+ if (index<0)
+ throw CImgIOException(_cimglist_instance
+ "load_video(): File '%s', no video reader slots available. "
+ "You have to release some of your previously opened videos.",
+ cimglist_instance,filename);
+ cimg::mutex(9);
+ captures[index] = cvCaptureFromFile(filename);
+ CImg<charT>::string(filename).move_to(filenames[index]);
+ positions[index] = 0;
+ cimg::mutex(9,0);
+ if (!captures[index]) {
+ filenames[index].assign();
+ std::fclose(cimg::fopen(filename,"rb")); // Check file availability.
+ throw CImgIOException(_cimglist_instance
+ "load_video(): File '%s', unable to detect format of video file.",
+ cimglist_instance,filename);
+ }
+ }
+
+ cimg::mutex(9);
+ const unsigned int nb_frames = (unsigned int)std::max(0.,cvGetCaptureProperty(captures[index],
+ CV_CAP_PROP_FRAME_COUNT));
+ cimg::mutex(9,0);
+ assign();
+
+ // Skip frames if necessary.
+ bool go_on = true;
+ unsigned int &pos = positions[index];
+ while (pos<first_frame) {
+ cimg::mutex(9);
+ if (!cvGrabFrame(captures[index])) { cimg::mutex(9,0); go_on = false; break; }
+ cimg::mutex(9,0);
+ ++pos;
+ }
+
+ // Read and convert frames.
+ const IplImage *src = 0;
+ if (go_on) {
+ const unsigned int _last_frame = std::min(nb_frames?nb_frames - 1:~0U,last_frame);
+ while (pos<=_last_frame) {
+ cimg::mutex(9);
+ src = cvQueryFrame(captures[index]);
+ if (src) {
+ CImg<T> frame(src->width,src->height,1,3);
+ const int step = (int)(src->widthStep - 3*src->width);
+ const unsigned char* ptrs = (unsigned char*)src->imageData;
+ T *ptr_r = frame.data(0,0,0,0), *ptr_g = frame.data(0,0,0,1), *ptr_b = frame.data(0,0,0,2);
+ if (step>0) cimg_forY(frame,y) {
+ cimg_forX(frame,x) { *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++); }
+ ptrs+=step;
+ } else for (ulongT siz = (ulongT)src->width*src->height; siz; --siz) {
+ *(ptr_b++) = (T)*(ptrs++); *(ptr_g++) = (T)*(ptrs++); *(ptr_r++) = (T)*(ptrs++);
+ }
+ frame.move_to(*this);
+ ++pos;
+
+ bool skip_failed = false;
+ for (unsigned int i = 1; i<step_frame && pos<=_last_frame; ++i, ++pos)
+ if (!cvGrabFrame(captures[index])) { skip_failed = true; break; }
+ if (skip_failed) src = 0;
+ }
+ cimg::mutex(9,0);
+ if (!src) break;
+ }
+ }
+
+ if (!src || (nb_frames && pos>=nb_frames)) { // Close video stream when necessary.
+ cimg::mutex(9);
+ cvReleaseCapture(&captures[index]);
+ captures[index] = 0;
+ filenames[index].assign();
+ positions[index] = 0;
+ index = -1;
+ cimg::mutex(9,0);
+ }
+
+ cimg::mutex(9);
+ last_used_index = index;
+ cimg::mutex(9,0);
+
+ if (is_empty())
+ throw CImgIOException(_cimglist_instance
+ "load_video(): File '%s', unable to locate frame %u.",
+ cimglist_instance,filename,first_frame);
+ return *this;
+#endif
+ }
+
+ //! Load an image from a video file, using OpenCV library \newinstance.
+ static CImgList<T> get_load_video(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_video(filename,first_frame,last_frame,step_frame);
+ }
+
+ //! Load an image from a video file using the external tool 'ffmpeg'.
+ /**
+ \param filename Filename to read data from.
+ **/
+ CImgList<T>& load_ffmpeg_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "load_ffmpeg_external(): Specified filename is (null).",
+ cimglist_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ CImg<charT> command(1024), filename_tmp(256), filename_tmp2(256);
+ std::FILE *file = 0;
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data);
+ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%%6d.ppm",filename_tmp._data);
+#if cimg_OS!=2
+ cimg_snprintf(command,command._width,"%s -i \"%s\" \"%s\" >/dev/null 2>&1",
+ cimg::ffmpeg_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp2)._system_strescape().data());
+#else
+ cimg_snprintf(command,command._width,"\"%s -i \"%s\" \"%s\"\" >NUL 2>&1",
+ cimg::ffmpeg_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp2)._system_strescape().data());
+#endif
+ cimg::system(command,0);
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ assign();
+ unsigned int i = 1;
+ for (bool stop_flag = false; !stop_flag; ++i) {
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,i);
+ CImg<T> img;
+ try { img.load_pnm(filename_tmp2); }
+ catch (CImgException&) { stop_flag = true; }
+ if (img) { img.move_to(*this); std::remove(filename_tmp2); }
+ }
+ cimg::exception_mode(omode);
+ if (is_empty())
+ throw CImgIOException(_cimglist_instance
+ "load_ffmpeg_external(): Failed to open file '%s' with external command 'ffmpeg'.",
+ cimglist_instance,
+ filename);
+ return *this;
+ }
+
+ //! Load an image from a video file using the external tool 'ffmpeg' \newinstance.
+ static CImgList<T> get_load_ffmpeg_external(const char *const filename) {
+ return CImgList<T>().load_ffmpeg_external(filename);
+ }
+
+ //! Load gif file, using ImageMagick or GraphicsMagick's external tools.
+ /**
+ \param filename Filename to read data from.
+ \param use_graphicsmagick Tells if GraphicsMagick's tool 'gm' is used instead of ImageMagick's tool 'convert'.
+ **/
+ CImgList<T>& load_gif_external(const char *const filename) {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "load_gif_external(): Specified filename is (null).",
+ cimglist_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ if (!_load_gif_external(filename,false))
+ if (!_load_gif_external(filename,true))
+ try { assign(CImg<T>().load_other(filename)); } catch (CImgException&) { assign(); }
+ if (is_empty())
+ throw CImgIOException(_cimglist_instance
+ "load_gif_external(): Failed to open file '%s'.",
+ cimglist_instance,filename);
+ return *this;
+ }
+
+ CImgList<T>& _load_gif_external(const char *const filename, const bool use_graphicsmagick=false) {
+ CImg<charT> command(1024), filename_tmp(256), filename_tmp2(256);
+ std::FILE *file = 0;
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.0",filename_tmp._data);
+ else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-0.png",filename_tmp._data);
+ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+#if cimg_OS!=2
+ if (use_graphicsmagick) cimg_snprintf(command,command._width,"%s convert \"%s\" \"%s.png\" >/dev/null 2>&1",
+ cimg::graphicsmagick_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+ else cimg_snprintf(command,command._width,"%s \"%s\" \"%s.png\" >/dev/null 2>&1",
+ cimg::imagemagick_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+#else
+ if (use_graphicsmagick) cimg_snprintf(command,command._width,"\"%s convert \"%s\" \"%s.png\"\" >NUL 2>&1",
+ cimg::graphicsmagick_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+ else cimg_snprintf(command,command._width,"\"%s \"%s\" \"%s.png\"\" >NUL 2>&1",
+ cimg::imagemagick_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+#endif
+ cimg::system(command,0);
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ assign();
+
+ // Try to read a single frame gif.
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png",filename_tmp._data);
+ CImg<T> img;
+ try { img.load_png(filename_tmp2); }
+ catch (CImgException&) { }
+ if (img) { img.move_to(*this); std::remove(filename_tmp2); }
+ else { // Try to read animated gif.
+ unsigned int i = 0;
+ for (bool stop_flag = false; !stop_flag; ++i) {
+ if (use_graphicsmagick) cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s.png.%u",filename_tmp._data,i);
+ else cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s-%u.png",filename_tmp._data,i);
+ CImg<T> img;
+ try { img.load_png(filename_tmp2); }
+ catch (CImgException&) { stop_flag = true; }
+ if (img) { img.move_to(*this); std::remove(filename_tmp2); }
+ }
+ }
+ cimg::exception_mode(omode);
+ return *this;
+ }
+
+ //! Load gif file, using ImageMagick or GraphicsMagick's external tools \newinstance.
+ static CImgList<T> get_load_gif_external(const char *const filename) {
+ return CImgList<T>().load_gif_external(filename);
+ }
+
+ //! Load a gzipped list, using external tool 'gunzip'.
+ /**
+ \param filename Filename to read data from.
+ **/
+ CImgList<T>& load_gzip_external(const char *const filename) {
+ if (!filename)
+ throw CImgIOException(_cimglist_instance
+ "load_gzip_external(): Specified filename is (null).",
+ cimglist_instance);
+ std::fclose(cimg::fopen(filename,"rb")); // Check if file exists.
+ CImg<charT> command(1024), filename_tmp(256), body(256);
+ 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(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ } else {
+ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ }
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"",
+ cimg::gunzip_path(),
+ CImg<charT>::string(filename)._system_strescape().data(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data());
+ cimg::system(command);
+ if (!(file = std_fopen(filename_tmp,"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(filename_tmp);
+ std::remove(filename_tmp);
+ return *this;
+ }
+
+ //! Load a gzipped list, using external tool 'gunzip' \newinstance.
+ static CImgList<T> get_load_gzip_external(const char *const filename) {
+ return CImgList<T>().load_gzip_external(filename);
+ }
+
+ //! Load a 3d object from a .OFF file.
+ /**
+ \param filename Filename to read data from.
+ \param[out] primitives At return, contains the list of 3d object primitives.
+ \param[out] colors At return, contains the list of 3d object colors.
+ \return List of 3d object vertices.
+ **/
+ template<typename tf, typename tc>
+ CImgList<T>& load_off(const char *const filename,
+ CImgList<tf>& primitives, CImgList<tc>& colors) {
+ return get_load_off(filename,primitives,colors).move_to(*this);
+ }
+
+ //! Load a 3d object from a .OFF file \newinstance.
+ template<typename tf, typename tc>
+ static CImgList<T> get_load_off(const char *const filename,
+ CImgList<tf>& primitives, CImgList<tc>& colors) {
+ return CImg<T>().load_off(filename,primitives,colors)<'x';
+ }
+
+ //! Load images from a TIFF file.
+ /**
+ \param filename Filename to read data from.
+ \param first_frame Index of first image frame to read.
+ \param last_frame Index of last image frame to read.
+ \param step_frame Step applied between each frame.
+ **/
+ CImgList<T>& load_tiff(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1,
+ float *const voxel_size=0,
+ CImg<charT> *const description=0) {
+ 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
+ cimg::unused(voxel_size,description);
+ 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
+#if cimg_verbosity<3
+ TIFFSetWarningHandler(0);
+ TIFFSetErrorHandler(0);
+#endif
+ 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);
+ cimglist_for(*this,l) _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame,voxel_size,description);
+ TIFFClose(tif);
+ } else throw CImgIOException(_cimglist_instance
+ "load_tiff(): Failed to open file '%s'.",
+ cimglist_instance,
+ filename);
+ return *this;
+#endif
+ }
+
+ //! Load a multi-page TIFF file \newinstance.
+ static CImgList<T> get_load_tiff(const char *const filename,
+ const unsigned int first_frame=0, const unsigned int last_frame=~0U,
+ const unsigned int step_frame=1,
+ float *const voxel_size=0,
+ CImg<charT> *const description=0) {
+ return CImgList<T>().load_tiff(filename,first_frame,last_frame,step_frame,voxel_size,description);
+ }
+
+ //@}
+ //----------------------------------
+ //
+ //! \name Data Output
+ //@{
+ //----------------------------------
+
+ //! Print information about the list on the standard output.
+ /**
+ \param title Label set to the information displayed.
+ \param display_stats Tells if image statistics must be computed and displayed.
+ **/
+ 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?0U:msiz<8*1024*1024?1U:2U;
+ CImg<charT> _title(64);
+ if (!title) cimg_snprintf(_title,_title._width,"CImgList<%s>",pixel_type());
+ std::fprintf(cimg::output(),"%s%s%s%s: %sthis%s = %p, %ssize%s = %u/%u [%u %s], %sdata%s = (CImg<%s>*)%p",
+ cimg::t_magenta,cimg::t_bold,title?title:_title._data,cimg::t_normal,
+ cimg::t_bold,cimg::t_normal,(void*)this,
+ cimg::t_bold,cimg::t_normal,_width,_allocated_width,
+ mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
+ mdisp==0?"b":(mdisp==1?"Kio":"Mio"),
+ cimg::t_bold,cimg::t_normal,pixel_type(),(void*)begin());
+ if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end() - 1));
+ else std::fprintf(cimg::output(),".\n");
+
+ char tmp[16] = { 0 };
+ cimglist_for(*this,ll) {
+ cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll);
+ std::fprintf(cimg::output()," ");
+ _data[ll].print(tmp,display_stats);
+ if (ll==3 && width()>8) { ll = width() - 5; std::fprintf(cimg::output()," ...\n"); }
+ }
+ std::fflush(cimg::output());
+ return *this;
+ }
+
+ //! Display the current CImgList instance in an existing CImgDisplay window (by reference).
+ /**
+ \param disp Reference to an existing CImgDisplay instance, where the current image list will be displayed.
+ \param axis Appending axis. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignmenet.
+ \note This function displays the list images of the current CImgList instance into an existing
+ CImgDisplay window.
+ Images of the list are appended in a single temporarly image for visualization purposes.
+ The function returns immediately.
+ **/
+ const CImgList<T>& display(CImgDisplay &disp, const char axis='x', const float align=0) const {
+ disp.display(*this,axis,align);
+ return *this;
+ }
+
+ //! Display the current CImgList instance in a new display window.
+ /**
+ \param disp Display window.
+ \param display_info Tells if image information are displayed on the standard output.
+ \param axis Alignment axis for images viewing.
+ \param align Apending alignment.
+ \note This function opens a new window with a specific title and displays the list images of the
+ current CImgList instance into it.
+ Images of the list are appended in a single temporarly image for visualization purposes.
+ The function returns when a key is pressed or the display window is closed by the user.
+ **/
+ const CImgList<T>& display(CImgDisplay &disp, const bool display_info,
+ const char axis='x', const float align=0,
+ unsigned int *const XYZ=0, const bool exit_on_anykey=false) const {
+ bool is_exit = false;
+ return _display(disp,0,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit);
+ }
+
+ //! Display the current CImgList instance in a new display window.
+ /**
+ \param title Title of the opening display window.
+ \param display_info Tells if list information must be written on standard output.
+ \param axis Appending axis. Can be <tt>{ 'x' | 'y' | 'z' | 'c' }</tt>.
+ \param align Appending alignment.
+ **/
+ const CImgList<T>& display(const char *const title=0, const bool display_info=true,
+ const char axis='x', const float align=0,
+ unsigned int *const XYZ=0, const bool exit_on_anykey=false) const {
+ CImgDisplay disp;
+ bool is_exit = false;
+ return _display(disp,title,0,display_info,axis,align,XYZ,exit_on_anykey,0,true,is_exit);
+ }
+
+ const CImgList<T>& _display(CImgDisplay &disp, const char *const title, const CImgList<charT> *const titles,
+ const bool display_info, const char axis, const float align, unsigned int *const XYZ,
+ const bool exit_on_anykey, const unsigned int orig, const bool is_first_call,
+ bool &is_exit) const {
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "display(): Empty instance.",
+ cimglist_instance);
+ if (!disp) {
+ if (axis=='x') {
+ unsigned int sum_width = 0, max_height = 0;
+ cimglist_for(*this,l) {
+ const CImg<T> &img = _data[l];
+ const unsigned int
+ w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
+ h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
+ sum_width+=w;
+ if (h>max_height) max_height = h;
+ }
+ disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:titles?titles->__display()._data:0,1);
+ } else {
+ unsigned int max_width = 0, sum_height = 0;
+ cimglist_for(*this,l) {
+ const CImg<T> &img = _data[l];
+ const unsigned int
+ w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
+ h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
+ if (w>max_width) max_width = w;
+ sum_height+=h;
+ }
+ disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:titles?titles->__display()._data:0,1);
+ }
+ if (!title && !titles) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width);
+ } else if (title) disp.set_title("%s",title);
+ else if (titles) disp.set_title("%s",titles->__display()._data);
+ const CImg<char> dtitle = CImg<char>::string(disp.title());
+ if (display_info) print(disp.title());
+ disp.show().flush();
+
+ if (_width==1) {
+ const unsigned int dw = disp._width, dh = disp._height;
+ if (!is_first_call)
+ disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false);
+ disp.set_title("%s (%ux%ux%ux%u)",
+ dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum);
+ _data[0]._display(disp,0,false,XYZ,exit_on_anykey,!is_first_call);
+ if (disp.key()) is_exit = true;
+ disp.resize(cimg_fitscreen(dw,dh,1),false).set_title("%s",dtitle.data());
+ } else {
+ bool disp_resize = !is_first_call;
+ while (!disp.is_closed() && !is_exit) {
+ const CImg<intT> s = _select(disp,0,true,axis,align,exit_on_anykey,orig,disp_resize,!is_first_call,true);
+ disp_resize = true;
+ if (s[0]<0 && !disp.wheel()) { // No selections done.
+ if (disp.button()&2) { disp.flush(); break; }
+ is_exit = true;
+ } else if (disp.wheel()) { // Zoom in/out.
+ const int wheel = disp.wheel();
+ disp.set_wheel();
+ if (!is_first_call && wheel<0) break;
+ if (wheel>0 && _width>=4) {
+ const unsigned int
+ delta = std::max(1U,(unsigned int)cimg::round(0.3*_width)),
+ ind0 = (unsigned int)std::max(0,s[0] - (int)delta),
+ ind1 = (unsigned int)std::min(width() - 1,s[0] + (int)delta);
+ if ((ind0!=0 || ind1!=_width - 1) && ind1 - ind0>=3) {
+ const CImgList<T> sublist = get_shared_images(ind0,ind1);
+ CImgList<charT> t_sublist;
+ if (titles) t_sublist = titles->get_shared_images(ind0,ind1);
+ sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey,
+ orig + ind0,false,is_exit);
+ }
+ }
+ } else if (s[0]!=0 || s[1]!=width() - 1) {
+ const CImgList<T> sublist = get_shared_images(s[0],s[1]);
+ CImgList<charT> t_sublist;
+ if (titles) t_sublist = titles->get_shared_images(s[0],s[1]);
+ sublist._display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey,
+ orig + s[0],false,is_exit);
+ }
+ disp.set_title("%s",dtitle.data());
+ }
+ }
+ return *this;
+ }
+
+ // [internal] Return string to describe display title.
+ CImg<charT> __display() const {
+ CImg<charT> res, str;
+ cimglist_for(*this,l) {
+ CImg<charT>::string(_data[l]).move_to(str);
+ if (l!=width() - 1) {
+ str.resize(str._width + 1,1,1,1,0);
+ str[str._width - 2] = ',';
+ str[str._width - 1] = ' ';
+ }
+ res.append(str,'x');
+ }
+ if (!res) return CImg<charT>(1,1,1,1,0).move_to(res);
+ cimg::strellipsize(res,128,false);
+ if (_width>1) {
+ const unsigned int l = (unsigned int)std::strlen(res);
+ if (res._width<=l + 16) res.resize(l + 16,1,1,1,0);
+ cimg_snprintf(res._data + l,16," (#%u)",_width);
+ }
+ return res;
+ }
+
+ //! Save list into a file.
+ /**
+ \param filename Filename to write data to.
+ \param number When positive, represents an index added to the filename. Otherwise, no number is added.
+ \param digits Number of digits used for adding the number to the filename.
+ **/
+ const CImgList<T>& save(const char *const filename, const int number=-1, const unsigned int digits=6) 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 bool is_stdout = *filename=='-' && (!filename[1] || filename[1]=='.');
+ const char *const ext = cimg::split_filename(filename);
+ CImg<charT> nfilename(1024);
+ const char *const fn = is_stdout?filename:number>=0?cimg::number_filename(filename,number,digits,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,"mp4") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"ogg") ||
+ !cimg::strcasecmp(ext,"ogv") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) return save_video(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,is_stdout?-1:l); if (is_stdout) std::fputc(EOF,cimg::_stdout()); }
+ }
+ return *this;
+ }
+
+ //! Tell if an image list can be saved as one single file.
+ /**
+ \param filename Filename, as a C-string.
+ \return \c true if the file format supports multiple images, \c false otherwise.
+ **/
+ 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,"mp4") ||
+ !cimg::strcasecmp(ext,"mkv") ||
+ !cimg::strcasecmp(ext,"mpe") ||
+ !cimg::strcasecmp(ext,"movie") ||
+ !cimg::strcasecmp(ext,"ogm") ||
+ !cimg::strcasecmp(ext,"ogg") ||
+ !cimg::strcasecmp(ext,"ogv") ||
+ !cimg::strcasecmp(ext,"qt") ||
+ !cimg::strcasecmp(ext,"rm") ||
+ !cimg::strcasecmp(ext,"vob") ||
+ !cimg::strcasecmp(ext,"wmv") ||
+ !cimg::strcasecmp(ext,"xvid") ||
+ !cimg::strcasecmp(ext,"mpeg")) return true;
+ return false;
+ }
+
+ //! Save image sequence as a GIF animated file.
+ /**
+ \param filename Filename to write data to.
+ \param fps Number of desired frames per second.
+ \param nb_loops Number of loops (\c 0 for infinite looping).
+ **/
+ const CImgList<T>& save_gif_external(const char *const filename, const float fps=25,
+ const unsigned int nb_loops=0) {
+ CImg<charT> command(1024), filename_tmp(256), filename_tmp2(256);
+ CImgList<charT> filenames;
+ std::FILE *file = 0;
+
+#ifdef cimg_use_png
+#define _cimg_save_gif_ext "png"
+#else
+#define _cimg_save_gif_ext "ppm"
+#endif
+
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_ext,filename_tmp._data);
+ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimglist_for(*this,l) {
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u." _cimg_save_gif_ext,filename_tmp._data,l + 1);
+ CImg<charT>::string(filename_tmp2).move_to(filenames);
+ if (_data[l]._depth>1 || _data[l]._spectrum!=3) _data[l].get_resize(-100,-100,1,3).save(filename_tmp2);
+ else _data[l].save(filename_tmp2);
+ }
+
+#if cimg_OS!=2
+ cimg_snprintf(command,command._width,"%s -delay %u -loop %u",
+ cimg::imagemagick_path(),(unsigned int)std::max(0.0f,cimg::round(100/fps)),nb_loops);
+ CImg<ucharT>::string(command).move_to(filenames,0);
+ cimg_snprintf(command,command._width,"\"%s\" >/dev/null 2>&1",
+ CImg<charT>::string(filename)._system_strescape().data());
+ CImg<ucharT>::string(command).move_to(filenames);
+#else
+ cimg_snprintf(command,command._width,"\"%s -delay %u -loop %u",
+ cimg::imagemagick_path(),(unsigned int)std::max(0.0f,cimg::round(100/fps)),nb_loops);
+ CImg<ucharT>::string(command).move_to(filenames,0);
+ cimg_snprintf(command,command._width,"\"%s\"\" >NUL 2>&1",
+ CImg<charT>::string(filename)._system_strescape().data());
+ CImg<ucharT>::string(command).move_to(filenames);
+#endif
+ CImg<charT> _command = filenames>'x';
+ cimg_for(_command,p,char) if (!*p) *p = ' ';
+ _command.back() = 0;
+
+ cimg::system(_command);
+ file = std_fopen(filename,"rb");
+ if (!file)
+ throw CImgIOException(_cimglist_instance
+ "save_gif_external(): Failed to save file '%s' with external command 'convert'.",
+ cimglist_instance,
+ filename);
+ else cimg::fclose(file);
+ cimglist_for_in(*this,1,filenames._width - 1,l) std::remove(filenames[l]);
+ return *this;
+ }
+
+ const CImgList<T>& _save_yuv(std::FILE *const file, const char *const filename, const bool is_rgb) const {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimglist_instance
+ "save_yuv(): Specified filename is (null).",
+ cimglist_instance);
+ if (is_empty()) { cimg::fempty(file,filename); return *this; }
+ 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 (is_rgb) YCbCr.RGBtoYCbCr();
+ cimg::fwrite(YCbCr._data,(size_t)YCbCr._width*YCbCr._height,nfile);
+ cimg::fwrite(YCbCr.get_resize(YCbCr._width/2, YCbCr._height/2,1,3,3).data(0,0,0,1),
+ (size_t)YCbCr._width*YCbCr._height/2,nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save list as a YUV image sequence file.
+ /**
+ \param filename Filename to write data to.
+ \param is_rgb Tells if the RGB to YUV conversion must be done for saving.
+ **/
+ const CImgList<T>& save_yuv(const char *const filename=0, const bool is_rgb=true) const {
+ return _save_yuv(0,filename,is_rgb);
+ }
+
+ //! Save image sequence into a YUV file.
+ /**
+ \param file File to write data to.
+ \param is_rgb Tells if the RGB to YUV conversion must be done for saving.
+ **/
+ const CImgList<T>& save_yuv(std::FILE *const file, const bool is_rgb=true) const {
+ return _save_yuv(file,0,is_rgb);
+ }
+
+ const CImgList<T>& _save_cimg(std::FILE *const file, const char *const filename, const bool is_compressed) const {
+ if (!file && !filename)
+ throw CImgArgumentException(_cimglist_instance
+ "save_cimg(): Specified filename is (null).",
+ cimglist_instance);
+#ifndef cimg_use_zlib
+ if (is_compressed)
+ 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 failed_to_compress = true;
+ if (is_compressed) {
+#ifdef cimg_use_zlib
+ const ulongT siz = sizeof(T)*ref.size();
+ uLongf 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*)");
+ else {
+ std::fprintf(nfile," #%lu\n",csiz);
+ cimg::fwrite(cbuf,csiz,nfile);
+ delete[] cbuf;
+ failed_to_compress = false;
+ }
+#endif
+ }
+ if (failed_to_compress) { // Write in a non-compressed way.
+ std::fputc('\n',nfile);
+ cimg::fwrite(ref._data,ref.size(),nfile);
+ }
+ } else std::fputc('\n',nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Save list into a .cimg file.
+ /**
+ \param filename Filename to write data to.
+ \param is_compressed Tells if data compression must be enabled.
+ **/
+ const CImgList<T>& save_cimg(const char *const filename, const bool is_compressed=false) const {
+ return _save_cimg(0,filename,is_compressed);
+ }
+
+ //! Save list into a .cimg file.
+ /**
+ \param file File to write data to.
+ \param is_compressed Tells if data compression must be enabled.
+ **/
+ const CImgList<T>& save_cimg(std::FILE *file, const bool is_compressed=false) const {
+ return _save_cimg(file,0,is_compressed);
+ }
+
+ 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 (cimg_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) cimg::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) cimg::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) cimg::fseek(nfile,skipzb,SEEK_CUR); \
+ for (unsigned int z = 1 + nz1 - z0; z; --z) { \
+ const unsigned int skipyb = y0*W*sizeof(Tss); \
+ if (skipyb) cimg::fseek(nfile,skipyb,SEEK_CUR); \
+ for (unsigned int y = 1 + ny1 - y0; y; --y) { \
+ const unsigned int skipxb = x0*sizeof(Tss); \
+ if (skipxb) cimg::fseek(nfile,skipxb,SEEK_CUR); \
+ raw.assign(ptrs, raw._width); \
+ ptrs+=img._width; \
+ if (endian) cimg::invert_endianness(raw._data,raw._width); \
+ cimg::fwrite(raw._data,raw._width,nfile); \
+ const unsigned int skipxe = (W - 1 - nx1)*sizeof(Tss); \
+ if (skipxe) cimg::fseek(nfile,skipxe,SEEK_CUR); \
+ } \
+ const unsigned int skipye = (H - 1 - ny1)*W*sizeof(Tss); \
+ if (skipye) cimg::fseek(nfile,skipye,SEEK_CUR); \
+ } \
+ const unsigned int skipze = (D - 1 - nz1)*W*H*sizeof(Tss); \
+ if (skipze) cimg::fseek(nfile,skipze,SEEK_CUR); \
+ } \
+ const unsigned int skipve = (C - 1 - nc1)*W*H*D*sizeof(Tss); \
+ if (skipve) cimg::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();
+ CImg<charT> tmp(256), str_pixeltype(256), str_endian(256);
+ *tmp = *str_pixeltype = *str_endian = 0;
+ unsigned int j, N, W, H, D, C;
+ int i, err;
+ j = 0; while ((i=std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = 0;
+ err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype._data,str_endian._data);
+ 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 = std::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_int64",uint64T);
+ _cimg_save_cimg_case("uint64",uint64T);
+ _cimg_save_cimg_case("int64",int64T);
+ _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._data);
+ }
+ if (!file) cimg::fclose(nfile);
+ return *this;
+ }
+
+ //! Insert the image instance into into an existing .cimg file, at specified coordinates.
+ /**
+ \param filename Filename to write data to.
+ \param n0 Starting index of images to write.
+ \param x0 Starting X-coordinates of image regions to write.
+ \param y0 Starting Y-coordinates of image regions to write.
+ \param z0 Starting Z-coordinates of image regions to write.
+ \param c0 Starting C-coordinates of image regions to write.
+ **/
+ const CImgList<T>& save_cimg(const char *const filename,
+ const unsigned int n0,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0) const {
+ return _save_cimg(0,filename,n0,x0,y0,z0,c0);
+ }
+
+ //! Insert the image instance into into an existing .cimg file, at specified coordinates.
+ /**
+ \param file File to write data to.
+ \param n0 Starting index of images to write.
+ \param x0 Starting X-coordinates of image regions to write.
+ \param y0 Starting Y-coordinates of image regions to write.
+ \param z0 Starting Z-coordinates of image regions to write.
+ \param c0 Starting C-coordinates of image regions to write.
+ **/
+ const CImgList<T>& save_cimg(std::FILE *const file,
+ const unsigned int n0,
+ const unsigned int x0, const unsigned int y0,
+ const unsigned int z0, const unsigned int c0) const {
+ return _save_cimg(file,0,n0,x0,y0,z0,c0);
+ }
+
+ static void _save_empty_cimg(std::FILE *const file, const char *const filename,
+ const unsigned int nb,
+ const unsigned int dx, const unsigned int dy,
+ const unsigned int dz, const unsigned int dc) {
+ std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
+ const ulongT siz = (ulongT)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 (ulongT off = siz; off; --off) std::fputc(0,nfile);
+ }
+ if (!file) cimg::fclose(nfile);
+ }
+
+ //! Save empty (non-compressed) .cimg file with specified dimensions.
+ /**
+ \param filename Filename to write data to.
+ \param nb Number of images to write.
+ \param dx Width of images in the written file.
+ \param dy Height of images in the written file.
+ \param dz Depth of images in the written file.
+ \param dc Spectrum of images in the written file.
+ **/
+ static void save_empty_cimg(const char *const filename,
+ const unsigned int nb,
+ const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dc=1) {
+ return _save_empty_cimg(0,filename,nb,dx,dy,dz,dc);
+ }
+
+ //! Save empty .cimg file with specified dimensions.
+ /**
+ \param file File to write data to.
+ \param nb Number of images to write.
+ \param dx Width of images in the written file.
+ \param dy Height of images in the written file.
+ \param dz Depth of images in the written file.
+ \param dc Spectrum of images in the written file.
+ **/
+ static void save_empty_cimg(std::FILE *const file,
+ const unsigned int nb,
+ const unsigned int dx, const unsigned int dy=1,
+ const unsigned int dz=1, const unsigned int dc=1) {
+ return _save_empty_cimg(file,0,nb,dx,dy,dz,dc);
+ }
+
+ //! Save list as a TIFF file.
+ /**
+ \param filename Filename to write data to.
+ \param compression_type Compression mode used to write data.
+ **/
+ const CImgList<T>& save_tiff(const char *const filename, const unsigned int compression_type=0,
+ const float *const voxel_size=0, const char *const description=0,
+ const bool use_bigtiff=true) const {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "save_tiff(): Specified filename is (null).",
+ cimglist_instance);
+ if (is_empty()) { cimg::fempty(0,filename); return *this; }
+
+#ifndef cimg_use_tiff
+ if (_width==1) _data[0].save_tiff(filename,compression_type,voxel_size,description,use_bigtiff);
+ else cimglist_for(*this,l) {
+ CImg<charT> nfilename(1024);
+ cimg::number_filename(filename,l,6,nfilename);
+ _data[l].save_tiff(nfilename,compression_type,voxel_size,description,use_bigtiff);
+ }
+#else
+ ulongT siz = 0;
+ cimglist_for(*this,l) siz+=_data[l].size();
+ const bool _use_bigtiff = use_bigtiff && sizeof(siz)>=8 && siz*sizeof(T)>=1UL<<31; // No bigtiff for small images.
+ TIFF *tif = TIFFOpen(filename,_use_bigtiff?"w8":"w4");
+ if (tif) {
+ for (unsigned int dir = 0, l = 0; l<_width; ++l) {
+ const CImg<T>& img = (*this)[l];
+ cimg_forZ(img,z) img._save_tiff(tif,dir++,z,compression_type,voxel_size,description);
+ }
+ TIFFClose(tif);
+ } else
+ throw CImgIOException(_cimglist_instance
+ "save_tiff(): Failed to open stream for file '%s'.",
+ cimglist_instance,
+ filename);
+#endif
+ return *this;
+ }
+
+ //! Save list as a gzipped file, using external tool 'gzip'.
+ /**
+ \param filename Filename to write data to.
+ **/
+ const CImgList<T>& save_gzip_external(const char *const filename) const {
+ if (!filename)
+ throw CImgIOException(_cimglist_instance
+ "save_gzip_external(): Specified filename is (null).",
+ cimglist_instance);
+ CImg<charT> command(1024), filename_tmp(256), body(256);
+ 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(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ } else {
+ if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
+ else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ }
+ if ((file=std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+
+ if (is_saveable(body)) {
+ save(filename_tmp);
+ cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"",
+ cimg::gzip_path(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data(),
+ CImg<charT>::string(filename)._system_strescape().data());
+ 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(filename_tmp);
+ } else {
+ CImg<charT> nfilename(1024);
+ cimglist_for(*this,l) {
+ cimg::number_filename(body,l,6,nfilename);
+ if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext);
+ _data[l].save_gzip_external(nfilename);
+ }
+ }
+ return *this;
+ }
+
+ //! Save image sequence, using the OpenCV library.
+ /**
+ \param filename Filename to write data to.
+ \param fps Number of frames per second.
+ \param codec Type of compression (See http://www.fourcc.org/codecs.php to see available codecs).
+ \param keep_open Tells if the video writer associated to the specified filename
+ must be kept open or not (to allow frames to be added in the same file afterwards).
+ **/
+ const CImgList<T>& save_video(const char *const filename, const unsigned int fps=25,
+ const char *codec=0, const bool keep_open=false) const {
+#ifndef cimg_use_opencv
+ cimg::unused(codec,keep_open);
+ return save_ffmpeg_external(filename,fps);
+#else
+ static CvVideoWriter *writers[32] = { 0 };
+ static CImgList<charT> filenames(32);
+ static CImg<intT> sizes(32,2,1,1,0);
+ static int last_used_index = -1;
+
+ // Detect if a video writer already exists for the specified filename.
+ cimg::mutex(9);
+ int index = -1;
+ if (filename) {
+ if (last_used_index>=0 && !std::strcmp(filename,filenames[last_used_index])) {
+ index = last_used_index;
+ } else cimglist_for(filenames,l) if (filenames[l] && !std::strcmp(filename,filenames[l])) {
+ index = l; break;
+ }
+ } else index = last_used_index;
+ cimg::mutex(9,0);
+
+ // Find empty slot for capturing video stream.
+ if (index<0) {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "save_video(): No already open video writer found. You must specify a "
+ "non-(null) filename argument for the first call.",
+ cimglist_instance);
+ else { cimg::mutex(9); cimglist_for(filenames,l) if (!filenames[l]) { index = l; break; } cimg::mutex(9,0); }
+ if (index<0)
+ throw CImgIOException(_cimglist_instance
+ "save_video(): File '%s', no video writer slots available. "
+ "You have to release some of your previously opened videos.",
+ cimglist_instance,filename);
+ if (is_empty())
+ throw CImgInstanceException(_cimglist_instance
+ "save_video(): Instance list is empty.",
+ cimglist_instance);
+ const unsigned int W = _data?_data[0]._width:0, H = _data?_data[0]._height:0;
+ if (!W || !H)
+ throw CImgInstanceException(_cimglist_instance
+ "save_video(): Frame [0] is an empty image.",
+ cimglist_instance);
+
+#define _cimg_docase(x) ((x)>='a'&&(x)<='z'?(x) + 'A' - 'a':(x))
+ const char
+ *const _codec = codec && *codec?codec:"mp4v",
+ codec0 = _cimg_docase(_codec[0]),
+ codec1 = _codec[0]?_cimg_docase(_codec[1]):0,
+ codec2 = _codec[1]?_cimg_docase(_codec[2]):0,
+ codec3 = _codec[2]?_cimg_docase(_codec[3]):0;
+ cimg::mutex(9);
+ writers[index] = cvCreateVideoWriter(filename,CV_FOURCC(codec0,codec1,codec2,codec3),
+ fps,cvSize(W,H));
+ CImg<charT>::string(filename).move_to(filenames[index]);
+ sizes(index,0) = W; sizes(index,1) = H;
+ cimg::mutex(9,0);
+ if (!writers[index])
+ throw CImgIOException(_cimglist_instance
+ "save_video(): File '%s', unable to initialize video writer with codec '%c%c%c%c'.",
+ cimglist_instance,filename,
+ codec0,codec1,codec2,codec3);
+ }
+
+ if (!is_empty()) {
+ const unsigned int W = sizes(index,0), H = sizes(index,1);
+ cimg::mutex(9);
+ IplImage *ipl = cvCreateImage(cvSize(W,H),8,3);
+ cimglist_for(*this,l) {
+ CImg<T> &src = _data[l];
+ if (src.is_empty())
+ cimg::warn(_cimglist_instance
+ "save_video(): Skip empty frame %d for file '%s'.",
+ cimglist_instance,l,filename);
+ if (src._depth>1 || src._spectrum>3)
+ cimg::warn(_cimglist_instance
+ "save_video(): Frame %u has incompatible dimension (%u,%u,%u,%u). "
+ "Some image data may be ignored when writing frame into video file '%s'.",
+ cimglist_instance,l,src._width,src._height,src._depth,src._spectrum,filename);
+ if (src._width==W && src._height==H && src._spectrum==3) {
+ const T *ptr_r = src.data(0,0,0,0), *ptr_g = src.data(0,0,0,1), *ptr_b = src.data(0,0,0,2);
+ char *ptrd = ipl->imageData;
+ cimg_forXY(src,x,y) {
+ *(ptrd++) = (char)*(ptr_b++); *(ptrd++) = (char)*(ptr_g++); *(ptrd++) = (char)*(ptr_r++);
+ }
+ } else {
+ CImg<unsigned char> _src(src,false);
+ _src.channels(0,std::min(_src._spectrum - 1,2U)).resize(W,H);
+ _src.resize(W,H,1,3,_src._spectrum==1);
+ const unsigned char *ptr_r = _src.data(0,0,0,0), *ptr_g = _src.data(0,0,0,1), *ptr_b = _src.data(0,0,0,2);
+ char *ptrd = ipl->imageData;
+ cimg_forXY(_src,x,y) {
+ *(ptrd++) = (char)*(ptr_b++); *(ptrd++) = (char)*(ptr_g++); *(ptrd++) = (char)*(ptr_r++);
+ }
+ }
+ cvWriteFrame(writers[index],ipl);
+ }
+ cvReleaseImage(&ipl);
+ cimg::mutex(9,0);
+ }
+
+ cimg::mutex(9);
+ if (!keep_open) {
+ cvReleaseVideoWriter(&writers[index]);
+ writers[index] = 0;
+ filenames[index].assign();
+ sizes(index,0) = sizes(index,1) = 0;
+ last_used_index = -1;
+ } else last_used_index = index;
+ cimg::mutex(9,0);
+
+ return *this;
+#endif
+ }
+
+ //! Save image sequence, using the external tool 'ffmpeg'.
+ /**
+ \param filename Filename to write data to.
+ \param fps Number of frames per second.
+ \param codec Type of compression.
+ \param bitrate Output bitrate
+ **/
+ const CImgList<T>& save_ffmpeg_external(const char *const filename, const unsigned int fps=25,
+ const char *const codec=0, const unsigned int bitrate=2048) const {
+ if (!filename)
+ throw CImgArgumentException(_cimglist_instance
+ "save_ffmpeg_external(): Specified filename is (null).",
+ cimglist_instance);
+ if (is_empty()) { cimg::fempty(0,filename); return *this; }
+
+ const char
+ *const ext = cimg::split_filename(filename),
+ *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video";
+
+ CImg<charT> command(1024), filename_tmp(256), filename_tmp2(256);
+ CImgList<charT> filenames;
+ std::FILE *file = 0;
+ cimglist_for(*this,l) if (!_data[l].is_sameXYZ(_data[0]))
+ throw CImgInstanceException(_cimglist_instance
+ "save_ffmpeg_external(): Invalid instance dimensions for file '%s'.",
+ cimglist_instance,
+ filename);
+ do {
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001.ppm",filename_tmp._data);
+ if ((file=std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+ cimglist_for(*this,l) {
+ cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,l + 1);
+ CImg<charT>::string(filename_tmp2).move_to(filenames);
+ if (_data[l]._depth>1 || _data[l]._spectrum!=3) _data[l].get_resize(-100,-100,1,3).save_pnm(filename_tmp2);
+ else _data[l].save_pnm(filename_tmp2);
+ }
+#if cimg_OS!=2
+ cimg_snprintf(command,command._width,"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\" >/dev/null 2>&1",
+ cimg::ffmpeg_path(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data(),
+ _codec,bitrate,fps,
+ CImg<charT>::string(filename)._system_strescape().data());
+#else
+ cimg_snprintf(command,command._width,"\"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\"\" >NUL 2>&1",
+ cimg::ffmpeg_path(),
+ CImg<charT>::string(filename_tmp)._system_strescape().data(),
+ _codec,bitrate,fps,
+ CImg<charT>::string(filename)._system_strescape().data());
+#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,l) std::remove(filenames[l]);
+ return *this;
+ }
+
+ //! Serialize a CImgList<T> instance into a raw CImg<unsigned char> buffer.
+ /**
+ \param is_compressed tells if zlib compression must be used for serialization
+ (this requires 'cimg_use_zlib' been enabled).
+ **/
+ CImg<ucharT> get_serialize(const bool is_compressed=false) const {
+#ifndef cimg_use_zlib
+ if (is_compressed)
+ cimg::warn(_cimglist_instance
+ "get_serialize(): Unable to compress data unless zlib is enabled, "
+ "storing them uncompressed.",
+ cimglist_instance);
+#endif
+ CImgList<ucharT> stream;
+ CImg<charT> tmpstr(128);
+ const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little";
+ if (std::strstr(ptype,"unsigned")==ptype)
+ cimg_snprintf(tmpstr,tmpstr._width,"%u unsigned_%s %s_endian\n",_width,ptype + 9,etype);
+ else
+ cimg_snprintf(tmpstr,tmpstr._width,"%u %s %s_endian\n",_width,ptype,etype);
+ CImg<ucharT>::string(tmpstr,false).move_to(stream);
+ cimglist_for(*this,l) {
+ const CImg<T>& img = _data[l];
+ cimg_snprintf(tmpstr,tmpstr._width,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum);
+ CImg<ucharT>::string(tmpstr,false).move_to(stream);
+ 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 failed_to_compress = true;
+ if (is_compressed) {
+#ifdef cimg_use_zlib
+ const ulongT siz = sizeof(T)*ref.size();
+ uLongf csiz = (ulongT)compressBound(siz);
+ Bytef *const cbuf = new Bytef[csiz];
+ if (compress(cbuf,&csiz,(Bytef*)ref._data,siz))
+ cimg::warn(_cimglist_instance
+ "get_serialize(): Failed to save compressed data, saving them uncompressed.",
+ cimglist_instance);
+ else {
+ cimg_snprintf(tmpstr,tmpstr._width," #%lu\n",csiz);
+ CImg<ucharT>::string(tmpstr,false).move_to(stream);
+ CImg<ucharT>(cbuf,csiz).move_to(stream);
+ delete[] cbuf;
+ failed_to_compress = false;
+ }
+#endif
+ }
+ if (failed_to_compress) { // Write in a non-compressed way.
+ CImg<charT>::string("\n",false).move_to(stream);
+ stream.insert(1);
+ stream.back().assign((unsigned char*)ref._data,ref.size()*sizeof(T),1,1,1,true);
+ }
+ } else CImg<charT>::string("\n",false).move_to(stream);
+ }
+ cimglist_apply(stream,unroll)('y');
+ return stream>'y';
+ }
+
+ //! Unserialize a CImg<unsigned char> serialized buffer into a CImgList<T> list.
+ template<typename t>
+ static CImgList<T> get_unserialize(const CImg<t>& buffer) {
+#ifdef cimg_use_zlib
+#define _cimgz_unserialize_case(Tss) { \
+ Bytef *cbuf = (Bytef*)stream; \
+ if (sizeof(t)!=1 || cimg::type<t>::string()==cimg::type<bool>::string()) { \
+ cbuf = new Bytef[csiz]; Bytef *_cbuf = cbuf; \
+ for (ulongT i = 0; i<csiz; ++i) *(_cbuf++) = (Bytef)*(stream++); \
+ is_bytef = false; \
+ } else { stream+=csiz; is_bytef = true; } \
+ CImg<Tss> raw(W,H,D,C); \
+ uLongf destlen = raw.size()*sizeof(Tss); \
+ uncompress((Bytef*)raw._data,&destlen,cbuf,csiz); \
+ if (!is_bytef) delete[] cbuf; \
+ if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \
+ raw.move_to(img); \
+ }
+#else
+#define _cimgz_unserialize_case(Tss) \
+ throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unable to unserialize compressed data " \
+ "unless zlib is enabled.", \
+ pixel_type());
+#endif
+
+#define _cimg_unserialize_case(Ts,Tss) \
+ if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
+ for (unsigned int l = 0; l<N; ++l) { \
+ j = 0; while ((i=(int)*stream)!='\n' && stream<estream && j<255) { ++stream; tmp[j++] = (char)i; } \
+ ++stream; tmp[j] = 0; \
+ W = H = D = C = 0; csiz = 0; \
+ if ((err = cimg_sscanf(tmp,"%u %u %u %u #" cimg_fuint64,&W,&H,&D,&C,&csiz))<4) \
+ throw CImgArgumentException("CImgList<%s>::unserialize(): Invalid specified size (%u,%u,%u,%u) for " \
+ "image #%u in serialized buffer.", \
+ pixel_type(),W,H,D,C,l); \
+ if (W*H*D*C>0) { \
+ CImg<T> &img = res._data[l]; \
+ if (err==5) _cimgz_unserialize_case(Tss) \
+ else { \
+ if (sizeof(t)!=1) { \
+ CImg<ucharT> raw(W*sizeof(Tss),H,D,C); \
+ cimg_for(raw,p,unsigned char) *p = (unsigned char)*(stream++); \
+ img.assign((Tss*)raw._data,W,H,D,C); \
+ } else img.assign((Tss*)stream,W,H,D,C); \
+ if (endian!=cimg::endianness()) cimg::invert_endianness(img._data,img.size()); \
+ } \
+ } \
+ } \
+ loaded = true; \
+ }
+
+ if (buffer.is_empty())
+ throw CImgArgumentException("CImgList<%s>::get_unserialize(): Specified serialized buffer is (null).",
+ pixel_type());
+ CImgList<T> res;
+ const t *stream = buffer._data, *const estream = buffer._data + buffer.size();
+ bool loaded = false, endian = cimg::endianness(), is_bytef = false;
+ CImg<charT> tmp(256), str_pixeltype(256), str_endian(256);
+ *tmp = *str_pixeltype = *str_endian = 0;
+ unsigned int j, N = 0, W, H, D, C;
+ uint64T csiz;
+ int i, err;
+ cimg::unused(is_bytef);
+ do {
+ j = 0; while ((i=(int)*stream)!='\n' && stream<estream && j<255) { ++stream; tmp[j++] = (char)i; }
+ ++stream; tmp[j] = 0;
+ } while (*tmp=='#' && stream<estream);
+ err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z64_]%*c%255[sA-Za-z_ ]",
+ &N,str_pixeltype._data,str_endian._data);
+ if (err<2)
+ throw CImgArgumentException("CImgList<%s>::get_unserialize(): CImg header not found in serialized buffer.",
+ pixel_type());
+ if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
+ else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
+ res.assign(N);
+ _cimg_unserialize_case("bool",bool);
+ _cimg_unserialize_case("unsigned_char",unsigned char);
+ _cimg_unserialize_case("uchar",unsigned char);
+ _cimg_unserialize_case("char",char);
+ _cimg_unserialize_case("unsigned_short",unsigned short);
+ _cimg_unserialize_case("ushort",unsigned short);
+ _cimg_unserialize_case("short",short);
+ _cimg_unserialize_case("unsigned_int",unsigned int);
+ _cimg_unserialize_case("uint",unsigned int);
+ _cimg_unserialize_case("int",int);
+ _cimg_unserialize_case("unsigned_int64",uint64T);
+ _cimg_unserialize_case("uint64",uint64T);
+ _cimg_unserialize_case("int64",int64T);
+ _cimg_unserialize_case("float",float);
+ _cimg_unserialize_case("double",double);
+ if (!loaded)
+ throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unsupported pixel type '%s' defined "
+ "in serialized buffer.",
+ pixel_type(),str_pixeltype._data);
+ return res;
+ }
+
+ //@}
+ //----------------------------------
+ //
+ //! \name Others
+ //@{
+ //----------------------------------
+
+ //! Crop font along the X-axis.
+ /**
+ **/
+ CImgList<T>& crop_font() {
+ return get_crop_font().move_to(*this);
+ }
+
+ //! Crop font along the X-axis \newinstance.
+ /**
+ **/
+ 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 13,23,53,103).
+ \param is_variable_width Decide if the font has a variable (\c true) or fixed (\c false) width.
+ **/
+ static const CImgList<ucharT>& font(const unsigned int font_height, const bool is_variable_width=true) {
+ if (!font_height) return CImgList<ucharT>::const_empty();
+ cimg::mutex(11);
+
+ // Decompress nearest base font data if needed.
+ static const char *data_fonts[] = { cimg::data_font12x13, cimg::data_font20x23, cimg::data_font47x53, 0 };
+ static const unsigned int data_widths[] = { 12,20,47,90 }, data_heights[] = { 13,23,53,103 },
+ data_Ms[] = { 86,79,57,47 };
+ const unsigned int data_ind = font_height<=13U?0U:font_height<=23U?1U:font_height<=53U?2U:3U;
+ static CImg<ucharT> base_fonts[4];
+ CImg<ucharT> &base_font = base_fonts[data_ind];
+ if (!base_font) {
+ const unsigned int w = data_widths[data_ind], h = data_heights[data_ind], M = data_Ms[data_ind];
+ base_font.assign(256*w,h);
+ const char *data_font = data_fonts[data_ind];
+ unsigned char *ptrd = base_font;
+ const unsigned char *const ptrde = base_font.end();
+
+ // Special case needed for 90x103 to avoid MS compiler limit with big strings.
+ CImg<char> data90x103;
+ if (!data_font) {
+ ((CImg<char>(cimg::_data_font90x103[0],
+ (unsigned int)std::strlen(cimg::_data_font90x103[0]),1,1,1,true),
+ CImg<char>(cimg::_data_font90x103[1],
+ (unsigned int)std::strlen(cimg::_data_font90x103[1]) + 1,1,1,1,true))>'x').
+ move_to(data90x103);
+ data_font = data90x103.data();
+ }
+
+ // Uncompress font data (decode RLE).
+ for (const char *ptrs = data_font; *ptrs; ++ptrs) {
+ const int c = (int)(*ptrs - M - 32), v = c>=0?255:0, n = c>=0?c:-c;
+ if (ptrd + n<=ptrde) { std::memset(ptrd,v,n); ptrd+=n; }
+ else { std::memset(ptrd,v,ptrde - ptrd); break; }
+ }
+ }
+
+ // Find optimal font cache location to return.
+ static CImgList<ucharT> fonts[16];
+ static bool is_variable_widths[16] = { 0 };
+ unsigned int ind = ~0U;
+ for (int i = 0; i<16; ++i)
+ if (!fonts[i] || (is_variable_widths[i]==is_variable_width && font_height==fonts[i][0]._height)) {
+ ind = (unsigned int)i; break; // Found empty slot or cached font.
+ }
+ if (ind==~0U) { // No empty slots nor existing font in cache.
+ fonts->assign();
+ std::memmove(fonts,fonts + 1,15*sizeof(CImgList<ucharT>));
+ std::memmove(is_variable_widths,is_variable_widths + 1,15*sizeof(bool));
+ std::memset(fonts + (ind=15),0,sizeof(CImgList<ucharT>)); // Free a slot in cache for new font.
+ }
+ CImgList<ucharT> &font = fonts[ind];
+
+ // Render requested font.
+ if (!font) {
+ const unsigned int padding_x = font_height<33U?1U:font_height<53U?2U:font_height<103U?3U:4U;
+ is_variable_widths[ind] = is_variable_width;
+ font = base_font.get_split('x',256);
+ if (font_height!=font[0]._height)
+ cimglist_for(font,l)
+ font[l].resize(std::max(1U,font[l]._width*font_height/font[l]._height),font_height,-100,-100,
+ font[0]._height>font_height?2:5);
+ if (is_variable_width) font.crop_font();
+ cimglist_for(font,l) font[l].resize(font[l]._width + padding_x,-100,1,1,0,0,0.5);
+ font.insert(256,0);
+ cimglist_for_in(font,0,255,l) font[l].assign(font[l + 256]._width,font[l + 256]._height,1,3,1);
+ }
+ cimg::mutex(11,0);
+ return font;
+ }
+
+ //! Compute a 1d Fast Fourier Transform, along specified axis.
+ /**
+ \param axis Axis along which the Fourier transform is computed.
+ \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed.
+ **/
+ CImgList<T>& FFT(const char axis, const bool invert=false) {
+ if (is_empty()) return *this;
+ if (_width==1) insert(1);
+ if (_width>2)
+ cimg::warn(_cimglist_instance
+ "FFT(): Instance has more than 2 images",
+ cimglist_instance);
+
+ CImg<T>::FFT(_data[0],_data[1],axis,invert);
+ return *this;
+ }
+
+ //! Compute a 1-D Fast Fourier Transform, along specified axis \newinstance.
+ CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
+ return CImgList<Tfloat>(*this,false).FFT(axis,invert);
+ }
+
+ //! Compute a n-d Fast Fourier Transform.
+ /**
+ \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed.
+ **/
+ CImgList<T>& FFT(const bool invert=false) {
+ if (is_empty()) return *this;
+ if (_width==1) insert(1);
+ if (_width>2)
+ cimg::warn(_cimglist_instance
+ "FFT(): Instance has more than 2 images",
+ cimglist_instance);
+
+ CImg<T>::FFT(_data[0],_data[1],invert);
+ return *this;
+ }
+
+ //! Compute a n-d Fast Fourier Transform \newinstance.
+ CImgList<Tfloat> get_FFT(const bool invert=false) const {
+ return CImgList<Tfloat>(*this,false).FFT(invert);
+ }
+
+ //! Reverse primitives orientations of a 3d object.
+ /**
+ **/
+ CImgList<T>& reverse_object3d() {
+ cimglist_for(*this,l) {
+ CImg<T>& p = _data[l];
+ switch (p.size()) {
+ case 2 : case 3: cimg::swap(p[0],p[1]); break;
+ case 6 : cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break;
+ case 9 : cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break;
+ case 4 : cimg::swap(p[0],p[1],p[2],p[3]); break;
+ case 12 : cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break;
+ }
+ }
+ return *this;
+ }
+
+ //! Reverse primitives orientations of a 3d object \newinstance.
+ CImgList<T> get_reverse_object3d() const {
+ return (+*this).reverse_object3d();
+ }
+
+ //@}
+ }; // struct CImgList<T> { ...
+
+ /*
+ #---------------------------------------------
+ #
+ # Completion of previously declared functions
+ #
+ #----------------------------------------------
+ */
+
+namespace cimg {
+
+ // Functions to return standard streams 'stdin', 'stdout' and 'stderr'.
+ // (throw a CImgIOException when macro 'cimg_use_r' is defined).
+ inline FILE* _stdin(const bool throw_exception) {
+#ifndef cimg_use_r
+ cimg::unused(throw_exception);
+ return stdin;
+#else
+ if (throw_exception) {
+ cimg::exception_mode(0);
+ throw CImgIOException("cimg::stdin(): Reference to 'stdin' stream not allowed in R mode "
+ "('cimg_use_r' is defined).");
+ }
+ return 0;
+#endif
+ }
+
+ inline FILE* _stdout(const bool throw_exception) {
+#ifndef cimg_use_r
+ cimg::unused(throw_exception);
+ return stdout;
+#else
+ if (throw_exception) {
+ cimg::exception_mode(0);
+ throw CImgIOException("cimg::stdout(): Reference to 'stdout' stream not allowed in R mode "
+ "('cimg_use_r' is defined).");
+ }
+ return 0;
+#endif
+ }
+
+ inline FILE* _stderr(const bool throw_exception) {
+#ifndef cimg_use_r
+ cimg::unused(throw_exception);
+ return stderr;
+#else
+ if (throw_exception) {
+ cimg::exception_mode(0);
+ throw CImgIOException("cimg::stderr(): Reference to 'stderr' stream not allowed in R mode "
+ "('cimg_use_r' is defined).");
+ }
+ return 0;
+#endif
+ }
+
+ // Open a file (with wide character support on Windows).
+ inline std::FILE *win_fopen(const char *const path, const char *const mode) {
+#if cimg_OS==2
+ // Convert 'path' to a wide-character string.
+ int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0);
+ if (!err) return std_fopen(path,mode);
+ CImg<wchar_t> wpath(err);
+ err = MultiByteToWideChar(CP_UTF8,0,path,-1,wpath,err);
+ if (!err) return std_fopen(path,mode);
+
+ // Convert 'mode' to a wide-character string.
+ err = MultiByteToWideChar(CP_UTF8,0,mode,-1,0,0);
+ if (!err) return std_fopen(path,mode);
+ CImg<wchar_t> wmode(err);
+ err = MultiByteToWideChar(CP_UTF8,0,mode,-1,wmode,err);
+ if (!err) return std_fopen(path,mode);
+
+ return _wfopen(wpath,wmode);
+#else
+ return std_fopen(path,mode);
+#endif
+ }
+
+ //! Get/set path to store temporary files.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path where temporary files can be saved.
+ **/
+ inline const char* temporary_path(const char *const user_path, const bool reinit_path) {
+#define _cimg_test_temporary_path(p) \
+ if (!path_found) { \
+ cimg_snprintf(s_path,s_path.width(),"%s",p); \
+ cimg_snprintf(tmp,tmp._width,"%s%c%s",s_path.data(),cimg_file_separator,filename_tmp._data); \
+ if ((file=std_fopen(tmp,"wb"))!=0) { cimg::fclose(file); std::remove(tmp); path_found = true; } \
+ }
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(1024);
+ bool path_found = false;
+ CImg<char> tmp(1024), filename_tmp(256);
+ std::FILE *file = 0;
+ cimg_snprintf(filename_tmp,filename_tmp._width,"%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) {
+ *s_path = 0;
+ std::strncpy(tmp,filename_tmp,tmp._width - 1);
+ if ((file=std_fopen(tmp,"wb"))!=0) { cimg::fclose(file); std::remove(tmp); path_found = true; }
+ }
+ if (!path_found) {
+ cimg::mutex(7,0);
+ throw CImgIOException("cimg::temporary_path(): Failed to locate path for writing temporary files.\n");
+ }
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the <i>Program Files/</i> directory (Windows only).
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the program files.
+ **/
+#if cimg_OS==2
+ inline const char* programfiles_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(MAX_PATH);
+ *s_path = 0;
+ // Note: in the following line, 0x26 = CSIDL_PROGRAM_FILES (not defined on every compiler).
+#if !defined(__INTEL_COMPILER)
+ if (!SHGetSpecialFolderPathA(0,s_path,0x0026,false)) {
+ const char *const pfPath = std::getenv("PROGRAMFILES");
+ if (pfPath) std::strncpy(s_path,pfPath,MAX_PATH - 1);
+ else std::strcpy(s_path,"C:\\PROGRA~1");
+ }
+#else
+ std::strcpy(s_path,"C:\\PROGRA~1");
+#endif
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+#endif
+
+ //! Get/set path to the ImageMagick's \c convert binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c convert binary.
+ **/
+ inline const char* imagemagick_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(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(s_path,".\\convert.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%.2d-\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d-Q\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%.2d-\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d-Q\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"convert.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./convert");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"convert");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the GraphicsMagick's \c gm binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c gm binary.
+ **/
+ inline const char* graphicsmagick_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(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(s_path,".\\gm.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%.2d-\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d-Q\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%.2d-\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d-Q\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=10 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 9; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ for (int k = 32; k>=0 && !path_found; --k) {
+ cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"gm.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./gm");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"gm");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the XMedcon's \c medcon binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c medcon binary.
+ **/
+ inline const char* medcon_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(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(s_path,".\\medcon.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) {
+ cimg_snprintf(s_path,s_path._width,"%s\\XMedCon\\bin\\medcon.bat",pf_path);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) {
+ cimg_snprintf(s_path,s_path._width,"%s\\XMedCon\\bin\\medcon.exe",pf_path);
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) {
+ std::strcpy(s_path,"C:\\XMedCon\\bin\\medcon.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"medcon.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./medcon");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"medcon");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the FFMPEG's \c ffmpeg binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c ffmpeg binary.
+ **/
+ inline const char *ffmpeg_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(1024);
+ bool path_found = false;
+ std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ std::strcpy(s_path,".\\ffmpeg.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"ffmpeg.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./ffmpeg");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"ffmpeg");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the \c gzip binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c gzip binary.
+ **/
+ inline const char *gzip_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(1024);
+ bool path_found = false;
+ std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ std::strcpy(s_path,".\\gzip.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"gzip.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./gzip");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"gzip");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the \c gunzip binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c gunzip binary.
+ **/
+ inline const char *gunzip_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(1024);
+ bool path_found = false;
+ std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ std::strcpy(s_path,".\\gunzip.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"gunzip.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./gunzip");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"gunzip");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the \c dcraw binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c dcraw binary.
+ **/
+ inline const char *dcraw_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(1024);
+ bool path_found = false;
+ std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ std::strcpy(s_path,".\\dcraw.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"dcraw.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./dcraw");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"dcraw");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the \c wget binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c wget binary.
+ **/
+ inline const char *wget_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(1024);
+ bool path_found = false;
+ std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ std::strcpy(s_path,".\\wget.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"wget.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./wget");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"wget");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ //! Get/set path to the \c curl binary.
+ /**
+ \param user_path Specified path, or \c 0 to get the path currently used.
+ \param reinit_path Force path to be recalculated (may take some time).
+ \return Path containing the \c curl binary.
+ **/
+ inline const char *curl_path(const char *const user_path, const bool reinit_path) {
+ static CImg<char> s_path;
+ cimg::mutex(7);
+ if (reinit_path) s_path.assign();
+ if (user_path) {
+ if (!s_path) s_path.assign(1024);
+ std::strncpy(s_path,user_path,1023);
+ } else if (!s_path) {
+ s_path.assign(1024);
+ bool path_found = false;
+ std::FILE *file = 0;
+#if cimg_OS==2
+ if (!path_found) {
+ std::strcpy(s_path,".\\curl.exe");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"curl.exe");
+#else
+ if (!path_found) {
+ std::strcpy(s_path,"./curl");
+ if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; }
+ }
+ if (!path_found) std::strcpy(s_path,"curl");
+#endif
+ winformat_string(s_path);
+ }
+ cimg::mutex(7,0);
+ return s_path;
+ }
+
+ // [internal] Sorting function, used by cimg::files().
+ inline int _sort_files(const void* a, const void* b) {
+ const CImg<char> &sa = *(CImg<char>*)a, &sb = *(CImg<char>*)b;
+ return std::strcmp(sa._data,sb._data);
+ }
+
+ //! Return list of files/directories in specified directory.
+ /**
+ \param path Path to the directory. Set to 0 for current directory.
+ \param is_pattern Tell if specified path has a matching pattern in it.
+ \param mode Output type, can be primary { 0=files only | 1=folders only | 2=files + folders }.
+ \param include_path Tell if \c path must be included in resulting filenames.
+ \return A list of filenames.
+ **/
+ inline CImgList<char> files(const char *const path, const bool is_pattern=false,
+ const unsigned int mode=2, const bool include_path=false) {
+ if (!path || !*path) return files("*",true,mode,include_path);
+ CImgList<char> res;
+
+ // If path is a valid folder name, ignore argument 'is_pattern'.
+ const bool _is_pattern = is_pattern && !cimg::is_directory(path);
+ bool is_root = false, is_current = false;
+ cimg::unused(is_root,is_current);
+
+ // Clean format of input path.
+ CImg<char> pattern, _path = CImg<char>::string(path);
+#if cimg_OS==2
+ for (char *ps = _path; *ps; ++ps) if (*ps=='\\') *ps='/';
+#endif
+ char *pd = _path;
+ for (char *ps = pd; *ps; ++ps) { if (*ps!='/' || *ps!=*(ps+1)) *(pd++) = *ps; }
+ *pd = 0;
+ unsigned int lp = (unsigned int)std::strlen(_path);
+ if (!_is_pattern && lp && _path[lp - 1]=='/') {
+ _path[lp - 1] = 0; --lp;
+#if cimg_OS!=2
+ is_root = !*_path;
+#endif
+ }
+
+ // Separate folder path and matching pattern.
+ if (_is_pattern) {
+ const unsigned int bpos = (unsigned int)(cimg::basename(_path,'/') - _path.data());
+ CImg<char>::string(_path).move_to(pattern);
+ if (bpos) {
+ _path[bpos - 1] = 0; // End 'path' at last slash.
+#if cimg_OS!=2
+ is_root = !*_path;
+#endif
+ } else { // No path to folder specified, assuming current folder.
+ is_current = true; *_path = 0;
+ }
+ lp = (unsigned int)std::strlen(_path);
+ }
+
+ // Windows version.
+#if cimg_OS==2
+ if (!_is_pattern) {
+ pattern.assign(lp + 3);
+ std::memcpy(pattern,_path,lp);
+ pattern[lp] = '/'; pattern[lp + 1] = '*'; pattern[lp + 2] = 0;
+ }
+ WIN32_FIND_DATAA file_data;
+ const HANDLE dir = FindFirstFileA(pattern.data(),&file_data);
+ if (dir==INVALID_HANDLE_VALUE) return CImgList<char>::const_empty();
+ do {
+ const char *const filename = file_data.cFileName;
+ if (*filename!='.' || (filename[1] && (filename[1]!='.' || filename[2]))) {
+ const unsigned int lf = (unsigned int)std::strlen(filename);
+ const bool is_directory = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)!=0;
+ if ((!mode && !is_directory) || (mode==1 && is_directory) || mode>=2) {
+ if (include_path) {
+ CImg<char> full_filename((lp?lp+1:0) + lf + 1);
+ if (lp) { std::memcpy(full_filename,_path,lp); full_filename[lp] = '/'; }
+ std::memcpy(full_filename._data + (lp?lp + 1:0),filename,lf + 1);
+ full_filename.move_to(res);
+ } else CImg<char>(filename,lf + 1).move_to(res);
+ }
+ }
+ } while (FindNextFileA(dir,&file_data));
+ FindClose(dir);
+
+ // Unix version (posix).
+#elif cimg_OS == 1
+ DIR *const dir = opendir(is_root?"/":is_current?".":_path.data());
+ if (!dir) return CImgList<char>::const_empty();
+ struct dirent *ent;
+ while ((ent=readdir(dir))!=0) {
+ const char *const filename = ent->d_name;
+ if (*filename!='.' || (filename[1] && (filename[1]!='.' || filename[2]))) {
+ const unsigned int lf = (unsigned int)std::strlen(filename);
+ CImg<char> full_filename(lp + lf + 2);
+
+ if (!is_current) {
+ full_filename.assign(lp + lf + 2);
+ if (lp) std::memcpy(full_filename,_path,lp);
+ full_filename[lp] = '/';
+ std::memcpy(full_filename._data + lp + 1,filename,lf + 1);
+ } else full_filename.assign(filename,lf + 1);
+
+ struct stat st;
+ if (stat(full_filename,&st)==-1) continue;
+ const bool is_directory = (st.st_mode & S_IFDIR)!=0;
+ if ((!mode && !is_directory) || (mode==1 && is_directory) || mode==2) {
+ if (include_path) {
+ if (!_is_pattern || (_is_pattern && !fnmatch(pattern,full_filename,0)))
+ full_filename.move_to(res);
+ } else {
+ if (!_is_pattern || (_is_pattern && !fnmatch(pattern,full_filename,0)))
+ CImg<char>(filename,lf + 1).move_to(res);
+ }
+ }
+ }
+ }
+ closedir(dir);
+#endif
+
+ // Sort resulting list by lexicographic order.
+ if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg<char>),_sort_files);
+
+ return res;
+ }
+
+ //! Try to guess format from an image file.
+ /**
+ \param file Input file (can be \c 0 if \c filename is set).
+ \param filename Filename, as a C-string (can be \c 0 if \c file is set).
+ \return C-string containing the guessed file format, or \c 0 if nothing has been guessed.
+ **/
+ inline const char *ftype(std::FILE *const file, const char *const filename) {
+ if (!file && !filename)
+ throw CImgArgumentException("cimg::ftype(): 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";
+ const char *f_type = 0;
+ CImg<char> header;
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ try {
+ header._load_raw(file,filename,512,1,1,1,false,false,0);
+ const unsigned char *const uheader = (unsigned char*)header._data;
+ if (!std::strncmp(header,"OFF\n",4)) f_type = _off; // OFF.
+ else if (!std::strncmp(header,"#INRIMAGE",9)) f_type = _inr; // INRIMAGE.
+ else if (!std::strncmp(header,"PANDORE",7)) f_type = _pan; // PANDORE.
+ else if (!std::strncmp(header.data() + 128,"DICM",4)) f_type = _dcm; // DICOM.
+ else if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) f_type = _jpg; // JPEG.
+ else if (header[0]=='B' && header[1]=='M') f_type = _bmp; // BMP.
+ else if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' && // GIF.
+ (header[4]=='7' || header[4]=='9')) f_type = _gif;
+ else if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 && // PNG.
+ 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; // TIFF.
+ else { // PNM or PFM.
+ CImgList<char> _header = header.get_split(CImg<char>::vector('\n'),0,false);
+ cimglist_for(_header,l) {
+ if (_header(l,0)=='#') continue;
+ if (_header[l]._height==2 && _header(l,0)=='P') {
+ const char c = _header(l,1);
+ if (c=='f' || c=='F') { f_type = _pfm; break; }
+ if (c>='1' && c<='9') { f_type = _pnm; break; }
+ }
+ f_type = 0; break;
+ }
+ }
+ } catch (CImgIOException&) { }
+ cimg::exception_mode(omode);
+ return f_type;
+ }
+
+ //! Load file from network as a local temporary file.
+ /**
+ \param filename Filename, as a C-string.
+ \param[out] filename_local C-string containing the path to a local copy of \c filename.
+ \param timeout Maximum time (in seconds) authorized for downloading the file from the URL.
+ \param try_fallback When using libcurl, tells using system calls as fallbacks in case of libcurl failure.
+ \return Value of \c filename_local.
+ \note Use the \c libcurl library, or the external binaries \c wget or \c curl to perform the download.
+ **/
+ inline char *load_network(const char *const url, char *const filename_local,
+ const unsigned int timeout, const bool try_fallback,
+ const char *const referer) {
+ if (!url)
+ throw CImgArgumentException("cimg::load_network(): Specified URL is (null).");
+ if (!filename_local)
+ throw CImgArgumentException("cimg::load_network(): Specified destination string is (null).");
+
+ const char *const __ext = cimg::split_filename(url), *const _ext = (*__ext && __ext>url)?__ext - 1:__ext;
+ CImg<char> ext = CImg<char>::string(_ext);
+ std::FILE *file = 0;
+ *filename_local = 0;
+ if (ext._width>16 || !cimg::strncasecmp(ext,"cgi",3)) *ext = 0;
+ else cimg::strwindows_reserved(ext);
+ do {
+ cimg_snprintf(filename_local,256,"%s%c%s%s",
+ cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext._data);
+ if ((file=std_fopen(filename_local,"rb"))!=0) cimg::fclose(file);
+ } while (file);
+
+#ifdef cimg_use_curl
+ const unsigned int omode = cimg::exception_mode();
+ cimg::exception_mode(0);
+ try {
+ CURL *curl = 0;
+ CURLcode res;
+ curl = curl_easy_init();
+ if (curl) {
+ file = cimg::fopen(filename_local,"wb");
+ curl_easy_setopt(curl,CURLOPT_URL,url);
+ curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,0);
+ curl_easy_setopt(curl,CURLOPT_WRITEDATA,file);
+ curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L);
+ curl_easy_setopt(curl,CURLOPT_SSL_VERIFYHOST,0L);
+ curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1L);
+ if (timeout) curl_easy_setopt(curl,CURLOPT_TIMEOUT,(long)timeout);
+ if (std::strchr(url,'?')) curl_easy_setopt(curl,CURLOPT_HTTPGET,1L);
+ if (referer) curl_easy_setopt(curl,CURLOPT_REFERER,referer);
+ res = curl_easy_perform(curl);
+ curl_easy_cleanup(curl);
+ cimg::fseek(file,0,SEEK_END); // Check if file size is 0.
+ const cimg_ulong siz = cimg::ftell(file);
+ cimg::fclose(file);
+ if (siz>0 && res==CURLE_OK) {
+ cimg::exception_mode(omode);
+ return filename_local;
+ } else std::remove(filename_local);
+ }
+ } catch (...) { }
+ cimg::exception_mode(omode);
+ if (!try_fallback) throw CImgIOException("cimg::load_network(): Failed to load file '%s' with libcurl.",url);
+#endif
+
+ CImg<char> command((unsigned int)std::strlen(url) + 64);
+ cimg::unused(try_fallback);
+
+ // Try with 'curl' first.
+ if (timeout) {
+ if (referer)
+ cimg_snprintf(command,command._width,"%s -e %s -m %u -f --silent --compressed -o \"%s\" \"%s\"",
+ cimg::curl_path(),referer,timeout,filename_local,url);
+ else
+ cimg_snprintf(command,command._width,"%s -m %u -f --silent --compressed -o \"%s\" \"%s\"",
+ cimg::curl_path(),timeout,filename_local,url);
+ } else {
+ if (referer)
+ cimg_snprintf(command,command._width,"%s -e %s -f --silent --compressed -o \"%s\" \"%s\"",
+ cimg::curl_path(),referer,filename_local,url);
+ else
+ cimg_snprintf(command,command._width,"%s -f --silent --compressed -o \"%s\" \"%s\"",
+ cimg::curl_path(),filename_local,url);
+ }
+ cimg::system(command);
+
+ if (!(file = std_fopen(filename_local,"rb"))) {
+
+ // Try with 'wget' otherwise.
+ if (timeout) {
+ if (referer)
+ cimg_snprintf(command,command._width,"%s --referer=%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"",
+ cimg::wget_path(),referer,timeout,filename_local,url);
+ else
+ cimg_snprintf(command,command._width,"%s -T %u -q -r -l 0 --no-cache -O \"%s\" \"%s\"",
+ cimg::wget_path(),timeout,filename_local,url);
+ } else {
+ if (referer)
+ cimg_snprintf(command,command._width,"%s --referer=%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"",
+ cimg::wget_path(),referer,filename_local,url);
+ else
+ cimg_snprintf(command,command._width,"%s -q -r -l 0 --no-cache -O \"%s\" \"%s\"",
+ cimg::wget_path(),filename_local,url);
+ }
+ cimg::system(command);
+
+ if (!(file = std_fopen(filename_local,"rb")))
+ throw CImgIOException("cimg::load_network(): Failed to load file '%s' with external commands "
+ "'wget' or 'curl'.",url);
+ cimg::fclose(file);
+
+ // Try gunzip it.
+ cimg_snprintf(command,command._width,"%s.gz",filename_local);
+ std::rename(filename_local,command);
+ cimg_snprintf(command,command._width,"%s --quiet \"%s.gz\"",
+ gunzip_path(),filename_local);
+ cimg::system(command);
+ file = std_fopen(filename_local,"rb");
+ if (!file) {
+ cimg_snprintf(command,command._width,"%s.gz",filename_local);
+ std::rename(command,filename_local);
+ file = std_fopen(filename_local,"rb");
+ }
+ }
+ cimg::fseek(file,0,SEEK_END); // Check if file size is 0.
+ if (std::ftell(file)<=0)
+ throw CImgIOException("cimg::load_network(): Failed to load URL '%s' with external commands "
+ "'wget' or 'curl'.",url);
+ cimg::fclose(file);
+ return filename_local;
+ }
+
+ // Implement a tic/toc mechanism to display elapsed time of algorithms.
+ inline cimg_ulong tictoc(const bool is_tic) {
+ cimg::mutex(2);
+ static CImg<cimg_ulong> times(64);
+ static unsigned int pos = 0;
+ const cimg_ulong t1 = cimg::time();
+ if (is_tic) { // Tic.
+ times[pos++] = t1;
+ if (pos>=times._width)
+ throw CImgArgumentException("cimg::tic(): Too much calls to 'cimg::tic()' without calls to 'cimg::toc()'.");
+ cimg::mutex(2,0);
+ return t1;
+ }
+ // Toc.
+ if (!pos)
+ throw CImgArgumentException("cimg::toc(): No previous call to 'cimg::tic()' has been made.");
+ const cimg_ulong
+ t0 = times[--pos],
+ dt = t1>=t0?(t1 - t0):cimg::type<cimg_ulong>::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]%*sElapsed time: %u ms%s\n",
+ cimg::t_red,1 + 2*pos,"",ems,cimg::t_normal);
+ else {
+ if (!edays && !ehours && !emin)
+ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u sec %u ms%s\n",
+ cimg::t_red,1 + 2*pos,"",esec,ems,cimg::t_normal);
+ else {
+ if (!edays && !ehours)
+ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u min %u sec %u ms%s\n",
+ cimg::t_red,1 + 2*pos,"",emin,esec,ems,cimg::t_normal);
+ else{
+ if (!edays)
+ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u hours %u min %u sec %u ms%s\n",
+ cimg::t_red,1 + 2*pos,"",ehours,emin,esec,ems,cimg::t_normal);
+ else{
+ std::fprintf(cimg::output(),"%s[CImg]%*sElapsed time: %u days %u hours %u min %u sec %u ms%s\n",
+ cimg::t_red,1 + 2*pos,"",edays,ehours,emin,esec,ems,cimg::t_normal);
+ }
+ }
+ }
+ }
+ cimg::mutex(2,0);
+ return dt;
+ }
+
+ // Return a temporary string describing the size of a memory buffer.
+ inline const char *strbuffersize(const cimg_ulong size) {
+ static CImg<char> res(256);
+ cimg::mutex(5);
+ if (size<1024LU) cimg_snprintf(res,res._width,"%lu byte%s",(unsigned long)size,size>1?"s":"");
+ else if (size<1024*1024LU) { const float nsize = size/1024.0f; cimg_snprintf(res,res._width,"%.1f Kio",nsize); }
+ else if (size<1024*1024*1024LU) {
+ const float nsize = size/(1024*1024.0f); cimg_snprintf(res,res._width,"%.1f Mio",nsize);
+ } else { const float nsize = size/(1024*1024*1024.0f); cimg_snprintf(res,res._width,"%.1f Gio",nsize); }
+ cimg::mutex(5,0);
+ return res;
+ }
+
+ //! Display a simple dialog box, and wait for the user's response.
+ /**
+ \param title Title of the dialog window.
+ \param msg Main message displayed inside the dialog window.
+ \param button1_label Label of the 1st button.
+ \param button2_label Label of the 2nd button (\c 0 to hide button).
+ \param button3_label Label of the 3rd button (\c 0 to hide button).
+ \param button4_label Label of the 4th button (\c 0 to hide button).
+ \param button5_label Label of the 5th button (\c 0 to hide button).
+ \param button6_label Label of the 6th button (\c 0 to hide button).
+ \param logo Image logo displayed at the left of the main message.
+ \param is_centered Tells if the dialog window must be centered on the screen.
+ \return Indice of clicked button (from \c 0 to \c 5), or \c -1 if the dialog window has been closed by the user.
+ \note
+ - Up to 6 buttons can be defined in the dialog window.
+ - The function returns when a user clicked one of the button or closed the dialog window.
+ - If a button text is set to 0, the corresponding button (and the followings) will not appear in the dialog box.
+ At least one button must be specified.
+ **/
+ template<typename t>
+ inline int dialog(const char *const title, const char *const msg,
+ const char *const button1_label, const char *const button2_label,
+ const char *const button3_label, const char *const button4_label,
+ const char *const button5_label, const char *const button6_label,
+ const CImg<t>& logo, const bool is_centered=false) {
+#if cimg_display==0
+ cimg::unused(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label,
+ logo._data,is_centered);
+ throw CImgIOException("cimg::dialog(): No display available.");
+#else
+ static 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 = std::max(bw,buttons[l]._width); bh = std::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",gray,0,1,13,msg)*=-1)+=200).resize(-100,-100,1,3).move_to(canvas);
+
+ const unsigned int
+ bwall = (buttons._width - 1)*(12 + bw) + bw,
+ w = cimg::max(196U,36 + logo._width + canvas._width,24 + bwall),
+ h = cimg::max(96U,36 + canvas._height + bh,36 + logo._height + bh),
+ lx = 12 + (canvas._data?0:((w - 24 - logo._width)/2)),
+ ly = (h - 12 - bh - logo._height)/2,
+ tx = lx + logo._width + 12,
+ ty = (h - 12 - bh - canvas._height)/2,
+ bx = (w - bwall)/2,
+ by = h - 12 - bh;
+
+ if (canvas._data)
+ canvas = CImg<unsigned char>(w,h,1,3).
+ draw_rectangle(0,0,w - 1,h - 1,gray).
+ draw_line(0,0,w - 1,0,white).draw_line(0,h - 1,0,0,white).
+ draw_line(w - 1,0,w - 1,h - 1,black).draw_line(w - 1,h - 1,0,h - 1,black).
+ draw_image(tx,ty,canvas);
+ else
+ canvas = CImg<unsigned char>(w,h,1,3).
+ draw_rectangle(0,0,w - 1,h - 1,gray).
+ draw_line(0,0,w - 1,0,white).draw_line(0,h - 1,0,0,white).
+ draw_line(w - 1,0,w - 1,h - 1,black).draw_line(w - 1,h - 1,0,h - 1,black);
+ if (logo._data) canvas.draw_image(lx,ly,logo);
+
+ unsigned int xbuttons[6] = { 0 };
+ cimglist_for(buttons,lll) { xbuttons[lll] = bx + (bw + 12)*lll; canvas.draw_image(xbuttons[lll],by,buttons[lll]); }
+
+ // Open window and enter events loop
+ CImgDisplay disp(canvas,title?title:" ",0,false,is_centered?true:false);
+ if (is_centered) disp.move((CImgDisplay::screen_width() - disp.width())/2,
+ (CImgDisplay::screen_height() - disp.height())/2);
+ bool stop_flag = false, refresh = false;
+ int oselected = -1, oclicked = -1, selected = -1, clicked = -1;
+ while (!disp.is_closed() && !stop_flag) {
+ if (refresh) {
+ if (clicked>=0)
+ CImg<unsigned char>(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp);
+ else {
+ if (selected>=0)
+ CImg<unsigned char>(canvas).draw_image(xbuttons[selected],by,sbuttons[selected]).display(disp);
+ else canvas.display(disp);
+ }
+ refresh = false;
+ }
+ disp.wait(15);
+ if (disp.is_resized()) disp.resize(disp,false);
+
+ if (disp.button()&1) {
+ oclicked = clicked;
+ clicked = -1;
+ cimglist_for(buttons,l)
+ if (disp.mouse_y()>=(int)by && disp.mouse_y()<(int)(by + bh) &&
+ disp.mouse_x()>=(int)xbuttons[l] && disp.mouse_x()<(int)(xbuttons[l] + bw)) {
+ clicked = selected = l;
+ refresh = true;
+ }
+ if (clicked!=oclicked) refresh = true;
+ } else if (clicked>=0) stop_flag = true;
+
+ if (disp.key()) {
+ oselected = selected;
+ switch (disp.key()) {
+ case cimg::keyESC : selected = -1; stop_flag = true; break;
+ case cimg::keyENTER : if (selected<0) selected = 0; stop_flag = 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
+ }
+
+ //! Display a simple dialog box, and wait for the user's response \specialization.
+ inline int dialog(const char *const title, const char *const msg,
+ const char *const button1_label, const char *const button2_label, const char *const button3_label,
+ const char *const button4_label, const char *const button5_label, const char *const button6_label,
+ const bool is_centered) {
+ return dialog(title,msg,button1_label,button2_label,button3_label,button4_label,button5_label,button6_label,
+ CImg<unsigned char>::_logo40x38(),is_centered);
+ }
+
+ //! Evaluate math expression.
+ /**
+ \param expression C-string describing the formula to evaluate.
+ \param x Value of the pre-defined variable \c x.
+ \param y Value of the pre-defined variable \c y.
+ \param z Value of the pre-defined variable \c z.
+ \param c Value of the pre-defined variable \c c.
+ \return Result of the formula evaluation.
+ \note Set \c expression to \c 0 to keep evaluating the last specified \c expression.
+ \par Example
+ \code
+ const double
+ res1 = cimg::eval("cos(x)^2 + sin(y)^2",2,2), // will return '1'.
+ res2 = cimg::eval(0,1,1); // will return '1' too.
+ \endcode
+ **/
+ inline double eval(const char *const expression, const double x, const double y, const double z, const double c) {
+ static const CImg<float> empty;
+ return empty.eval(expression,x,y,z,c);
+ }
+
+ template<typename t>
+ inline CImg<typename cimg::superset<double,t>::type> eval(const char *const expression, const CImg<t>& xyzc) {
+ static const CImg<float> empty;
+ return empty.eval(expression,xyzc);
+ }
+
+ // End of cimg:: namespace
+}
+
+ // End of cimg_library:: namespace
+}
+
+//! Short alias name.
+namespace cil = cimg_library_suffixed;
+
+#ifdef _cimg_redefine_False
+#define False 0
+#endif
+#ifdef _cimg_redefine_True
+#define True 1
+#endif
+#ifdef _cimg_redefine_None
+#define None 0
+#endif
+#ifdef _cimg_redefine_min
+#define min(a,b) (((a)<(b))?(a):(b))
+#endif
+#ifdef _cimg_redefine_max
+#define max(a,b) (((a)>(b))?(a):(b))
+#endif
+#ifdef _cimg_redefine_PI
+#define PI 3.141592653589793238462643383
+#endif
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif
+// Local Variables:
+// mode: c++
+// End:
diff --git a/include/aspeed/JTABLES.H b/include/aspeed/JTABLES.H
new file mode 100644
index 0000000000..24e9c55421
--- /dev/null
+++ b/include/aspeed/JTABLES.H
@@ -0,0 +1,303 @@
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static unsigned char zigzag[64] = {
+ 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42,
+ 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53,
+ 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60,
+ 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63};
+
+static unsigned char dezigzag[64 + 15] = {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40,
+ 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61,
+ 54, 47, 55, 62, 63,
+ // let corrupt input sample past end
+ 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63};
+
+static unsigned char *std_luminance_qt;
+static unsigned char *std_chrominance_qt;
+
+// Standard Huffman tables (cf. JPEG standard section K.3) */
+
+static unsigned char std_dc_luminance_nrcodes[17] = {0, 0, 1, 5, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0, 0, 0};
+static unsigned char std_dc_luminance_values[12] = {0, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11};
+
+static unsigned char std_dc_chrominance_nrcodes[17] = {
+ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
+static unsigned char std_dc_chrominance_values[12] = {0, 1, 2, 3, 4, 5,
+ 6, 7, 8, 9, 10, 11};
+
+static unsigned char std_ac_luminance_nrcodes[17] = {
+ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d};
+static unsigned char std_ac_luminance_values[162] = {
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+ 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
+ 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72,
+ 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+ 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75,
+ 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+ 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+ 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4,
+ 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa};
+
+static unsigned char std_ac_chrominance_nrcodes[17] = {
+ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77};
+static unsigned char std_ac_chrominance_values[162] = {
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41,
+ 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+ 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1,
+ 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
+ 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+ 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
+ 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
+ 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
+ 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
+ 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa};
+
+unsigned short int DC_LUMINANCE_HUFFMANCODE[13 * 2] = {
+ /* 0 */ 0x0000, 0,
+ /* 1 */ 0x4000, 2,
+ /* 2 */ 0x6000, 3,
+ /* 3 */ 0x8000, 3,
+ /* 4 */ 0xA000, 3,
+ /* 5 */ 0xC000, 3,
+ /* 6 */ 0xE000, 3,
+ /* 7 */ 0xF000, 4,
+ /* 8 */ 0xF800, 5,
+ /* 9 */ 0xFC00, 6,
+ /* 10 */ 0xFE00, 7,
+ /* 11 */ 0xFF00, 8,
+ /* 12 */ 0xFFFF, 9,
+};
+
+unsigned short int DC_CHROMINANCE_HUFFMANCODE[13 * 2] = {
+ /* 0 */ 0x0000, 0,
+ /* 1 */ 0x4000, 2,
+ /* 2 */ 0x8000, 2,
+ /* 3 */ 0xC000, 2,
+ /* 4 */ 0xE000, 3,
+ /* 5 */ 0xF000, 4,
+ /* 6 */ 0xF800, 5,
+ /* 7 */ 0xFC00, 6,
+ /* 8 */ 0xFE00, 7,
+ /* 9 */ 0xFF00, 8,
+ /* 10 */ 0xFF80, 9,
+ /* 11 */ 0xFFC0, 10,
+ /* 12 */ 0xFFFF, 11,
+};
+
+unsigned short int AC_LUMINANCE_HUFFMANCODE[39 * 2] = {
+ /* 0 */ 0x0000, 0,
+ /* 1 */ 0x4000, 2,
+ /* 2 */ 0x8000, 2,
+ /* 3 */ 0xA000, 3,
+ /* 4 */ 0xB000, 4,
+ /* 5 */ 0xC000, 4,
+ /* 6 */ 0xD000, 4,
+ /* 7 */ 0xD800, 5,
+ /* 8 */ 0xE000, 5,
+ /* 9 */ 0xE800, 5,
+ /* 10 */ 0xEC00, 6,
+ /* 11 */ 0xF000, 6,
+ /* 12 */ 0xF200, 7,
+ /* 13 */ 0xF400, 7,
+ /* 14 */ 0xF600, 7,
+ /* 15 */ 0xF800, 7,
+ /* 16 */ 0xF900, 8,
+ /* 17 */ 0xFA00, 8,
+ /* 18 */ 0xFB00, 8,
+ /* 19 */ 0xFB80, 9,
+ /* 20 */ 0xFC00, 9,
+ /* 21 */ 0xFC80, 9,
+ /* 22 */ 0xFD00, 9,
+ /* 23 */ 0xFD80, 9,
+ /* 24 */ 0xFDC0, 10,
+ /* 25 */ 0xFE00, 10,
+ /* 26 */ 0xFE40, 10,
+ /* 27 */ 0xFE80, 10,
+ /* 28 */ 0xFEC0, 10,
+ /* 29 */ 0xFEE0, 11,
+ /* 30 */ 0xFF00, 11,
+ /* 31 */ 0xFF20, 11,
+ /* 32 */ 0xFF40, 11,
+ /* 33 */ 0xFF50, 12,
+ /* 34 */ 0xFF60, 12,
+ /* 35 */ 0xFF70, 12,
+ /* 36 */ 0xFF80, 12,
+ /* 37 */ 0xFF82, 15,
+ /* 38 */ 0xFFFF, 16,
+};
+
+unsigned short int AC_CHROMINANCE_HUFFMANCODE[45 * 2] = {
+ /* 0 */ 0x0000, 0,
+ /* 1 */ 0x4000, 2,
+ /* 2 */ 0x8000, 2,
+ /* 3 */ 0xA000, 3,
+ /* 4 */ 0xB000, 4,
+ /* 5 */ 0xC000, 4,
+ /* 6 */ 0xC800, 5,
+ /* 7 */ 0xD000, 5,
+ /* 8 */ 0xD800, 5,
+ /* 9 */ 0xE000, 5,
+ /* 10 */ 0xE400, 6,
+ /* 11 */ 0xE800, 6,
+ /* 12 */ 0xEC00, 6,
+ /* 13 */ 0xF000, 6,
+ /* 14 */ 0xF200, 7,
+ /* 15 */ 0xF400, 7,
+ /* 16 */ 0xF600, 7,
+ /* 17 */ 0xF700, 8,
+ /* 18 */ 0xF800, 8,
+ /* 19 */ 0xF900, 8,
+ /* 20 */ 0xFA00, 8,
+ /* 21 */ 0xFA80, 9,
+ /* 22 */ 0xFB00, 9,
+ /* 23 */ 0xFB80, 9,
+ /* 24 */ 0xFC00, 9,
+ /* 25 */ 0xFC80, 9,
+ /* 26 */ 0xFD00, 9,
+ /* 27 */ 0xFD80, 9,
+ /* 28 */ 0xFDC0, 10,
+ /* 29 */ 0xFE00, 10,
+ /* 30 */ 0xFE40, 10,
+ /* 31 */ 0xFE80, 10,
+ /* 32 */ 0xFEC0, 10,
+ /* 33 */ 0xFEE0, 11,
+ /* 34 */ 0xFF00, 11,
+ /* 35 */ 0xFF20, 11,
+ /* 36 */ 0xFF40, 11,
+ /* 37 */ 0xFF50, 12,
+ /* 38 */ 0xFF60, 12,
+ /* 39 */ 0xFF70, 12,
+ /* 40 */ 0xFF80, 12,
+ /* 41 */ 0xFF84, 14,
+ /* 42 */ 0xFF86, 15,
+ /* 43 */ 0xFF88, 15,
+ /* 44 */ 0xFFFF, 16,
+};
+
+//[100]=========================
+static unsigned char Tbl_100Y[64] = {
+ 2, 1, 1, 2, 3, 5, 6, 7, 1, 1, 1, 2, 3, 7, 7, 6,
+ 1, 1, 2, 3, 5, 7, 8, 7, 1, 2, 2, 3, 6, 10, 10, 7,
+ 2, 2, 4, 7, 8, 13, 12, 9, 3, 4, 6, 8, 10, 13, 14, 11,
+ 6, 8, 9, 10, 12, 15, 15, 12, 9, 11, 11, 12, 14, 12, 12, 12};
+static unsigned char Tbl_100UV[64] = {
+ 3, 3, 4, 8, 18, 18, 18, 18, 3, 3, 4, 12, 18, 18, 18, 18,
+ 4, 4, 10, 18, 18, 18, 18, 18, 8, 12, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18};
+
+//[086]=========================
+static unsigned char Tbl_086Y[64] = {
+ 3, 2, 1, 3, 4, 7, 9, 11, 2, 2, 2, 3, 4, 10, 11, 10,
+ 2, 2, 3, 4, 7, 10, 12, 10, 2, 3, 4, 5, 9, 16, 15, 11,
+ 3, 4, 6, 10, 12, 20, 19, 14, 4, 6, 10, 12, 15, 19, 21, 17,
+ 9, 12, 14, 16, 19, 22, 22, 18, 13, 17, 17, 18, 21, 18, 19, 18};
+static unsigned char Tbl_086UV[64] = {
+ 4, 5, 6, 13, 27, 27, 27, 27, 5, 5, 7, 18, 27, 27, 27, 27,
+ 6, 7, 15, 27, 27, 27, 27, 27, 13, 18, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27};
+
+//[071]=========================
+static unsigned char Tbl_071Y[64] = {
+ 6, 4, 3, 6, 9, 15, 19, 22, 4, 4, 5, 7, 9, 21, 22, 20,
+ 5, 4, 6, 9, 15, 21, 25, 21, 5, 6, 8, 10, 19, 32, 30, 23,
+ 6, 8, 13, 21, 25, 40, 38, 28, 9, 13, 20, 24, 30, 39, 42, 34,
+ 18, 24, 29, 32, 38, 45, 45, 37, 27, 34, 35, 36, 42, 37, 38, 37};
+static unsigned char Tbl_071UV[64] = {
+ 9, 10, 13, 26, 55, 55, 55, 55, 10, 11, 14, 37, 55, 55, 55, 55,
+ 13, 14, 31, 55, 55, 55, 55, 55, 26, 37, 55, 55, 55, 55, 55, 55,
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55};
+//[057]=========================
+static unsigned char Tbl_057Y[64] = {
+ 9, 6, 5, 9, 13, 22, 28, 34, 6, 6, 7, 10, 14, 32, 33, 30,
+ 7, 7, 9, 13, 22, 32, 38, 31, 7, 9, 12, 16, 28, 48, 45, 34,
+ 10, 12, 20, 31, 38, 61, 57, 43, 13, 19, 30, 36, 45, 58, 63, 51,
+ 27, 36, 43, 48, 57, 68, 67, 56, 40, 51, 53, 55, 63, 56, 57, 55};
+static unsigned char Tbl_057UV[64] = {
+ 13, 14, 19, 38, 80, 80, 80, 80, 14, 17, 21, 53, 80, 80, 80, 80,
+ 19, 21, 45, 80, 80, 80, 80, 80, 38, 53, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80};
+
+//[043]=========================
+static unsigned char Tbl_043Y[64] = {
+ 11, 7, 7, 11, 17, 28, 36, 43, 8, 8, 10, 13, 18, 41, 43, 39,
+ 10, 9, 11, 17, 28, 40, 49, 40, 10, 12, 15, 20, 36, 62, 57, 44,
+ 12, 15, 26, 40, 48, 78, 74, 55, 17, 25, 39, 46, 58, 74, 81, 66,
+ 35, 46, 56, 62, 74, 86, 86, 72, 51, 66, 68, 70, 80, 71, 74, 71};
+static unsigned char Tbl_043UV[64] = {
+ 18, 19, 26, 51, 108, 108, 108, 108, 19, 22, 28, 72, 108,
+ 108, 108, 108, 26, 28, 61, 108, 108, 108, 108, 108, 51, 72,
+ 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108,
+ 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108,
+ 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108};
+
+//[029]=========================
+static unsigned char Tbl_029Y[64] = {
+ 14, 9, 9, 14, 21, 36, 46, 55, 10, 10, 12, 17, 23, 52, 54, 49,
+ 12, 11, 14, 21, 36, 51, 62, 50, 12, 15, 19, 26, 46, 78, 72, 56,
+ 16, 19, 33, 50, 61, 98, 93, 69, 21, 31, 49, 58, 73, 94, 102, 83,
+ 44, 58, 70, 78, 93, 109, 108, 91, 65, 83, 86, 88, 101, 90, 93, 89};
+static unsigned char Tbl_029UV[64] = {
+ 22, 24, 32, 63, 133, 133, 133, 133, 24, 28, 34, 88, 133,
+ 133, 133, 133, 32, 34, 75, 133, 133, 133, 133, 133, 63, 88,
+ 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133,
+ 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133,
+ 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133, 133};
+
+//[014]=========================
+static unsigned char Tbl_014Y[64] = {
+ 17, 12, 10, 17, 26, 43, 55, 66, 13, 13, 15, 20, 28, 63, 65, 60,
+ 15, 14, 17, 26, 43, 62, 75, 61, 15, 18, 24, 31, 55, 95, 87, 67,
+ 19, 24, 40, 61, 74, 119, 112, 84, 26, 38, 60, 70, 88, 113, 123, 100,
+ 53, 70, 85, 95, 112, 132, 131, 110, 78, 100, 103, 107, 122, 109, 112, 108};
+static unsigned char Tbl_014UV[64] = {
+ 27, 29, 39, 76, 160, 160, 160, 160, 29, 34, 42, 107, 160,
+ 160, 160, 160, 39, 42, 91, 160, 160, 160, 160, 160, 76, 107,
+ 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160,
+ 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160,
+ 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, 160};
+//[000]=========================
+static unsigned char Tbl_000Y[64] = {
+ 20, 13, 12, 20, 30, 50, 63, 76, 15, 15, 17, 23, 32, 72, 75, 68,
+ 17, 16, 20, 30, 50, 71, 86, 70, 17, 21, 27, 36, 63, 108, 100, 77,
+ 22, 27, 46, 70, 85, 136, 128, 96, 30, 43, 68, 80, 101, 130, 141, 115,
+ 61, 80, 97, 108, 128, 151, 150, 126, 90, 115, 118, 122, 140, 125, 128, 123};
+static unsigned char Tbl_000UV[64] = {
+ 31, 33, 45, 88, 185, 185, 185, 185, 33, 39, 48, 123, 185,
+ 185, 185, 185, 45, 48, 105, 185, 185, 185, 185, 185, 88, 123,
+ 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185,
+ 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185,
+ 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185};
+
+typedef struct {
+ unsigned char Length[17]; // k =1-16 ; L[k] indicates the number of Huffman
+ // codes of length k
+ unsigned short int minor_code[17]; // indicates the value of the smallest
+ // Huffman code of length k
+ unsigned short int major_code[17]; // similar, but the highest code
+ unsigned char V[65536]; // V[k][j] = Value associated to the j-th Huffman
+ // code of length k
+ // High nibble = nr of previous 0 coefficients
+ // Low nibble = size (in bits) of the coefficient which will be taken from the
+ // data stream
+ unsigned char Len[65536];
+} Huffman_table;
diff --git a/include/ast_jpeg_decoder.hpp b/include/ast_jpeg_decoder.hpp
new file mode 100644
index 0000000000..7e096783f6
--- /dev/null
+++ b/include/ast_jpeg_decoder.hpp
@@ -0,0 +1,1386 @@
+#pragma once
+
+#include <cstdint>
+#include <array>
+#include <aspeed/JTABLES.H>
+#include <vector>
+
+#include <ast_video_types.hpp>
+#include <iostream>
+
+namespace AstVideo {
+
+static const uint32_t VQ_HEADER_MASK = 0x01;
+static const uint32_t VQ_NO_UPDATE_HEADER = 0x00;
+static const uint32_t VQ_UPDATE_HEADER = 0x01;
+static const int VQ_NO_UPDATE_LENGTH = 0x03;
+static const int VQ_UPDATE_LENGTH = 0x1B;
+static const uint32_t VQ_INDEX_MASK = 0x03;
+static const uint32_t VQ_COLOR_MASK = 0xFFFFFF;
+
+static const int BLOCK_AST2100_START_LENGTH = 0x04;
+static const int BLOCK_AST2100_SKIP_LENGTH = 20; // S:1 H:3 X:8 Y:8
+
+struct COLOR_CACHE {
+ unsigned long Color[4];
+ unsigned char Index[4];
+ unsigned char BitMapBits;
+};
+
+struct RGB {
+ unsigned char B;
+ unsigned char G;
+ unsigned char R;
+ unsigned char Reserved;
+};
+
+enum class JpgBlock {
+ JPEG_NO_SKIP_CODE = 0x00,
+ JPEG_SKIP_CODE = 0x08,
+
+ JPEG_PASS2_CODE = 0x02,
+ JPEG_SKIP_PASS2_CODE = 0x0A,
+
+ LOW_JPEG_NO_SKIP_CODE = 0x04,
+ LOW_JPEG_SKIP_CODE = 0x0C,
+
+ VQ_NO_SKIP_1_COLOR_CODE = 0x05,
+ VQ_SKIP_1_COLOR_CODE = 0x0D,
+
+ VQ_NO_SKIP_2_COLOR_CODE = 0x06,
+ VQ_SKIP_2_COLOR_CODE = 0x0E,
+
+ VQ_NO_SKIP_4_COLOR_CODE = 0x07,
+ VQ_SKIP_4_COLOR_CODE = 0x0F,
+
+ FRAME_END_CODE = 0x09,
+
+};
+
+class AstJpegDecoder {
+ public:
+ AstJpegDecoder() {
+ // TODO(ed) figure out how to init this in the constructor
+ YUVBuffer.resize(800 * 600);
+ OutBuffer.resize(800 * 600);
+ for (auto &r : OutBuffer) {
+ r.R = 0x00;
+ r.G = 0x00;
+ r.B = 0x00;
+ r.Reserved = 0xAA;
+ }
+ init_jpg_table();
+ }
+
+ void load_quant_table(std::array<long, 64> &quant_table) {
+ float scalefactor[8] = {1.0f, 1.387039845f, 1.306562965f, 1.175875602f,
+ 1.0f, 0.785694958f, 0.541196100f, 0.275899379f};
+ uint8_t j, row, col;
+ uint8_t tempQT[64];
+
+ // Load quantization coefficients from JPG file, scale them for DCT and
+ // reorder
+ // from zig-zag order
+ switch (Y_selector) {
+ case 0:
+ std_luminance_qt = Tbl_000Y;
+ break;
+ case 1:
+ std_luminance_qt = Tbl_014Y;
+ break;
+ case 2:
+ std_luminance_qt = Tbl_029Y;
+ break;
+ case 3:
+ std_luminance_qt = Tbl_043Y;
+ break;
+ case 4:
+ std_luminance_qt = Tbl_057Y;
+ break;
+ case 5:
+ std_luminance_qt = Tbl_071Y;
+ break;
+ case 6:
+ std_luminance_qt = Tbl_086Y;
+ break;
+ case 7:
+ std_luminance_qt = Tbl_100Y;
+ break;
+ }
+ set_quant_table(std_luminance_qt, (uint8_t)SCALEFACTOR, tempQT);
+
+ for (j = 0; j <= 63; j++) quant_table[j] = tempQT[zigzag[j]];
+ j = 0;
+ for (row = 0; row <= 7; row++)
+ for (col = 0; col <= 7; col++) {
+ quant_table[j] =
+ (long)((quant_table[j] * scalefactor[row] * scalefactor[col]) *
+ 65536);
+ j++;
+ }
+ byte_pos += 64;
+ }
+
+ void load_quant_tableCb(std::array<long, 64> &quant_table) {
+ float scalefactor[8] = {1.0f, 1.387039845f, 1.306562965f, 1.175875602f,
+ 1.0f, 0.785694958f, 0.541196100f, 0.275899379f};
+ uint8_t j, row, col;
+ uint8_t tempQT[64];
+
+ // Load quantization coefficients from JPG file, scale them for DCT and
+ // reorder from zig-zag order
+ if (Mapping == 0) {
+ switch (UV_selector) {
+ case 0:
+ std_chrominance_qt = Tbl_000Y;
+ break;
+ case 1:
+ std_chrominance_qt = Tbl_014Y;
+ break;
+ case 2:
+ std_chrominance_qt = Tbl_029Y;
+ break;
+ case 3:
+ std_chrominance_qt = Tbl_043Y;
+ break;
+ case 4:
+ std_chrominance_qt = Tbl_057Y;
+ break;
+ case 5:
+ std_chrominance_qt = Tbl_071Y;
+ break;
+ case 6:
+ std_chrominance_qt = Tbl_086Y;
+ break;
+ case 7:
+ std_chrominance_qt = Tbl_100Y;
+ break;
+ }
+ } else {
+ switch (UV_selector) {
+ case 0:
+ std_chrominance_qt = Tbl_000UV;
+ break;
+ case 1:
+ std_chrominance_qt = Tbl_014UV;
+ break;
+ case 2:
+ std_chrominance_qt = Tbl_029UV;
+ break;
+ case 3:
+ std_chrominance_qt = Tbl_043UV;
+ break;
+ case 4:
+ std_chrominance_qt = Tbl_057UV;
+ break;
+ case 5:
+ std_chrominance_qt = Tbl_071UV;
+ break;
+ case 6:
+ std_chrominance_qt = Tbl_086UV;
+ break;
+ case 7:
+ std_chrominance_qt = Tbl_100UV;
+ break;
+ }
+ }
+ set_quant_table(std_chrominance_qt, (uint8_t)SCALEFACTORUV, tempQT);
+
+ for (j = 0; j <= 63; j++) {
+ quant_table[j] = tempQT[zigzag[j]];
+ }
+ j = 0;
+ for (row = 0; row <= 7; row++) {
+ for (col = 0; col <= 7; col++) {
+ quant_table[j] =
+ (long)((quant_table[j] * scalefactor[row] * scalefactor[col]) *
+ 65536);
+ j++;
+ }
+ }
+ byte_pos += 64;
+ }
+ // Note: Added for Dual_JPEG
+ void load_advance_quant_table(std::array<long, 64> &quant_table) {
+ float scalefactor[8] = {1.0f, 1.387039845f, 1.306562965f, 1.175875602f,
+ 1.0f, 0.785694958f, 0.541196100f, 0.275899379f};
+ uint8_t j, row, col;
+ uint8_t tempQT[64];
+
+ // Load quantization coefficients from JPG file, scale them for DCT and
+ // reorder
+ // from zig-zag order
+ switch (advance_selector) {
+ case 0:
+ std_luminance_qt = Tbl_000Y;
+ break;
+ case 1:
+ std_luminance_qt = Tbl_014Y;
+ break;
+ case 2:
+ std_luminance_qt = Tbl_029Y;
+ break;
+ case 3:
+ std_luminance_qt = Tbl_043Y;
+ break;
+ case 4:
+ std_luminance_qt = Tbl_057Y;
+ break;
+ case 5:
+ std_luminance_qt = Tbl_071Y;
+ break;
+ case 6:
+ std_luminance_qt = Tbl_086Y;
+ break;
+ case 7:
+ std_luminance_qt = Tbl_100Y;
+ break;
+ }
+ // Note: pass ADVANCE SCALE FACTOR to sub-function in Dual-JPEG
+ set_quant_table(std_luminance_qt, (uint8_t)ADVANCESCALEFACTOR, tempQT);
+
+ for (j = 0; j <= 63; j++) quant_table[j] = tempQT[zigzag[j]];
+ j = 0;
+ for (row = 0; row <= 7; row++)
+ for (col = 0; col <= 7; col++) {
+ quant_table[j] =
+ (long)((quant_table[j] * scalefactor[row] * scalefactor[col]) *
+ 65536);
+ j++;
+ }
+ byte_pos += 64;
+ }
+
+ // Note: Added for Dual-JPEG
+ void load_advance_quant_tableCb(std::array<long, 64> &quant_table) {
+ float scalefactor[8] = {1.0f, 1.387039845f, 1.306562965f, 1.175875602f,
+ 1.0f, 0.785694958f, 0.541196100f, 0.275899379f};
+ uint8_t j, row, col;
+ uint8_t tempQT[64];
+
+ // Load quantization coefficients from JPG file, scale them for DCT and
+ // reorder
+ // from zig-zag order
+ if (Mapping == 1) {
+ switch (advance_selector) {
+ case 0:
+ std_chrominance_qt = Tbl_000Y;
+ break;
+ case 1:
+ std_chrominance_qt = Tbl_014Y;
+ break;
+ case 2:
+ std_chrominance_qt = Tbl_029Y;
+ break;
+ case 3:
+ std_chrominance_qt = Tbl_043Y;
+ break;
+ case 4:
+ std_chrominance_qt = Tbl_057Y;
+ break;
+ case 5:
+ std_chrominance_qt = Tbl_071Y;
+ break;
+ case 6:
+ std_chrominance_qt = Tbl_086Y;
+ break;
+ case 7:
+ std_chrominance_qt = Tbl_100Y;
+ break;
+ }
+ } else {
+ switch (advance_selector) {
+ case 0:
+ std_chrominance_qt = Tbl_000UV;
+ break;
+ case 1:
+ std_chrominance_qt = Tbl_014UV;
+ break;
+ case 2:
+ std_chrominance_qt = Tbl_029UV;
+ break;
+ case 3:
+ std_chrominance_qt = Tbl_043UV;
+ break;
+ case 4:
+ std_chrominance_qt = Tbl_057UV;
+ break;
+ case 5:
+ std_chrominance_qt = Tbl_071UV;
+ break;
+ case 6:
+ std_chrominance_qt = Tbl_086UV;
+ break;
+ case 7:
+ std_chrominance_qt = Tbl_100UV;
+ break;
+ }
+ }
+ // Note: pass ADVANCE SCALE FACTOR to sub-function in Dual-JPEG
+ set_quant_table(std_chrominance_qt, (uint8_t)ADVANCESCALEFACTORUV, tempQT);
+
+ for (j = 0; j <= 63; j++) quant_table[j] = tempQT[zigzag[j]];
+ j = 0;
+ for (row = 0; row <= 7; row++)
+ for (col = 0; col <= 7; col++) {
+ quant_table[j] =
+ (long)((quant_table[j] * scalefactor[row] * scalefactor[col]) *
+ 65536);
+ j++;
+ }
+ byte_pos += 64;
+ }
+
+ void IDCT_transform(short *coef, uint8_t *data, uint8_t nBlock) {
+#define FIX_1_082392200 ((int)277) /* FIX(1.082392200) */
+#define FIX_1_414213562 ((int)362) /* FIX(1.414213562) */
+#define FIX_1_847759065 ((int)473) /* FIX(1.847759065) */
+#define FIX_2_613125930 ((int)669) /* FIX(2.613125930) */
+
+#define MULTIPLY(var, cons) ((int)((var) * (cons)) >> 8)
+
+ int tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
+ int tmp10, tmp11, tmp12, tmp13;
+ int z5, z10, z11, z12, z13;
+ int workspace[64]; /* buffers data between passes */
+
+ short *inptr = coef;
+ long *quantptr;
+ int *wsptr = workspace;
+ unsigned char *outptr;
+ unsigned char *r_limit = rlimit_table + 128;
+ int ctr, dcval, DCTSIZE = 8;
+
+ quantptr = &QT[nBlock][0];
+
+ // Pass 1: process columns from input (inptr), store into work array(wsptr)
+
+ for (ctr = 8; ctr > 0; ctr--) {
+ /* Due to quantization, we will usually find that many of the input
+ * coefficients are zero, especially the AC terms. We can exploit this
+ * by short-circuiting the IDCT calculation for any column in which all
+ * the AC terms are zero. In that case each output is equal to the
+ * DC coefficient (with scale factor as needed).
+ * With typical images and quantization tables, half or more of the
+ * column DCT calculations can be simplified this way.
+ */
+
+ if ((inptr[DCTSIZE * 1] | inptr[DCTSIZE * 2] | inptr[DCTSIZE * 3] |
+ inptr[DCTSIZE * 4] | inptr[DCTSIZE * 5] | inptr[DCTSIZE * 6] |
+ inptr[DCTSIZE * 7]) == 0) {
+ /* AC terms all zero */
+ dcval = (int)((inptr[DCTSIZE * 0] * quantptr[DCTSIZE * 0]) >> 16);
+
+ wsptr[DCTSIZE * 0] = dcval;
+ wsptr[DCTSIZE * 1] = dcval;
+ wsptr[DCTSIZE * 2] = dcval;
+ wsptr[DCTSIZE * 3] = dcval;
+ wsptr[DCTSIZE * 4] = dcval;
+ wsptr[DCTSIZE * 5] = dcval;
+ wsptr[DCTSIZE * 6] = dcval;
+ wsptr[DCTSIZE * 7] = dcval;
+
+ inptr++; /* advance pointers to next column */
+ quantptr++;
+ wsptr++;
+ continue;
+ }
+
+ /* Even part */
+
+ tmp0 = (inptr[DCTSIZE * 0] * quantptr[DCTSIZE * 0]) >> 16;
+ tmp1 = (inptr[DCTSIZE * 2] * quantptr[DCTSIZE * 2]) >> 16;
+ tmp2 = (inptr[DCTSIZE * 4] * quantptr[DCTSIZE * 4]) >> 16;
+ tmp3 = (inptr[DCTSIZE * 6] * quantptr[DCTSIZE * 6]) >> 16;
+
+ tmp10 = tmp0 + tmp2; /* phase 3 */
+ tmp11 = tmp0 - tmp2;
+
+ tmp13 = tmp1 + tmp3; /* phases 5-3 */
+ tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */
+
+ tmp0 = tmp10 + tmp13; /* phase 2 */
+ tmp3 = tmp10 - tmp13;
+ tmp1 = tmp11 + tmp12;
+ tmp2 = tmp11 - tmp12;
+
+ /* Odd part */
+
+ tmp4 = (inptr[DCTSIZE * 1] * quantptr[DCTSIZE * 1]) >> 16;
+ tmp5 = (inptr[DCTSIZE * 3] * quantptr[DCTSIZE * 3]) >> 16;
+ tmp6 = (inptr[DCTSIZE * 5] * quantptr[DCTSIZE * 5]) >> 16;
+ tmp7 = (inptr[DCTSIZE * 7] * quantptr[DCTSIZE * 7]) >> 16;
+
+ z13 = tmp6 + tmp5; /* phase 6 */
+ z10 = tmp6 - tmp5;
+ z11 = tmp4 + tmp7;
+ z12 = tmp4 - tmp7;
+
+ tmp7 = z11 + z13; /* phase 5 */
+ tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */
+
+ z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */
+ tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */
+ tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5; /* -2*(c2+c6) */
+
+ tmp6 = tmp12 - tmp7; /* phase 2 */
+ tmp5 = tmp11 - tmp6;
+ tmp4 = tmp10 + tmp5;
+
+ wsptr[DCTSIZE * 0] = (int)(tmp0 + tmp7);
+ wsptr[DCTSIZE * 7] = (int)(tmp0 - tmp7);
+ wsptr[DCTSIZE * 1] = (int)(tmp1 + tmp6);
+ wsptr[DCTSIZE * 6] = (int)(tmp1 - tmp6);
+ wsptr[DCTSIZE * 2] = (int)(tmp2 + tmp5);
+ wsptr[DCTSIZE * 5] = (int)(tmp2 - tmp5);
+ wsptr[DCTSIZE * 4] = (int)(tmp3 + tmp4);
+ wsptr[DCTSIZE * 3] = (int)(tmp3 - tmp4);
+
+ inptr++; /* advance pointers to next column */
+ quantptr++;
+ wsptr++;
+ }
+
+/* Pass 2: process rows from work array, store into output array. */
+/* Note that we must descale the results by a factor of 8 == 2**3, */
+/* and also undo the PASS1_BITS scaling. */
+
+//#define RANGE_MASK 1023; //2 bits wider than legal samples
+#define PASS1_BITS 0
+#define IDESCALE(x, n) ((int)((x) >> n))
+
+ wsptr = workspace;
+ for (ctr = 0; ctr < DCTSIZE; ctr++) {
+ outptr = data + ctr * 8;
+
+ /* Rows of zeroes can be exploited in the same way as we did with columns.
+ * However, the column calculation has created many nonzero AC terms, so
+ * the simplification applies less often (typically 5% to 10% of the time).
+ * On machines with very fast multiplication, it's possible that the
+ * test takes more time than it's worth. In that case this section
+ * may be commented out.
+ */
+ /* Even part */
+
+ tmp10 = ((int)wsptr[0] + (int)wsptr[4]);
+ tmp11 = ((int)wsptr[0] - (int)wsptr[4]);
+
+ tmp13 = ((int)wsptr[2] + (int)wsptr[6]);
+ tmp12 = MULTIPLY((int)wsptr[2] - (int)wsptr[6], FIX_1_414213562) - tmp13;
+
+ tmp0 = tmp10 + tmp13;
+ tmp3 = tmp10 - tmp13;
+ tmp1 = tmp11 + tmp12;
+ tmp2 = tmp11 - tmp12;
+
+ /* Odd part */
+
+ z13 = (int)wsptr[5] + (int)wsptr[3];
+ z10 = (int)wsptr[5] - (int)wsptr[3];
+ z11 = (int)wsptr[1] + (int)wsptr[7];
+ z12 = (int)wsptr[1] - (int)wsptr[7];
+
+ tmp7 = z11 + z13; /* phase 5 */
+ tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */
+
+ z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */
+ tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */
+ tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5; /* -2*(c2+c6) */
+
+ tmp6 = tmp12 - tmp7; /* phase 2 */
+ tmp5 = tmp11 - tmp6;
+ tmp4 = tmp10 + tmp5;
+
+ /* Final output stage: scale down by a factor of 8 and range-limit */
+
+ outptr[0] = r_limit[IDESCALE((tmp0 + tmp7), (PASS1_BITS + 3)) & 1023L];
+ outptr[7] = r_limit[IDESCALE((tmp0 - tmp7), (PASS1_BITS + 3)) & 1023L];
+ outptr[1] = r_limit[IDESCALE((tmp1 + tmp6), (PASS1_BITS + 3)) & 1023L];
+ outptr[6] = r_limit[IDESCALE((tmp1 - tmp6), (PASS1_BITS + 3)) & 1023L];
+ outptr[2] = r_limit[IDESCALE((tmp2 + tmp5), (PASS1_BITS + 3)) & 1023L];
+ outptr[5] = r_limit[IDESCALE((tmp2 - tmp5), (PASS1_BITS + 3)) & 1023L];
+ outptr[4] = r_limit[IDESCALE((tmp3 + tmp4), (PASS1_BITS + 3)) & 1023L];
+ outptr[3] = r_limit[IDESCALE((tmp3 - tmp4), (PASS1_BITS + 3)) & 1023L];
+
+ wsptr += DCTSIZE; /* advance pointer to next row */
+ }
+ }
+ void YUVToRGB(
+ int txb, int tyb,
+ unsigned char
+ *pYCbCr, // in, Y: 256 or 64 bytes; Cb: 64 bytes; Cr: 64 bytes
+ struct RGB *pYUV, // in, Y: 256 or 64 bytes; Cb: 64 bytes; Cr: 64 bytes
+ unsigned char
+ *pBgr // out, BGR format, 16*16*3 = 768 bytes; or 8*8*3=192 bytes
+ ) {
+ int i, j, pos, m, n;
+ unsigned char cb, cr, *py, *pcb, *pcr, *py420[4];
+ int y;
+ struct RGB *pByte;
+ int nBlocksInMcu = 6;
+ unsigned int pixel_x, pixel_y;
+
+ pByte = (struct RGB *)pBgr;
+ if (yuvmode == YuvMode::YUV444) {
+ py = pYCbCr;
+ pcb = pYCbCr + 64;
+ pcr = pcb + 64;
+
+ pixel_x = txb * 8;
+ pixel_y = tyb * 8;
+ pos = (pixel_y * WIDTH) + pixel_x;
+
+ for (j = 0; j < 8; j++) {
+ for (i = 0; i < 8; i++) {
+ m = ((j << 3) + i);
+ y = py[m];
+ cb = pcb[m];
+ cr = pcr[m];
+ n = pos + i;
+ // For 2Pass. Save the YUV value
+ pYUV[n].B = cb;
+ pYUV[n].G = y;
+ pYUV[n].R = cr;
+ pByte[n].B = rlimit_table[m_Y[y] + m_CbToB[cb]];
+ pByte[n].G = rlimit_table[m_Y[y] + m_CbToG[cb] + m_CrToG[cr]];
+ pByte[n].R = rlimit_table[m_Y[y] + m_CrToR[cr]];
+ /*
+ std::cout << "set y:" << n / 800 << " x:" << n % 800 << " to "
+ << " B:" << static_cast<uint32_t>(pByte[n].B)
+ << " G:" << static_cast<uint32_t>(pByte[n].G)
+ << " R:" << static_cast<uint32_t>(pByte[n].R) << "\n";
+ */
+ }
+ pos += WIDTH;
+ }
+ } else {
+ for (i = 0; i < nBlocksInMcu - 2; i++) py420[i] = pYCbCr + i * 64;
+ pcb = pYCbCr + (nBlocksInMcu - 2) * 64;
+ pcr = pcb + 64;
+
+ pixel_x = txb * 16;
+ pixel_y = tyb * 16;
+ pos = (pixel_y * WIDTH) + pixel_x;
+
+ for (j = 0; j < 16; j++) {
+ for (i = 0; i < 16; i++) {
+ // block number is ((j/8) * 2 + i/8)={0, 1, 2, 3}
+ y = *(py420[(j >> 3) * 2 + (i >> 3)]++);
+ m = ((j >> 1) << 3) + (i >> 1);
+ cb = pcb[m];
+ cr = pcr[m];
+ n = pos + i;
+ pByte[n].B = rlimit_table[m_Y[y] + m_CbToB[cb]];
+ pByte[n].G = rlimit_table[m_Y[y] + m_CbToG[cb] + m_CrToG[cr]];
+ pByte[n].R = rlimit_table[m_Y[y] + m_CrToR[cr]];
+ }
+ pos += WIDTH;
+ }
+ }
+ }
+ void YUVToBuffer(
+ int txb, int tyb,
+ unsigned char
+ *pYCbCr, // in, Y: 256 or 64 bytes; Cb: 64 bytes; Cr: 64 bytes
+ struct RGB
+ *pYUV, // out, BGR format, 16*16*3 = 768 bytes; or 8*8*3=192 bytes
+ unsigned char
+ *pBgr // out, BGR format, 16*16*3 = 768 bytes; or 8*8*3=192 bytes
+ ) {
+ int i, j, pos, m, n;
+ unsigned char cb, cr, *py, *pcb, *pcr, *py420[4];
+ int y;
+ struct RGB *pByte;
+ int nBlocksInMcu = 6;
+ unsigned int pixel_x, pixel_y;
+
+ pByte = (struct RGB *)pBgr;
+ if (yuvmode == YuvMode::YUV444) {
+ py = pYCbCr;
+ pcb = pYCbCr + 64;
+ pcr = pcb + 64;
+
+ pixel_x = txb * 8;
+ pixel_y = tyb * 8;
+ pos = (pixel_y * WIDTH) + pixel_x;
+
+ for (j = 0; j < 8; j++) {
+ for (i = 0; i < 8; i++) {
+ m = ((j << 3) + i);
+ n = pos + i;
+ y = pYUV[n].G + (py[m] - 128);
+ cb = pYUV[n].B + (pcb[m] - 128);
+ cr = pYUV[n].R + (pcr[m] - 128);
+ pYUV[n].B = cb;
+ pYUV[n].G = y;
+ pYUV[n].R = cr;
+ pByte[n].B = rlimit_table[m_Y[y] + m_CbToB[cb]];
+ pByte[n].G = rlimit_table[m_Y[y] + m_CbToG[cb] + m_CrToG[cr]];
+ pByte[n].R = rlimit_table[m_Y[y] + m_CrToR[cr]];
+ }
+ pos += WIDTH;
+ }
+ } else {
+ for (i = 0; i < nBlocksInMcu - 2; i++) py420[i] = pYCbCr + i * 64;
+ pcb = pYCbCr + (nBlocksInMcu - 2) * 64;
+ pcr = pcb + 64;
+
+ pixel_x = txb * 16;
+ pixel_y = tyb * 16;
+ pos = (pixel_y * WIDTH) + pixel_x;
+
+ for (j = 0; j < 16; j++) {
+ for (i = 0; i < 16; i++) {
+ // block number is ((j/8) * 2 + i/8)={0, 1, 2, 3}
+ y = *(py420[(j >> 3) * 2 + (i >> 3)]++);
+ m = ((j >> 1) << 3) + (i >> 1);
+ cb = pcb[m];
+ cr = pcr[m];
+ n = pos + i;
+ pByte[n].B = rlimit_table[m_Y[y] + m_CbToB[cb]];
+ pByte[n].G = rlimit_table[m_Y[y] + m_CbToG[cb] + m_CrToG[cr]];
+ pByte[n].R = rlimit_table[m_Y[y] + m_CrToR[cr]];
+ }
+ pos += WIDTH;
+ }
+ }
+ }
+ int Decompress(int txb, int tyb, char *outBuf, uint8_t QT_TableSelection) {
+ unsigned char *ptr;
+ unsigned char byTileYuv[768] = {};
+
+ memset(DCT_coeff, 0, 384 * 2);
+ ptr = byTileYuv;
+ process_Huffman_data_unit(YDC_nr, YAC_nr, &DCY, 0);
+ IDCT_transform(DCT_coeff, ptr, QT_TableSelection);
+ ptr += 64;
+
+ if (yuvmode == YuvMode::YUV420) {
+ process_Huffman_data_unit(YDC_nr, YAC_nr, &DCY, 64);
+ IDCT_transform(DCT_coeff + 64, ptr, QT_TableSelection);
+ ptr += 64;
+
+ process_Huffman_data_unit(YDC_nr, YAC_nr, &DCY, 128);
+ IDCT_transform(DCT_coeff + 128, ptr, QT_TableSelection);
+ ptr += 64;
+
+ process_Huffman_data_unit(YDC_nr, YAC_nr, &DCY, 192);
+ IDCT_transform(DCT_coeff + 192, ptr, QT_TableSelection);
+ ptr += 64;
+
+ process_Huffman_data_unit(CbDC_nr, CbAC_nr, &DCCb, 256);
+ IDCT_transform(DCT_coeff + 256, ptr, QT_TableSelection + 1);
+ ptr += 64;
+
+ process_Huffman_data_unit(CrDC_nr, CrAC_nr, &DCCr, 320);
+ IDCT_transform(DCT_coeff + 320, ptr, QT_TableSelection + 1);
+ } else {
+ process_Huffman_data_unit(CbDC_nr, CbAC_nr, &DCCb, 64);
+ IDCT_transform(DCT_coeff + 64, ptr, QT_TableSelection + 1);
+ ptr += 64;
+
+ process_Huffman_data_unit(CrDC_nr, CrAC_nr, &DCCr, 128);
+ IDCT_transform(DCT_coeff + 128, ptr, QT_TableSelection + 1);
+ }
+
+ // YUVToRGB (txb, tyb, byTileYuv, (unsigned char *)outBuf);
+ // YUVBuffer for YUV record
+ YUVToRGB(txb, tyb, byTileYuv, YUVBuffer.data(), (unsigned char *)outBuf);
+ if (txb == 0 && tyb == 0) {
+ for (int i=0; i < 10; i++) {
+ auto pixel = YUVBuffer[i];
+ std::cout << "YUBuffer " << static_cast<int>(pixel.R) << " "
+ << static_cast<int>(pixel.G) << static_cast<int>(pixel.B)
+ << "\n";
+ }
+ }
+ return 1;
+ }
+
+ int Decompress_2PASS(int txb, int tyb, char *outBuf,
+ uint8_t QT_TableSelection) {
+ unsigned char *ptr;
+ unsigned char byTileYuv[768];
+ memset(DCT_coeff, 0, 384 * 2);
+
+ ptr = byTileYuv;
+ process_Huffman_data_unit(YDC_nr, YAC_nr, &DCY, 0);
+ IDCT_transform(DCT_coeff, ptr, QT_TableSelection);
+ ptr += 64;
+
+ process_Huffman_data_unit(CbDC_nr, CbAC_nr, &DCCb, 64);
+ IDCT_transform(DCT_coeff + 64, ptr, QT_TableSelection + 1);
+ ptr += 64;
+
+ process_Huffman_data_unit(CrDC_nr, CrAC_nr, &DCCr, 128);
+ IDCT_transform(DCT_coeff + 128, ptr, QT_TableSelection + 1);
+
+ YUVToBuffer(txb, tyb, byTileYuv, YUVBuffer.data(), (unsigned char *)outBuf);
+ // YUVToRGB (txb, tyb, byTileYuv, (unsigned char *)outBuf);
+
+ return 1;
+ }
+
+ int VQ_Decompress(int txb, int tyb, char *outBuf, uint8_t QT_TableSelection,
+ struct COLOR_CACHE *VQ) {
+ unsigned char *ptr, i;
+ unsigned char byTileYuv[192];
+ int Data;
+
+ ptr = byTileYuv;
+ if (VQ->BitMapBits == 0) {
+ for (i = 0; i < 64; i++) {
+ ptr[0] = (VQ->Color[VQ->Index[0]] & 0xFF0000) >> 16;
+ ptr[64] = (VQ->Color[VQ->Index[0]] & 0x00FF00) >> 8;
+ ptr[128] = VQ->Color[VQ->Index[0]] & 0x0000FF;
+ ptr += 1;
+ }
+ } else {
+ for (i = 0; i < 64; i++) {
+ Data = (int)lookKbits(VQ->BitMapBits);
+ ptr[0] = (VQ->Color[VQ->Index[Data]] & 0xFF0000) >> 16;
+ ptr[64] = (VQ->Color[VQ->Index[Data]] & 0x00FF00) >> 8;
+ ptr[128] = VQ->Color[VQ->Index[Data]] & 0x0000FF;
+ ptr += 1;
+ skipKbits(VQ->BitMapBits);
+ }
+ }
+ // YUVToRGB (txb, tyb, byTileYuv, (unsigned char *)outBuf);
+ YUVToRGB(txb, tyb, byTileYuv, YUVBuffer.data(), (unsigned char *)outBuf);
+
+ return 1;
+ }
+
+ void MoveBlockIndex(void) {
+ if (yuvmode == YuvMode::YUV444) {
+ txb++;
+ if (txb >= (int)(tmp_WIDTH / 8)) {
+ tyb++;
+ if (tyb >= (int)(tmp_HEIGHT / 8)) tyb = 0;
+ txb = 0;
+ }
+ } else {
+ txb++;
+ if (txb >= (int)(tmp_WIDTH / 16)) {
+ tyb++;
+ if (tyb >= (int)(tmp_HEIGHT / 16)) tyb = 0;
+ txb = 0;
+ }
+ }
+ }
+
+ void VQ_Initialize(struct COLOR_CACHE *VQ) {
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ VQ->Index[i] = i;
+ }
+ VQ->Color[0] = 0x008080;
+ VQ->Color[1] = 0xFF8080;
+ VQ->Color[2] = 0x808080;
+ VQ->Color[3] = 0xC08080;
+ }
+ void init_QT() {}
+
+ void Init_Color_Table() {
+ int i, x;
+ int nScale = 1L << 16; // equal to power(2,16)
+ int nHalf = nScale >> 1;
+
+#define FIX(x) ((int)((x)*nScale + 0.5))
+
+ /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */
+ /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */
+ /* Cr=>R value is nearest int to 1.597656 * x */
+ /* Cb=>B value is nearest int to 2.015625 * x */
+ /* Cr=>G value is scaled-up -0.8125 * x */
+ /* Cb=>G value is scaled-up -0.390625 * x */
+ for (i = 0, x = -128; i < 256; i++, x++) {
+ m_CrToR[i] = (int)(FIX(1.597656) * x + nHalf) >> 16;
+ m_CbToB[i] = (int)(FIX(2.015625) * x + nHalf) >> 16;
+ m_CrToG[i] = (int)(-FIX(0.8125) * x + nHalf) >> 16;
+ m_CbToG[i] = (int)(-FIX(0.390625) * x + nHalf) >> 16;
+ }
+ for (i = 0, x = -16; i < 256; i++, x++) {
+ m_Y[i] = (int)(FIX(1.164) * x + nHalf) >> 16;
+ }
+ // For Color Text Enchance Y Re-map. Recommend to disable in default
+ /*
+ for (i = 0; i < (VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate);
+ i++) {
+ temp = (double)i /
+ VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate;
+ temp1 = 1.0 / VideoEngineInfo->INFData.Gamma1Parameter;
+ m_Y[i] =
+ (BYTE)(VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate * pow (temp,
+ temp1));
+ if (m_Y[i] > 255) m_Y[i] = 255;
+ }
+ for (i = (VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate); i < 256;
+ i++) {
+ m_Y[i] =
+ (BYTE)((VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate) + (256 -
+ VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate) * ( pow((double)((i -
+ VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate) / (256 -
+ (VideoEngineInfo->INFData.Gamma1_Gamma2_Seperate))), (1.0 /
+ VideoEngineInfo->INFData.Gamma2Parameter)) ));
+ if (m_Y[i] > 255) m_Y[i] = 255;
+ }
+ */
+ }
+ void load_Huffman_table(Huffman_table *HT, unsigned char *nrcode,
+ unsigned char *value, unsigned short int *Huff_code) {
+ unsigned char k, j, i;
+ unsigned int code, code_index;
+
+ for (j = 1; j <= 16; j++) {
+ HT->Length[j] = nrcode[j];
+ }
+ for (i = 0, k = 1; k <= 16; k++)
+ for (j = 0; j < HT->Length[k]; j++) {
+ HT->V[WORD_hi_lo(k, j)] = value[i];
+ i++;
+ }
+
+ code = 0;
+ for (k = 1; k <= 16; k++) {
+ HT->minor_code[k] = (unsigned short int)code;
+ for (j = 1; j <= HT->Length[k]; j++) code++;
+ HT->major_code[k] = (unsigned short int)(code - 1);
+ code *= 2;
+ if (HT->Length[k] == 0) {
+ HT->minor_code[k] = 0xFFFF;
+ HT->major_code[k] = 0;
+ }
+ }
+
+ HT->Len[0] = 2;
+ i = 2;
+
+ for (code_index = 1; code_index < 65535; code_index++) {
+ if (code_index < Huff_code[i]) {
+ HT->Len[code_index] = (unsigned char)Huff_code[i + 1];
+ } else {
+ i = i + 2;
+ HT->Len[code_index] = (unsigned char)Huff_code[i + 1];
+ }
+ }
+ }
+ void init_jpg_table() {
+ init_QT();
+ Init_Color_Table();
+ prepare_range_limit_table();
+ load_Huffman_table(&HTDC[0], std_dc_luminance_nrcodes,
+ std_dc_luminance_values, DC_LUMINANCE_HUFFMANCODE);
+ load_Huffman_table(&HTAC[0], std_ac_luminance_nrcodes,
+ std_ac_luminance_values, AC_LUMINANCE_HUFFMANCODE);
+ load_Huffman_table(&HTDC[1], std_dc_chrominance_nrcodes,
+ std_dc_chrominance_values, DC_CHROMINANCE_HUFFMANCODE);
+ load_Huffman_table(&HTAC[1], std_ac_chrominance_nrcodes,
+ std_ac_chrominance_values, AC_CHROMINANCE_HUFFMANCODE);
+ }
+
+ void prepare_range_limit_table()
+ /* Allocate and fill in the sample_range_limit table */
+ {
+ int j;
+ rlimit_table = (unsigned char *)malloc(5 * 256L + 128);
+ /* First segment of "simple" table: limit[x] = 0 for x < 0 */
+ memset((void *)rlimit_table, 0, 256);
+ rlimit_table += 256; /* allow negative subscripts of simple table */
+ /* Main part of "simple" table: limit[x] = x */
+ for (j = 0; j < 256; j++) rlimit_table[j] = j;
+ /* End of simple table, rest of first half of post-IDCT table */
+ for (j = 256; j < 640; j++) rlimit_table[j] = 255;
+
+ /* Second half of post-IDCT table */
+ memset((void *)(rlimit_table + 640), 0, 384);
+ for (j = 0; j < 128; j++) rlimit_table[j + 1024] = j;
+ }
+
+ inline unsigned short int WORD_hi_lo(uint8_t byte_high, uint8_t byte_low) {
+ return (byte_high << 8) + byte_low;
+ }
+
+ // river
+ void process_Huffman_data_unit(uint8_t DC_nr, uint8_t AC_nr,
+ signed short int *previous_DC,
+ unsigned short int position) {
+ uint8_t nr = 0;
+ uint8_t k;
+ unsigned short int tmp_Hcode;
+ uint8_t size_val, count_0;
+ unsigned short int *min_code;
+ uint8_t *huff_values;
+ uint8_t byte_temp;
+
+ min_code = HTDC[DC_nr].minor_code;
+ // maj_code=HTDC[DC_nr].major_code;
+ huff_values = HTDC[DC_nr].V;
+
+ // DC
+ k = HTDC[DC_nr].Len[(unsigned short int)(codebuf >> 16)];
+ // river
+ // tmp_Hcode=lookKbits(k);
+ tmp_Hcode = (unsigned short int)(codebuf >> (32 - k));
+ skipKbits(k);
+ size_val = huff_values[WORD_hi_lo(k, (uint8_t)(tmp_Hcode - min_code[k]))];
+ if (size_val == 0)
+ DCT_coeff[position + 0] = *previous_DC;
+ else {
+ DCT_coeff[position + 0] = *previous_DC + getKbits(size_val);
+ *previous_DC = DCT_coeff[position + 0];
+ }
+
+ // Second, AC coefficient decoding
+ min_code = HTAC[AC_nr].minor_code;
+ // maj_code=HTAC[AC_nr].major_code;
+ huff_values = HTAC[AC_nr].V;
+
+ nr = 1; // AC coefficient
+ do {
+ k = HTAC[AC_nr].Len[(unsigned short int)(codebuf >> 16)];
+ tmp_Hcode = (unsigned short int)(codebuf >> (32 - k));
+ skipKbits(k);
+
+ byte_temp =
+ huff_values[WORD_hi_lo(k, (uint8_t)(tmp_Hcode - min_code[k]))];
+ size_val = byte_temp & 0xF;
+ count_0 = byte_temp >> 4;
+ if (size_val == 0) {
+ if (count_0 != 0xF) {
+ break;
+ }
+ nr += 16;
+ } else {
+ nr += count_0; // skip count_0 zeroes
+ DCT_coeff[position + dezigzag[nr++]] = getKbits(size_val);
+ }
+ } while (nr < 64);
+ }
+
+ unsigned short int lookKbits(uint8_t k) {
+ unsigned short int revcode;
+
+ revcode = (unsigned short int)(codebuf >> (32 - k));
+
+ return (revcode);
+ }
+
+ void skipKbits(uint8_t k) {
+ unsigned long readbuf;
+
+ if ((newbits - k) <= 0) {
+ readbuf = Buffer[buffer_index];
+ buffer_index++;
+ codebuf =
+ (codebuf << k) | ((newbuf | (readbuf >> (newbits))) >> (32 - k));
+ newbuf = readbuf << (k - newbits);
+ newbits = 32 + newbits - k;
+ } else {
+ codebuf = (codebuf << k) | (newbuf >> (32 - k));
+ newbuf = newbuf << k;
+ newbits -= k;
+ }
+ }
+
+ signed short int getKbits(uint8_t k) {
+ signed short int signed_wordvalue;
+
+ // river
+ // signed_wordvalue=lookKbits(k);
+ signed_wordvalue = (unsigned short int)(codebuf >> (32 - k));
+ if (((1L << (k - 1)) & signed_wordvalue) == 0) {
+ // neg_pow2 was previously defined as the below. It seemed silly to keep
+ // a table of values around for something
+ // THat's relatively easy to compute, so it was replaced with the
+ // appropriate math
+ // signed_wordvalue = signed_wordvalue - (0xFFFF >> (16 - k));
+ std::array<signed short int, 17> neg_pow2 = {
+ 0, -1, -3, -7, -15, -31, -63, -127,
+ -255, -511, -1023, -2047, -4095, -8191, -16383, -32767};
+
+ signed_wordvalue = signed_wordvalue + neg_pow2[k];
+ }
+ skipKbits(k);
+ return signed_wordvalue;
+ }
+ int init_JPG_decoding() {
+ byte_pos = 0;
+ load_quant_table(QT[0]);
+ load_quant_tableCb(QT[1]);
+ // Note: Added for Dual-JPEG
+ load_advance_quant_table(QT[2]);
+ load_advance_quant_tableCb(QT[3]);
+ return 1;
+ }
+
+ void set_quant_table(uint8_t *basic_table, uint8_t scale_factor,
+ uint8_t *newtable)
+ // Set quantization table and zigzag reorder it
+ {
+ uint8_t i;
+ long temp;
+ for (i = 0; i < 64; i++) {
+ temp = ((long)(basic_table[i] * 16) / scale_factor);
+ /* limit the values to the valid range */
+ if (temp <= 0L) temp = 1L;
+ if (temp > 255L) temp = 255L; /* limit to baseline range if requested */
+ newtable[zigzag[i]] = (uint8_t)temp;
+ }
+ }
+
+ void updatereadbuf(uint32_t *codebuf, uint32_t *newbuf, int walks,
+ int *newbits, std::vector<uint32_t> &Buffer) {
+ unsigned long readbuf;
+
+ if ((*newbits - walks) <= 0) {
+ readbuf = Buffer[buffer_index];
+ buffer_index++;
+ *codebuf = (*codebuf << walks) |
+ ((*newbuf | (readbuf >> (*newbits))) >> (32 - walks));
+ *newbuf = readbuf << (walks - *newbits);
+ *newbits = 32 + *newbits - walks;
+ } else {
+ *codebuf = (*codebuf << walks) | (*newbuf >> (32 - walks));
+ *newbuf = *newbuf << walks;
+ *newbits -= walks;
+ }
+ }
+
+ uint32_t decode(std::vector<uint32_t> &buffer, unsigned long width,
+ unsigned long height, YuvMode yuvmode_in, int y_selector,
+ int uv_selector) {
+ uint32_t i;
+ COLOR_CACHE Decode_Color;
+
+ // TODO(ed) use the enum everywhere, not just externally
+ yuvmode = yuvmode_in; // 0 = YUV444, 1 = YUV420
+ Y_selector = y_selector; // 0-7
+ UV_selector = uv_selector; // 0-7
+
+ // TODO(ed) Magic number section. Document appropriately
+ advance_selector = 0; // 0-7
+ First_Frame = 1; // 0 or 1
+ Mapping = 0; // 0 or 1
+ /*
+ if (yuvmode == YuvMode::YUV420) {
+ Y_selector = 4;
+ UV_selector = 7;
+ Mapping = 0;
+ } else { // YUV444
+ Y_selector = 7;
+ UV_selector = 7;
+ Mapping = 0;
+ }
+ */
+ auto test = static_cast<int>(yuvmode);
+ std::cout << "YUVmode " << test << " " << static_cast<int>(Y_selector) << static_cast<int>(UV_selector) << "\n";
+
+ tmp_WIDTH = width;
+ tmp_HEIGHT = height;
+ WIDTH = width;
+ HEIGHT = height;
+
+ VQ_Initialize(&Decode_Color);
+ // OutputDebugString ("In decode\n");
+ // GetINFData (VideoEngineInfo);
+ // WIDTH = VideoEngineInfo->SourceModeInfo.X = 640;
+ // HEIGHT = VideoEngineInfo->SourceModeInfo.Y = 480;
+ // AST2000 JPEG block is 16x16(pixels) base
+ if (yuvmode == YuvMode::YUV420) {
+ if (WIDTH % 16) {
+ WIDTH = WIDTH + 16 - (WIDTH % 16);
+ }
+ if (HEIGHT % 16) {
+ HEIGHT = HEIGHT + 16 - (HEIGHT % 16);
+ }
+ } else {
+ if (WIDTH % 8) {
+ WIDTH = WIDTH + 8 - (WIDTH % 8);
+ }
+ if (HEIGHT % 8) {
+ HEIGHT = HEIGHT + 8 - (HEIGHT % 8);
+ }
+ }
+
+ // tmp_WDITH, tmp_HEIGHT are for block position
+ // tmp_WIDTH = VideoEngineInfo->DestinationModeInfo.X;
+ // tmp_HEIGHT = VideoEngineInfo->DestinationModeInfo.Y;
+ if (yuvmode == YuvMode::YUV420) {
+ if (tmp_WIDTH % 16) {
+ tmp_WIDTH = tmp_WIDTH + 16 - (tmp_WIDTH % 16);
+ }
+ if (tmp_HEIGHT % 16) {
+ tmp_HEIGHT = tmp_HEIGHT + 16 - (tmp_HEIGHT % 16);
+ }
+ } else {
+ if (tmp_WIDTH % 8) {
+ tmp_WIDTH = tmp_WIDTH + 8 - (tmp_WIDTH % 8);
+ }
+ if (tmp_HEIGHT % 8) {
+ tmp_HEIGHT = tmp_HEIGHT + 8 - (tmp_HEIGHT % 8);
+ }
+ }
+
+ int qfactor = 16;
+
+ SCALEFACTOR = qfactor;
+ SCALEFACTORUV = qfactor;
+ ADVANCESCALEFACTOR = 16;
+ ADVANCESCALEFACTORUV = 16;
+
+ if (First_Frame == 1) {
+ init_jpg_table();
+ init_JPG_decoding();
+ }
+ // TODO(ed) cleanup cruft
+ Buffer = buffer.data();
+
+ codebuf = buffer[0];
+ newbuf = buffer[1];
+ buffer_index = 2;
+
+ txb = tyb = 0;
+ newbits = 32;
+ DCY = DCCb = DCCr = 0;
+
+ do {
+ auto block_header = static_cast<JpgBlock>((codebuf >> 28) & 0xFF);
+ switch (block_header) {
+ case JpgBlock::JPEG_NO_SKIP_CODE:
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_START_LENGTH, &newbits,
+ buffer);
+ Decompress(txb, tyb, (char *)OutBuffer.data(), 0);
+ break;
+ case JpgBlock::FRAME_END_CODE:
+ return 0;
+ break;
+ case JpgBlock::JPEG_SKIP_CODE:
+
+ txb = (codebuf & 0x0FF00000) >> 20;
+ tyb = (codebuf & 0x0FF000) >> 12;
+
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_SKIP_LENGTH, &newbits,
+ buffer);
+ Decompress(txb, tyb, (char *)OutBuffer.data(), 0);
+ break;
+ case JpgBlock::VQ_NO_SKIP_1_COLOR_CODE:
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_START_LENGTH, &newbits,
+ buffer);
+ Decode_Color.BitMapBits = 0;
+
+ for (i = 0; i < 1; i++) {
+ Decode_Color.Index[i] = ((codebuf >> 29) & VQ_INDEX_MASK);
+ if (((codebuf >> 31) & VQ_HEADER_MASK) == VQ_NO_UPDATE_HEADER) {
+ updatereadbuf(&codebuf, &newbuf, VQ_NO_UPDATE_LENGTH, &newbits,
+ buffer);
+ } else {
+ Decode_Color.Color[Decode_Color.Index[i]] =
+ ((codebuf >> 5) & VQ_COLOR_MASK);
+ updatereadbuf(&codebuf, &newbuf, VQ_UPDATE_LENGTH, &newbits,
+ buffer);
+ }
+ }
+ VQ_Decompress(txb, tyb, (char *)OutBuffer.data(), 0, &Decode_Color);
+ break;
+ case JpgBlock::VQ_SKIP_1_COLOR_CODE:
+ txb = (codebuf & 0x0FF00000) >> 20;
+ tyb = (codebuf & 0x0FF000) >> 12;
+
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_SKIP_LENGTH, &newbits,
+ buffer);
+ Decode_Color.BitMapBits = 0;
+
+ for (i = 0; i < 1; i++) {
+ Decode_Color.Index[i] = ((codebuf >> 29) & VQ_INDEX_MASK);
+ if (((codebuf >> 31) & VQ_HEADER_MASK) == VQ_NO_UPDATE_HEADER) {
+ updatereadbuf(&codebuf, &newbuf, VQ_NO_UPDATE_LENGTH, &newbits,
+ buffer);
+ } else {
+ Decode_Color.Color[Decode_Color.Index[i]] =
+ ((codebuf >> 5) & VQ_COLOR_MASK);
+ updatereadbuf(&codebuf, &newbuf, VQ_UPDATE_LENGTH, &newbits,
+ buffer);
+ }
+ }
+ VQ_Decompress(txb, tyb, (char *)OutBuffer.data(), 0, &Decode_Color);
+ break;
+
+ case JpgBlock::VQ_NO_SKIP_2_COLOR_CODE:
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_START_LENGTH, &newbits,
+ buffer);
+ Decode_Color.BitMapBits = 1;
+
+ for (i = 0; i < 2; i++) {
+ Decode_Color.Index[i] = ((codebuf >> 29) & VQ_INDEX_MASK);
+ if (((codebuf >> 31) & VQ_HEADER_MASK) == VQ_NO_UPDATE_HEADER) {
+ updatereadbuf(&codebuf, &newbuf, VQ_NO_UPDATE_LENGTH, &newbits,
+ buffer);
+ } else {
+ Decode_Color.Color[Decode_Color.Index[i]] =
+ ((codebuf >> 5) & VQ_COLOR_MASK);
+ updatereadbuf(&codebuf, &newbuf, VQ_UPDATE_LENGTH, &newbits,
+ buffer);
+ }
+ }
+ VQ_Decompress(txb, tyb, (char *)OutBuffer.data(), 0, &Decode_Color);
+ break;
+ case JpgBlock::VQ_SKIP_2_COLOR_CODE:
+ txb = (codebuf & 0x0FF00000) >> 20;
+ tyb = (codebuf & 0x0FF000) >> 12;
+
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_SKIP_LENGTH, &newbits,
+ buffer);
+ Decode_Color.BitMapBits = 1;
+
+ for (i = 0; i < 2; i++) {
+ Decode_Color.Index[i] = ((codebuf >> 29) & VQ_INDEX_MASK);
+ if (((codebuf >> 31) & VQ_HEADER_MASK) == VQ_NO_UPDATE_HEADER) {
+ updatereadbuf(&codebuf, &newbuf, VQ_NO_UPDATE_LENGTH, &newbits,
+ buffer);
+ } else {
+ Decode_Color.Color[Decode_Color.Index[i]] =
+ ((codebuf >> 5) & VQ_COLOR_MASK);
+ updatereadbuf(&codebuf, &newbuf, VQ_UPDATE_LENGTH, &newbits,
+ buffer);
+ }
+ }
+ VQ_Decompress(txb, tyb, (char *)OutBuffer.data(), 0, &Decode_Color);
+
+ break;
+ case JpgBlock::VQ_NO_SKIP_4_COLOR_CODE:
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_START_LENGTH, &newbits,
+ buffer);
+ Decode_Color.BitMapBits = 2;
+
+ for (i = 0; i < 4; i++) {
+ Decode_Color.Index[i] = ((codebuf >> 29) & VQ_INDEX_MASK);
+ if (((codebuf >> 31) & VQ_HEADER_MASK) == VQ_NO_UPDATE_HEADER) {
+ updatereadbuf(&codebuf, &newbuf, VQ_NO_UPDATE_LENGTH, &newbits,
+ buffer);
+ } else {
+ Decode_Color.Color[Decode_Color.Index[i]] =
+ ((codebuf >> 5) & VQ_COLOR_MASK);
+ updatereadbuf(&codebuf, &newbuf, VQ_UPDATE_LENGTH, &newbits,
+ buffer);
+ }
+ }
+ VQ_Decompress(txb, tyb, (char *)OutBuffer.data(), 0, &Decode_Color);
+
+ break;
+
+ case JpgBlock::VQ_SKIP_4_COLOR_CODE:
+ txb = (codebuf & 0x0FF00000) >> 20;
+ tyb = (codebuf & 0x0FF000) >> 12;
+
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_SKIP_LENGTH, &newbits,
+ buffer);
+ Decode_Color.BitMapBits = 2;
+
+ for (i = 0; i < 4; i++) {
+ Decode_Color.Index[i] = ((codebuf >> 29) & VQ_INDEX_MASK);
+ if (((codebuf >> 31) & VQ_HEADER_MASK) == VQ_NO_UPDATE_HEADER) {
+ updatereadbuf(&codebuf, &newbuf, VQ_NO_UPDATE_LENGTH, &newbits,
+ buffer);
+ } else {
+ Decode_Color.Color[Decode_Color.Index[i]] =
+ ((codebuf >> 5) & VQ_COLOR_MASK);
+ updatereadbuf(&codebuf, &newbuf, VQ_UPDATE_LENGTH, &newbits,
+ buffer);
+ }
+ }
+ VQ_Decompress(txb, tyb, (char *)OutBuffer.data(), 0, &Decode_Color);
+
+ break;
+ case JpgBlock::JPEG_SKIP_PASS2_CODE:
+ txb = (codebuf & 0x0FF00000) >> 20;
+ tyb = (codebuf & 0x0FF000) >> 12;
+
+ updatereadbuf(&codebuf, &newbuf, BLOCK_AST2100_SKIP_LENGTH, &newbits,
+ buffer);
+ Decompress_2PASS(txb, tyb, (char *)OutBuffer.data(), 2);
+
+ break;
+ default:
+ // TODO(ed) propogate errors upstream
+ return -1;
+ break;
+ }
+ MoveBlockIndex();
+
+ } while (buffer_index <= buffer.size());
+
+ return -1;
+ }
+
+#ifdef cimg_version
+ void dump_to_bitmap_file() {
+ cimg_library::CImg<unsigned char> image(WIDTH, HEIGHT, 1, 3);
+ for (int y = 0; y < WIDTH; y++) {
+ for (int x = 0; x < HEIGHT; x++) {
+ auto pixel = OutBuffer[x + (y * WIDTH)];
+ image(x, y, 0) = pixel.R;
+ image(x, y, 1) = pixel.G;
+ image(x, y, 2) = pixel.B;
+ }
+ }
+ image.save("/tmp/file2.bmp");
+ }
+#endif
+
+ private:
+ YuvMode yuvmode;
+ // WIDTH and HEIGHT are the modes your display used
+ unsigned long WIDTH;
+ unsigned long HEIGHT;
+ unsigned long tmp_HEIGHT;
+ unsigned long tmp_WIDTH;
+ unsigned char Y_selector;
+ int SCALEFACTOR;
+ int SCALEFACTORUV;
+ int ADVANCESCALEFACTOR;
+ int ADVANCESCALEFACTORUV;
+ int Mapping;
+ unsigned char UV_selector;
+ unsigned char advance_selector;
+ unsigned char First_Frame;
+ int byte_pos; // current byte position
+
+ // quantization tables, no more than 4 quantization tables
+ std::array<std::array<long, 64>, 4> QT;
+
+ // DC huffman tables , no more than 4 (0..3)
+ std::array<Huffman_table, 4> HTDC;
+ // AC huffman tables (0..3)
+ std::array<Huffman_table, 4> HTAC;
+ std::array<int, 256> m_CrToR;
+ std::array<int, 256> m_CbToB;
+ std::array<int, 256> m_CrToG;
+ std::array<int, 256> m_CbToG;
+ std::array<int, 256> m_Y;
+ unsigned long buffer_index;
+ uint32_t codebuf, newbuf, readbuf;
+ uint8_t *std_luminance_qt;
+ uint8_t *std_chrominance_qt;
+
+ signed short int DCY, DCCb, DCCr; // Coeficientii DC pentru Y,Cb,Cr
+ signed short int DCT_coeff[384];
+ // std::vector<signed short int> DCT_coeff; // Current DCT_coefficients
+ // quantization table number for Y, Cb, Cr
+ uint8_t YQ_nr = 0, CbQ_nr = 1, CrQ_nr = 1;
+ // DC Huffman table number for Y,Cb, Cr
+ uint8_t YDC_nr = 0, CbDC_nr = 1, CrDC_nr = 1;
+ // AC Huffman table number for Y,Cb, Cr
+ uint8_t YAC_nr = 0, CbAC_nr = 1, CrAC_nr = 1;
+ int txb, tyb;
+ int newbits;
+ uint8_t *rlimit_table;
+ std::vector<RGB> YUVBuffer;
+ // TODO(ed) this shouldn't exist. It is cruft that needs cleaning up'
+ uint32_t *Buffer;
+
+ public:
+ std::vector<RGB> OutBuffer;
+};
+} \ No newline at end of file
diff --git a/include/ast_video_puller.hpp b/include/ast_video_puller.hpp
new file mode 100644
index 0000000000..99c718b296
--- /dev/null
+++ b/include/ast_video_puller.hpp
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <vector>
+#include <assert.h>
+
+#include <ast_video_types.hpp>
+
+#include <video.h>
+#include <iostream>
+
+namespace AstVideo {
+class VideoPuller {
+ public:
+ VideoPuller() : image_info(){};
+
+ void initialize() {
+ std::cout << "Opening /dev/video\n";
+ video_fd = open("/dev/video", O_RDWR);
+ if (!video_fd) {
+ std::cout << "Failed to open /dev/video\n";
+ // TODO(Ed) throw exception?
+ } else {
+ std::cout << "Opened successfully\n";
+ }
+ }
+
+ RawVideoBuffer read_video() {
+ assert(video_fd != 0);
+ RawVideoBuffer raw;
+
+ IMAGE_INFO image_info{};
+ image_info.do_image_refresh = 1; // full frame refresh
+ image_info.qc_valid = 0; // quick cursor disabled
+ image_info.parameter.features.w = 0;
+ image_info.parameter.features.h = 0;
+ image_info.parameter.features.chrom_tbl = 0; // level
+ image_info.parameter.features.lumin_tbl = 0;
+ image_info.parameter.features.jpg_fmt = 1;
+ image_info.parameter.features.buf =
+ reinterpret_cast<unsigned char *>(raw.buffer.data());
+ image_info.crypttype = -1;
+ std::cout << "Writing\n";
+
+ int status;
+ /*
+ status = write(video_fd, reinterpret_cast<char*>(&image_info),
+ sizeof(image_info));
+ if (status != sizeof(image_info)) {
+ std::cout << "Write failed. Return: " << status << "\n";
+ perror("perror output:");
+ }
+
+ std::cout << "Write done\n";
+ */
+ std::cout << "Reading\n";
+ status = read(video_fd, reinterpret_cast<char *>(&image_info),
+ sizeof(image_info));
+ std::cout << "Reading\n";
+
+ if (status != 0) {
+ std::cout << "Read failed with status " << status << "\n";
+ }
+
+ raw.buffer.resize(image_info.len);
+
+ raw.height = image_info.parameter.features.h;
+ raw.width = image_info.parameter.features.w;
+ if (image_info.parameter.features.jpg_fmt == 422) {
+ raw.mode = YuvMode::YUV420;
+ } else {
+ raw.mode = YuvMode::YUV444;
+ }
+ return raw;
+ }
+
+ private:
+ int video_fd;
+ IMAGE_INFO image_info;
+};
+}
diff --git a/include/ast_video_types.hpp b/include/ast_video_types.hpp
new file mode 100644
index 0000000000..206a7ef512
--- /dev/null
+++ b/include/ast_video_types.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <vector>
+namespace AstVideo {
+enum class YuvMode { YUV444 = 0, YUV420 = 1 };
+
+class RawVideoBuffer {
+ public:
+ RawVideoBuffer() : buffer(1024 * 1024 * 10, 0){};
+ unsigned long height;
+ unsigned long width;
+ int y_selector;
+ int uv_selector;
+ YuvMode mode;
+ // TODO(ed) determine a more appropriate buffer size
+ std::vector<uint32_t> buffer;
+};
+} \ No newline at end of file
diff --git a/include/web_kvm.hpp b/include/web_kvm.hpp
index 40c51288ca..728dc0d2f3 100644
--- a/include/web_kvm.hpp
+++ b/include/web_kvm.hpp
@@ -2,6 +2,8 @@
#include <boost/endian/arithmetic.hpp>
#include <string>
+#include <ast_jpeg_decoder.hpp>
+#include <ast_video_puller.hpp>
#include <video.h>
namespace crow {
@@ -227,8 +229,8 @@ void request_routes(Crow<Middlewares...>& app) {
case VncState::AWAITING_CLIENT_INIT_msg: {
// Now send the server initialization
server_initialization_msg server_init_msg;
- server_init_msg.framebuffer_width = 640;
- server_init_msg.framebuffer_height = 480;
+ server_init_msg.framebuffer_width = 800;
+ server_init_msg.framebuffer_height = 600;
server_init_msg.pixel_format.bits_per_pixel = 32;
server_init_msg.pixel_format.is_big_endian = 0;
server_init_msg.pixel_format.is_true_color = 1;
@@ -271,6 +273,16 @@ void request_routes(Crow<Middlewares...>& app) {
data.data() + sizeof(client_to_server_msg_type));
if (!msg->incremental) {
+ // Todo(ed) lifecycle of the video puller and decoder
+ // should be
+ // with the websocket, not recreated every time
+ AstVideo::VideoPuller p;
+ p.initialize();
+ auto out = p.read_video();
+ AstVideo::AstJpegDecoder d;
+ d.decode(out.buffer, out.width, out.height, out.mode,
+ out.y_selector, out.uv_selector);
+
framebuffer_update_msg buffer_update_msg;
// If the viewer is requesting a full update, force write
@@ -279,27 +291,29 @@ void request_routes(Crow<Middlewares...>& app) {
framebuffer_rectangle this_rect;
this_rect.x = msg->x_position;
this_rect.y = msg->y_position;
- this_rect.width = msg->width;
- this_rect.height = msg->height;
+ this_rect.width = out.width;
+ this_rect.height = out.height;
this_rect.encoding =
static_cast<uint8_t>(encoding_type::raw);
- LOG(DEBUG) << "Encoding is" << this_rect.encoding;
+ LOG(DEBUG) << "Encoding is " << this_rect.encoding;
this_rect.data.reserve(this_rect.width *
this_rect.height * 4);
+ LOG(DEBUG) << "Width " << out.width << " Height "
+ << out.height;
+
+ for (int i = 0; i < 16; i++) {
+ auto pixel = d.OutBuffer[i];
+ LOG(DEBUG) << "R " << static_cast<int>(pixel.R) << " G "
+ << static_cast<int>(pixel.R) << " B "
+ << static_cast<int>(pixel.B) << " Reserved " << static_cast<int>(pixel.Reserved);
+ }
- for (unsigned int x_index = 0; x_index < this_rect.width;
- x_index++) {
- for (unsigned int y_index = 0;
- y_index < this_rect.height; y_index++) {
- this_rect.data.push_back(
- static_cast<uint8_t>(0)); // Blue
- this_rect.data.push_back(
- static_cast<uint8_t>(0)); // Green
- this_rect.data.push_back(static_cast<uint8_t>(
- x_index * 0xFF / msg->width)); // RED
- this_rect.data.push_back(
- static_cast<uint8_t>(0)); // UNUSED
- }
+ for (int i = 0; i < out.width * out.height; i++) {
+ auto& pixel = d.OutBuffer[i];
+ this_rect.data.push_back(pixel.B);
+ this_rect.data.push_back(pixel.G);
+ this_rect.data.push_back(pixel.R);
+ this_rect.data.push_back(0);
}
buffer_update_msg.rectangles.push_back(
@@ -308,7 +322,7 @@ void request_routes(Crow<Middlewares...>& app) {
conn.send_binary(serialized);
}
- }
+ } // TODO(Ed) handle error
}
diff --git a/src/ast_jpeg_decoder_test.cpp b/src/ast_jpeg_decoder_test.cpp
new file mode 100644
index 0000000000..bc6857e846
--- /dev/null
+++ b/src/ast_jpeg_decoder_test.cpp
@@ -0,0 +1,156 @@
+#include "ast_jpeg_decoder.hpp"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#ifdef BUILD_CIMG
+#define cimg_display 0
+#include <CImg.h>
+#endif
+
+using namespace testing;
+MATCHER_P2(IsBetween, a, b, std::string(negation ? "isn't" : "is") +
+ " between " + PrintToString(a) + " and " +
+ PrintToString(b)) {
+ return a <= arg && arg <= b;
+};
+
+TEST(AstJpegDecoder, AllBlue) {
+ AstVideo::RawVideoBuffer out;
+
+ // This binary blog was created on the aspeed hardware using a blue screen
+ // consisting of the color 0x8EFFFA in a web browser window
+ FILE *fp = fopen("test_resources/aspeedbluescreen.bin", "rb");
+ EXPECT_NE(fp, nullptr);
+ size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+ out.buffer.size() * sizeof(long), fp);
+ fclose(fp);
+
+ ASSERT_GT(bufferlen, 0);
+
+ out.y_selector = 0;
+ out.uv_selector = 0;
+ out.mode = AstVideo::YuvMode::YUV444;
+ out.width = 800;
+ out.height = 600;
+
+ AstVideo::AstJpegDecoder d;
+ d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+ out.uv_selector);
+
+ int tolerance = 16;
+
+ // All pixels should be blue (0x8EFFFA) to within a tolerance (due to jpeg
+ // compression artifacts and quanitization)
+ for (int i = 0; i < out.width * out.height; i++) {
+ AstVideo::RGB &pixel = d.OutBuffer[i];
+ EXPECT_GT(pixel.R, 0x8E - tolerance);
+ EXPECT_LT(pixel.R, 0x8E + tolerance);
+ EXPECT_GT(pixel.G, 0xFF - tolerance);
+ EXPECT_LT(pixel.G, 0xFF + tolerance);
+ EXPECT_GT(pixel.B, 0xF1 - tolerance);
+ EXPECT_LT(pixel.B, 0xF1 + tolerance);
+ }
+}
+
+TEST(AstJpegDecoder, AllBlack) {
+ AstVideo::RawVideoBuffer out;
+
+ // This binary blog was created on the aspeed hardware using a black screen
+ FILE *fp = fopen("test_resources/aspeedblackscreen.bin", "rb");
+ EXPECT_NE(fp, nullptr);
+ size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+ out.buffer.size() * sizeof(long), fp);
+ fclose(fp);
+
+ ASSERT_GT(bufferlen, 0);
+
+ out.y_selector = 0;
+ out.uv_selector = 0;
+ out.mode = AstVideo::YuvMode::YUV444;
+ out.width = 800;
+ out.height = 600;
+
+ AstVideo::AstJpegDecoder d;
+ d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+ out.uv_selector);
+
+ // All pixels should be blue (0x8EFFFA) to within a tolerance (due to jpeg
+ // compression artifacts and quanitization)
+ for (int x = 0; x < out.width; x++) {
+ for (int y = 0; y < out.height; y++) {
+ AstVideo::RGB pixel = d.OutBuffer[x + (y * out.width)];
+ ASSERT_EQ(pixel.R, 0x00) << "X:" << x << " Y: " << y;
+ ASSERT_EQ(pixel.G, 0x00) << "X:" << x << " Y: " << y;
+ ASSERT_EQ(pixel.B, 0x00) << "X:" << x << " Y: " << y;
+ }
+ }
+}
+
+TEST(AstJpegDecoder, TestColors) {
+ AstVideo::RawVideoBuffer out;
+
+ // This binary blog was created on the aspeed hardware using a blue screen
+ // consisting of the color 0x8EFFFA in a web browser window
+ FILE *fp = fopen("test_resources/ubuntu_444_800x600_0chrom_0lum.bin", "rb");
+ EXPECT_NE(fp, nullptr);
+ size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+ out.buffer.size() * sizeof(long), fp);
+ fclose(fp);
+
+ ASSERT_GT(bufferlen, 0);
+
+ out.y_selector = 0;
+ out.uv_selector = 0;
+ out.mode = AstVideo::YuvMode::YUV444;
+ out.width = 800;
+ out.height = 600;
+
+ AstVideo::AstJpegDecoder d;
+ d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+ out.uv_selector);
+
+ int tolerance = 16;
+
+ for (int i = 0; i < out.width * out.height; i++) {
+ AstVideo::RGB &pixel = d.OutBuffer[i];
+ EXPECT_GT(pixel.R, 0x8E - tolerance);
+ EXPECT_LT(pixel.R, 0x8E + tolerance);
+ EXPECT_GT(pixel.G, 0xFF - tolerance);
+ EXPECT_LT(pixel.G, 0xFF + tolerance);
+ EXPECT_GT(pixel.B, 0xF1 - tolerance);
+ EXPECT_LT(pixel.B, 0xF1 + tolerance);
+ }
+}
+// Tests the buffers around the screen aren't written to
+TEST(AstJpegDecoder, BufferLimits) {
+ AstVideo::RawVideoBuffer out;
+
+ // This binary blog was created on the aspeed hardware using a black screen
+ FILE *fp = fopen("test_resources/aspeedblackscreen.bin", "rb");
+ EXPECT_NE(fp, nullptr);
+ size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+ out.buffer.size() * sizeof(long), fp);
+ fclose(fp);
+
+ ASSERT_GT(bufferlen, 0);
+
+ out.y_selector = 0;
+ out.uv_selector = 0;
+ out.mode = AstVideo::YuvMode::YUV444;
+ out.width = 800;
+ out.height = 600;
+
+ AstVideo::AstJpegDecoder d;
+ d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+ out.uv_selector);
+ // Reserved pixel should be default value
+ for (auto &pixel : d.OutBuffer) {
+ EXPECT_EQ(pixel.Reserved, 0xAA);
+ }
+ // All pixels beyond the buffer should be zero
+ for (int i = out.width * out.height; i < d.OutBuffer.size(); i++) {
+ EXPECT_EQ(d.OutBuffer[i].R, 0x00) << "index:" << i;
+ EXPECT_EQ(d.OutBuffer[i].B, 0x00) << "index:" << i;
+ EXPECT_EQ(d.OutBuffer[i].G, 0x00) << "index:" << i;
+ }
+} \ No newline at end of file
diff --git a/src/getvideo_main.cpp b/src/getvideo_main.cpp
index 50889c9bbe..4f375ca025 100644
--- a/src/getvideo_main.cpp
+++ b/src/getvideo_main.cpp
@@ -9,120 +9,68 @@
#include <fcntl.h>
#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#define BUILD_CIMG
+#ifdef BUILD_CIMG
+#define cimg_display 0
+#include <CImg.h>
+#endif
-namespace AstVideo {
-class VideoPuller {
- public:
- VideoPuller() {}
+#include <ast_jpeg_decoder.hpp>
+#include <ast_video_puller.hpp>
- void initialize() {
- std::cout << "Opening /dev/video\n";
- video_fd = open("/dev/video", O_RDWR);
- if (!video_fd) {
- std::cout << "Failed to open /dev/video\n";
- } else {
- std::cout << "Opened successfully\n";
- }
-
- std::vector<unsigned char> buffer(1024 * 1024, 0);
-
- IMAGE_INFO image_info{};
- image_info.do_image_refresh = 1; // full frame refresh
- image_info.qc_valid = 0; // quick cursor disabled
- image_info.parameter.features.w = 800;
- image_info.parameter.features.h = 600;
- image_info.parameter.features.chrom_tbl = 0; // level
- image_info.parameter.features.lumin_tbl = 0;
- image_info.parameter.features.jpg_fmt = 1;
- image_info.parameter.features.buf = buffer.data();
- image_info.crypttype = -1;
- std::cout << "Writing\n";
-
- int status;
-
- status = write(video_fd, reinterpret_cast<char*>(&image_info),
- sizeof(image_info));
- if (status != 0) {
- std::cout << "Write failed. Return: " << status <<"\n";
- perror("perror output:");
- }
-
- std::cout << "Write done\n";
- //std::this_thread::sleep_for(std::chrono::milliseconds(2000));
-
- std::cout << "Reading\n";
- status = read(video_fd, reinterpret_cast<char*>(&image_info), sizeof(image_info));
- std::cout << "Reading\n";
-
- if (status != 0) {
- std::cout << "Read failed with status " << status << "\n";
- }
-
- auto pt = reinterpret_cast<char*>(&image_info);
+int main() {
+ std::cout << "Started\n";
+ AstVideo::RawVideoBuffer out;
+ bool have_hardware = false;
+ if( access( "/dev/video", F_OK ) != -1 ) {
+ AstVideo::VideoPuller p;
+ p.initialize();
+ out = p.read_video();
+ } else {
+ FILE *fp = fopen("/home/ed/screendata.bin", "rb");
+ if (fp) {
+ size_t newLen = fread(out.buffer.data(), sizeof(char),
+ out.buffer.size() * sizeof(long), fp);
+ if (ferror(fp) != 0) {
+ fputs("Error reading file", stderr);
+ }
+ fclose(fp);
+ out.buffer.resize(newLen);
+ out.mode = AstVideo::YuvMode::YUV444;
+ out.width = 800;
+ out.height = 600;
+ out.y_selector = 0;
+ out.uv_selector = 0;
- for (int i = 0; i < sizeof(image_info); i++) {
- std::cout << std::hex << std::setfill('0') << std::setw(2)
- << int(*(pt + i)) << " ";
}
+ }
- std::cout << "\nprinting buffer\n";
-
- for(int i = 0; i < 512; i++){
- if (i % 16 == 0){
- std::cout << "\n";
- }
- std::cout << std::hex << std::setfill('0') << std::setw(2)
- << int(buffer[i]) << " ";
+ FILE *fp = fopen("/tmp/screendata.bin", "wb");
+ fwrite(out.buffer.data(), sizeof(char),
+ out.buffer.size(), fp);
+
+ AstVideo::AstJpegDecoder d;
+ std::cout << "MODE " << static_cast<int>(out.mode);
+ d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+ out.uv_selector);
+ #ifdef BUILD_CIMG
+ cimg_library::CImg<unsigned char> image(out.width, out.height, 1,
+ 3 /*numchannels*/);
+ for (int y = 0; y < out.height; y++) {
+ for (int x = 0; x < out.width; x++) {
+ auto pixel = d.OutBuffer[x + (y * out.width)];
+ image(x, y, 0) = pixel.R;
+ image(x, y, 1) = pixel.G;
+ image(x, y, 2) = pixel.B;
}
-
- buffer.resize(image_info.len);
-
- std::ofstream f("/tmp/screen.jpg",std::ios::out | std::ios::binary);
-
- f.write(reinterpret_cast<char*>(buffer.data()), buffer.size());
-
- std::cout << "\n";
-
- std::cout << "typedef struct _video_features {\n";
- std::cout << "short jpg_fmt: " << image_info.parameter.features.jpg_fmt
- << "\n";
- std::cout << "short lumin_tbl;" << image_info.parameter.features.lumin_tbl
- << "\n";
- std::cout << "short chrom_tbl;" << image_info.parameter.features.chrom_tbl
- << "\n";
- std::cout << "short tolerance_noise;"
- << image_info.parameter.features.tolerance_noise << "\n";
- std::cout << "int w; 0X" << image_info.parameter.features.w << "\n";
- std::cout << "int h; 0X" << image_info.parameter.features.h << "\n";
-
- std::cout << "void* buf; 0X" << static_cast<void*>(image_info.parameter.features.buf) << "\n";
- // std::cout << "unsigned char *buf;" << image_info.parameter.features.buf
- // << "\n";
- std::cout << "} FEATURES_TAG;\n";
-
- std::cout << "typedef struct _image_info {";
- std::cout << "short do_image_refresh;" << image_info.do_image_refresh
- << "\n";
- std::cout << "char qc_valid;" << image_info.qc_valid << "\n";
- std::cout << "unsigned int len;" << image_info.len << "\n";
- std::cout << "int crypttype;" << image_info.crypttype << "\n";
- std::cout << "char cryptkey[16];" << image_info.cryptkey << "\n";
- std::cout << "union {\n";
- std::cout << " FEATURES_TAG features;\n";
- std::cout << " AST_CURSOR_TAG cursor_info;\n";
- std::cout << "} parameter;\n";
- std::cout << "} IMAGE_INFO;\n";
- std::cout << std::endl;
-
- close(video_fd);
}
- int video_fd;
-};
-}
+ image.save("/tmp/file2.bmp");
+ #endif
+
+ std::cout << "Done!\n";
-int main() {
- AstVideo::VideoPuller p;
- p.initialize();
return 1;
-} \ No newline at end of file
+}
diff --git a/src/test_resources/aspeedblackscreen.bin b/src/test_resources/aspeedblackscreen.bin
new file mode 100644
index 0000000000..537216dc34
--- /dev/null
+++ b/src/test_resources/aspeedblackscreen.bin
Binary files differ
diff --git a/src/test_resources/aspeedbluescreen.bin b/src/test_resources/aspeedbluescreen.bin
new file mode 100644
index 0000000000..829c674ef6
--- /dev/null
+++ b/src/test_resources/aspeedbluescreen.bin
Binary files differ
diff --git a/src/test_resources/ubuntu_444_800x600_0chrom_0lum.bin b/src/test_resources/ubuntu_444_800x600_0chrom_0lum.bin
new file mode 100644
index 0000000000..929540c365
--- /dev/null
+++ b/src/test_resources/ubuntu_444_800x600_0chrom_0lum.bin
Binary files differ
diff --git a/src/token_authorization_middleware.cpp b/src/token_authorization_middleware.cpp
index a5ecd2d73b..82a5bde33c 100644
--- a/src/token_authorization_middleware.cpp
+++ b/src/token_authorization_middleware.cpp
@@ -65,7 +65,9 @@ void TokenAuthorizationMiddleware::before_handle(crow::request& req,
std::random_device rand;
random_bytes_engine rbe;
std::string token('a', 20);
- std::generate(begin(token), end(token), std::ref(rbe));
+ // TODO(ed) for some reason clang-tidy finds a divide by zero error in cstdlibc here
+ // commented out for now. Needs investigation
+ std::generate(begin(token), end(token), std::ref(rbe)); // NOLINT
std::string encoded_token;
base64::base64_encode(token, encoded_token);
ctx.auth_token = encoded_token;
diff --git a/static/CMakeLists.txt b/static/CMakeLists.txt
index 7353ccc3a4..acfc8a8924 100644
--- a/static/CMakeLists.txt
+++ b/static/CMakeLists.txt
@@ -60,48 +60,52 @@ set(OTHER_ASSETS
)
set(STATIC_ASSETS_OUT "")
+set(MINIFIED_ASSETS_OUT "")
foreach(JAVASCRIPT_ASSET ${JAVASCRIPT_ASSETS})
- # if it's a debug build, don't minify
+
+ set(MINIFIED_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/${JAVASCRIPT_ASSET})
+ get_filename_component(FOLDERNAME ${MINIFIED_FILENAME} DIRECTORY)
+ # string(REGEX REPLACE "(\\.[^.]*$)" ".min\\1" OUTPUT_FILENAME ${OUTPUT_FILENAME})
+ file(MAKE_DIRECTORY "${FOLDERNAME}")
+ add_custom_command(OUTPUT ${MINIFIED_FILENAME}
+ COMMAND closure-compiler --language_in=ECMASCRIPT5
+ --js "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
+ --js_output_file "${MINIFIED_FILENAME}"
+ --compilation_level SIMPLE_OPTIMIZATIONS
+ --angular_pass
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
+ COMMENT "Minifying ${JAVASCRIPT_ASSET}"
+ )
+
+
+ list(APPEND MINIFIED_ASSETS_OUT ${MINIFIED_FILENAME})
+ # if it's a debug build, use the unminified version
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET})
else()
- set(OUTPUT_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/${JAVASCRIPT_ASSET})
- get_filename_component(FOLDERNAME ${OUTPUT_FILENAME} DIRECTORY)
- # string(REGEX REPLACE "(\\.[^.]*$)" ".min\\1" OUTPUT_FILENAME ${OUTPUT_FILENAME})
- file(MAKE_DIRECTORY "${FOLDERNAME}")
- # TODO only minify if not a debug build
- add_custom_command(OUTPUT ${OUTPUT_FILENAME}
- COMMAND closure-compiler --language_in=ECMASCRIPT5
- --js "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
- --js_output_file "${OUTPUT_FILENAME}"
- --compilation_level SIMPLE_OPTIMIZATIONS
- --angular_pass
- DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${JAVASCRIPT_ASSET}"
- COMMENT "Minifying ${JAVASCRIPT_ASSET}"
- )
- list(APPEND STATIC_ASSETS_OUT ${OUTPUT_FILENAME})
+ list(APPEND STATIC_ASSETS_OUT ${MINIFIED_FILENAME})
endif (CMAKE_BUILD_TYPE STREQUAL "Debug")
endforeach(JAVASCRIPT_ASSET)
# for now CSS is included as is
foreach(CSS_ASSET ${CSS_ASSETS})
- # if it's a debug build, don't minify
+ set(MINIFIED_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET})
+ get_filename_component(FOLDERNAME ${MINIFIED_FILENAME} DIRECTORY)
+ file(MAKE_DIRECTORY "${FOLDERNAME}")
+ add_custom_command(OUTPUT ${MINIFIED_FILENAME}
+ COMMAND cssnano
+ "${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET}"
+ "${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET}"
+
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET}"
+ COMMENT "Minifying ${CSS_ASSET}"
+ )
+ list(APPEND MINIFIED_ASSETS_OUT ${MINIFIED_FILENAME})
+ # if it's a debug build, use the unminified version
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET})
else()
- set(OUTPUT_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET})
- get_filename_component(FOLDERNAME ${OUTPUT_FILENAME} DIRECTORY)
- file(MAKE_DIRECTORY "${FOLDERNAME}")
- # TODO only minify if not a debug build
- add_custom_command(OUTPUT ${OUTPUT_FILENAME}
- COMMAND cssnano
- "${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET}"
- "${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET}"
-
- DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${CSS_ASSET}"
- COMMENT "Minifying ${CSS_ASSET}"
- )
- list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_BINARY_DIR}/${CSS_ASSET})
+ list(APPEND STATIC_ASSETS_OUT ${MINIFIED_FILENAME})
endif (CMAKE_BUILD_TYPE STREQUAL "Debug")
endforeach(CSS_ASSET)
@@ -116,7 +120,12 @@ foreach(OTHER_ASSET ${OTHER_ASSETS})
list(APPEND STATIC_ASSETS_OUT ${CMAKE_CURRENT_SOURCE_DIR}/${OTHER_ASSET})
endforeach(OTHER_ASSET)
-add_custom_target(minifyjs ALL DEPENDS ${STATIC_ASSETS_OUT})
+# this taret is the dependency that the build script uses to make sure everything is available
+add_custom_target(buildstatic ALL DEPENDS ${STATIC_ASSETS_OUT})
+
+# this target makes sure that assets are minified in debug builds, even if they aren't used
+add_custom_target(minifyassets ALL DEPENDS ${MINIFIED_ASSETS_OUT})
+
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated)