diff --git a/.gitignore b/.gitignore index 3a5c937..068d31c 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,18 @@ hs_err_pid* .directory +###Visual Studio### + +.vs/ +*.pdb +*.idb +*.ilk +x64/ +*.sln +*.vcxproj +*.vcxproj.* + + ###OSX### .DS_Store @@ -78,7 +90,6 @@ hs_err_pid* # Icon must end with two \r Icon - # Thumbnails ._* diff --git a/LICENSE b/LICENSE index 368341d..8d3c3d3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,14 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright (c) 2023 Stefan Haustein and Aaron Liu - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +This program is free software: you may copy, redistribute and/or modify it +under the terms of (at your option) either the Apache License, Version 2.0, +or the GNU General Public License as published by the Free Software Foundation, +version 3, or any later version. - 1. Definitions. +This file is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +For details, see either + for the Apache License, Version 2.0 +, or for the GNU GPL, version 3. diff --git a/README.md b/README.md index 1600c4a..2394c02 100644 --- a/README.md +++ b/README.md @@ -2,36 +2,48 @@ Small C++ program to display images in a (modern) terminal using RGB ANSI codes and unicode block graphic characters. -There are various similar tools (such as `timg`) using the unicode half block character to display two 24bit pixels per character cell. This program enhances the resolution by mapping 4x8 pixel cells to different unicode characters, using the following algorithm: - -For each 4x8 pixel cell of the (potentially downscaled) image: +There are various similar tools (such as `timg`) that use the unicode half block character to display two 24bit pixels per character cell. This program enhances the resolution by mapping 4x8 pixel cells to different unicode characters, using the following algorithm for each 4x8 pixel cell of the (potentially downscaled) image: 1. Find the color channel (R, G or B) that has the biggest range of values for the current cell 2. Split this range in the middle and create a corresponding bitmap for the cell -4. Compare the bitmap to the assumed bitmaps for various unicode block graphics characters -5. Re-calculate the foreground and background colors for the chosen character. +3. Compare the bitmap to the assumed bitmaps for various unicode block graphics characters +4. Re-calculate the foreground and background colors for the chosen character. See the difference by disabling this optimization using the `-0` option. Or just take a look at the comparison image at the end of this text. +## Usage + +```sh +tiv [options] [...] +``` + +The shell will expand wildcards. By default, thumbnails and file names will be displayed if more than one image is provided. For a list of options, run the command without any parameters or with `--help`. + ## News -- 2019-03-26: Exciting week: @Cableo has fixed output redirection, @boretom has added cross-compilation support to the build file and @AlanDeSmet has fixed tall thumbnails and greyscale images. -- 2019-01-14: Install via snap: `sudo snap install --edge tiv` -- 2020-04-09: @aaronliu0130 has added homebrew support. -- 2020-07-05: @cxwx has fixed homebrew support. -- 2021-05-21: @aaronliu0130 has added Apple Clang support. -- 2022-02-02: @aaronliu0130 added homebrew support from homebrew-core. +- 2020-10-22: The Java version is now **deprecated**. Development has long shifted to the C++ version since that was created, and the last meaningful update to it was in 2016. +- 2021-05-22: We now support Apple Clang, thanks to the C++ filesystem library being no longer experimental. Issue forms have also been added to the GitHub repository. +- 2023-09-29: Today marks the 40th anniversary of the GNU project. If you haven't learned the news concerning it and Stallman, please do. In project news, @aaronliu0130 will probably be developing this project from now on as the original author is inactive. Support for MSVC has been added and the repository is now under an Apache 2.0 or GPL3 dual license. CI building for each release will hopefully be setup soon. The main program has also adopted a mostly Google code-style because I (aaron) think it simply makes sense. +`SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later` ## Installation +> [!IMPORTANT] +> All installation methods require installing ImageMagick, a required dependency. Most package managers should install it automatically. + ### Build from source + +Our makefile currently only supports `g++`. It should be possible to compile `tiv` manually using any of your favorite compilers that support C++20 and Unix headers (`ioctl.h`, specifically) or `windows.h`. PRs are welcome. + ```sh -brew install imagemagick || yum install ImageMagick || sudo apt install imagemagick git clone https://github.com/stefanhaustein/TerminalImageViewer.git -cd TerminalImageViewer/src/main/cpp +cd TerminalImageViewer/src make + +# To move the tiv binary into your PATH (hopefully), also do sudo make install ``` + ### Homebrew Option 1: @@ -51,27 +63,25 @@ brew install terminalimageviewer sudo snap install --edge tiv -## Usage +## Packages - tiv [options] +- @aaronliu0130 has added homebrew support. +- @megamaced has created [an RPM for SUSE](https://build.opensuse.org/package/show/home:megamaced/terminalimageviewer) +- @bperel has created [a Docker image](https://hub.docker.com/r/bperel/terminalimageviewer) +- @teresaejunior has created a snapcraft.yaml file, which can build a Snap package with `sudo docker run -it --rm -v "$PWD:$PWD" -w "$PWD" snapcore/snapcraft sh -c 'apt-get update && snapcraft'`, and then installed with `sudo snap install --dangerous ./*.snap`. This is no longer supported. +- An AUR package is also available, though the maintainer appears to be inactive. Replacements coming soon. -The shell will expand wildcards. By default, thumbnails and file names will be displayed if more than one image is provided. To display a list of options, just run the command without any parameters. +## Contributions -## Packages / Contributions - -- megamaced has created a RPM for SUSE: - -- bperel has created a Docker image: - -- teresaejunior has created a snapcraft.yaml file, which can build a Snap package with `sudo docker run -it --rm -v "$PWD:$PWD" -w "$PWD" snapcore/snapcraft sh -c 'apt-get update && snapcraft'`, and then installed with `sudo snap install --dangerous ./*.snap`. -- aaronliu0130 has added brew support. +- 2019-03-26: Exciting week: @Cableo has fixed output redirection, @boretom has added cross-compilation support to the build file and @AlanDeSmet has fixed tall thumbnails and greyscale images. +- 2020-07-05: @cxwx has fixed homebrew support. I am happy to accept useful contributions under the Apache 2.0 licencse, but... - Before investing in larger contributions, please use an issue to discuss this - Pull requests should be as "atomic" as possible. I won't accept any pull request doing multiple things at once. -- This library currently only depends on ImageMagic as an image processing library and I'd prefer to keep it that way. -- Support for additional platforms, CPUs or similar will require somebody who is happy to help with maintenance, in particular, if I don't have access to it. +- This program currently only depends on CImg and ImageMagick as image processing libraries and I'd prefer to keep it that way. +- Support for additional platforms, CPUs or similar will require somebody who is happy to help with maintenance, in particular if I don't have access to it. ## Common problems @@ -82,14 +92,14 @@ I am happy to accept useful contributions under the Apache 2.0 licencse, but... ## Examples +Most examples were shot with the Java version of this program, which should have equivalent output but slower by millenia in CPU years. + ![Examples](https://i.imgur.com/yWRZ3yk.png) If multiple images match the filename spec, thumbnails are shown. ![Thumbnails](https://i.imgur.com/PTYgSqz.png) -## Comparison to Using Half-Block Characters Only - -The top image was generated with the character optimization disabled via the `-0` option. +For the example below, the top image was generated with the character optimization disabled via the `-0` option. ![Comparison](https://i.imgur.com/OzdCeh6.png) diff --git a/src/main/cpp/CImg.h b/src/CImg.h similarity index 66% rename from src/main/cpp/CImg.h rename to src/CImg.h index f5d4544..844a313 100644 --- a/src/main/cpp/CImg.h +++ b/src/CImg.h @@ -3,11 +3,11 @@ # File : CImg.h # ( C++ header file ) # - # Description : The C++ Template Image Processing Toolkit. + # Description : C++ Template Image Processing Toolkit. # This file is the main component of the CImg Library project. # ( http://cimg.eu ) # - # Project manager : David Tschumperle. + # Project manager : David Tschumperlé # ( http://tschumperle.users.greyc.fr/ ) # # A complete list of contributors is available in file 'README.txt' @@ -18,17 +18,17 @@ # # CeCILL-C # The CeCILL-C license is close to the GNU LGPL. - # ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html ) + # ( http://cecill.info/licences/Licence_CeCILL-C_V1-en.html ) # # or CeCILL v2.1 # The CeCILL license is compatible with the GNU GPL. - # ( http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.html ) + # ( http://cecill.info/licences/Licence_CeCILL_V2.1-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". + # at the following URL: "http://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 @@ -54,7 +54,7 @@ // Set version number of the library. #ifndef cimg_version -#define cimg_version 233 +#define cimg_version 331 /*----------------------------------------------------------- # @@ -80,6 +80,8 @@ #include #include #include +#define cimg_str(x) #x +#define cimg_str2(x) cimg_str(x) // Detect/configure OS variables. // @@ -94,7 +96,7 @@ || defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \ || defined(__FreeBSD__) || defined (__DragonFly__) \ || defined(sgi) || defined(__sgi) \ - || defined(__MACOSX__) || defined(__APPLE__) \ + || defined(__OSX__) || defined(__MACOSX__) || defined(__APPLE__) \ || defined(__CYGWIN__) #define cimg_OS 1 #elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \ @@ -119,6 +121,7 @@ #pragma warning(push) #pragma warning(disable:4127) #pragma warning(disable:4244) +#pragma warning(disable:4307) #pragma warning(disable:4311) #pragma warning(disable:4312) #pragma warning(disable:4319) @@ -130,6 +133,7 @@ #pragma warning(disable:4800) #pragma warning(disable:4804) #pragma warning(disable:4820) +#pragma warning(disable:4995) #pragma warning(disable:4996) #ifndef _CRT_SECURE_NO_DEPRECATE @@ -146,19 +150,16 @@ // 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 #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 @@ -173,9 +174,6 @@ #include #include #elif cimg_OS==2 -#ifndef std_fopen -#define std_fopen cimg::win_fopen -#endif #ifndef NOMINMAX #define NOMINMAX #endif @@ -189,6 +187,7 @@ #include #include #include +enum {FALSE_WIN = 0}; #endif // Look for C++11 features. @@ -211,8 +210,21 @@ #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' ). +// Define own datatypes to ensure portability. +// ( 'sizeof(cimg_ulong/cimg_long) = sizeof(void*)' ). +#define cimg_uint8 unsigned char +#if defined(CHAR_MAX) && CHAR_MAX==255 +#define cimg_int8 signed char +#else +#define cimg_int8 char +#endif +#define cimg_uint16 unsigned short +#define cimg_int16 short +#define cimg_uint32 unsigned int +#define cimg_int32 int +#define cimg_float32 float +#define cimg_float64 double + #if cimg_OS==2 #define cimg_uint64 unsigned __int64 @@ -251,6 +263,14 @@ #endif +#ifndef cimg_max_buf_size +#if UINTPTR_MAX==0xffffffff +#define cimg_max_buf_size ((cimg_ulong)3*1024*1024*1024) +#else +#define cimg_max_buf_size ((cimg_ulong)16*1024*1024*1024) +#endif +#endif + // Configure filename separator. // // Filename separator is set by default to '/', except for Windows where it is '\'. @@ -284,6 +304,93 @@ #error (should be { 0=quiet | 1=console | 2=dialog | 3=console+warnings | 4=dialog+warnings }). #endif +// Configure OpenMP support. +// (http://www.openmp.org) +// +// Define 'cimg_use_openmp' to enable OpenMP support (requires OpenMP 3.0+). +// +// OpenMP directives are used in many CImg functions to get +// advantages of multi-core CPUs. +#if !defined(cimg_use_openmp) +#ifdef _OPENMP +#define cimg_use_openmp 1 +#else +#define cimg_use_openmp 0 +#endif +#else +#undef cimg_use_openmp +#define cimg_use_openmp 1 +#endif +#if cimg_use_openmp!=0 +#include +#define cimg_pragma_openmp(p) cimg_pragma(omp p) +#else +#define cimg_pragma_openmp(p) +#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) && cimg_use_openmp!=0 + +// Define abort macros to be used with OpenMP. +#ifndef _cimg_abort_init_openmp +#define _cimg_abort_init_openmp unsigned int _cimg_abort_go_openmp = 1; cimg::unused(_cimg_abort_go_openmp) +#endif +#ifndef _cimg_abort_try_openmp +#define _cimg_abort_try_openmp if (_cimg_abort_go_openmp) try +#endif +#ifndef _cimg_abort_catch_openmp +#define _cimg_abort_catch_openmp catch (CImgAbortException&) { \ + cimg_pragma_openmp(atomic) _cimg_abort_go_openmp&=0; \ +} +#endif +#ifndef _cimg_abort_catch_fill_openmp +#define _cimg_abort_catch_fill_openmp \ + catch (CImgException& e) { cimg_pragma(omp critical(abort)) CImg::string(e._message).move_to(is_error_expr); \ + cimg_pragma_openmp(atomic) _cimg_abort_go_openmp&=0; } +#endif +#ifdef cimg_abort_test2 +#ifndef _cimg_abort_try_openmp2 +#define _cimg_abort_try_openmp2 _cimg_abort_try_openmp +#endif +#ifndef _cimg_abort_catch_openmp2 +#define _cimg_abort_catch_openmp2 _cimg_abort_catch_openmp +#endif +#endif +#endif + +#ifndef _cimg_abort_init_openmp +#define _cimg_abort_init_openmp +#endif +#ifndef _cimg_abort_try_openmp +#define _cimg_abort_try_openmp +#endif +#ifndef _cimg_abort_catch_openmp +#define _cimg_abort_catch_openmp +#endif +#ifndef _cimg_abort_try_openmp2 +#define _cimg_abort_try_openmp2 +#endif +#ifndef _cimg_abort_catch_openmp2 +#define _cimg_abort_catch_openmp2 +#endif +#ifndef _cimg_abort_catch_fill_openmp +#define _cimg_abort_catch_fill_openmp +#endif +#ifndef cimg_abort_init +#define cimg_abort_init +#endif +#ifndef cimg_abort_test +#define cimg_abort_test +#endif +#ifndef cimg_abort_test2 +#define cimg_abort_test2 +#endif + // Configure display framework. // // Define 'cimg_display' to: '0' to disable display capabilities. @@ -302,70 +409,6 @@ #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_omp -#define _cimg_abort_init_omp bool _cimg_abort_go_omp = true; cimg::unused(_cimg_abort_go_omp) -#endif -#ifndef _cimg_abort_try_omp -#define _cimg_abort_try_omp if (_cimg_abort_go_omp) try -#endif -#ifndef _cimg_abort_catch_omp -#define _cimg_abort_catch_omp catch (CImgAbortException&) { cimg_pragma(omp atomic) _cimg_abort_go_omp&=false; } -#endif -#ifdef cimg_abort_test2 -#ifndef _cimg_abort_try_omp2 -#define _cimg_abort_try_omp2 _cimg_abort_try_omp -#endif -#ifndef _cimg_abort_catch_omp2 -#define _cimg_abort_catch_omp2 _cimg_abort_catch_omp -#endif -#ifndef _cimg_abort_catch_fill_omp -#define _cimg_abort_catch_fill_omp \ - catch (CImgException& e) { cimg_pragma(omp critical(abort)) CImg::string(e._message).move_to(is_error); \ - cimg_pragma(omp atomic) _cimg_abort_go_omp&=false; } -#endif -#endif -#endif - -#ifndef _cimg_abort_init_omp -#define _cimg_abort_init_omp -#endif -#ifndef _cimg_abort_try_omp -#define _cimg_abort_try_omp -#endif -#ifndef _cimg_abort_catch_omp -#define _cimg_abort_catch_omp -#endif -#ifndef _cimg_abort_try_omp2 -#define _cimg_abort_try_omp2 -#endif -#ifndef _cimg_abort_catch_omp2 -#define _cimg_abort_catch_omp2 -#endif -#ifndef _cimg_abort_catch_fill_omp -#define _cimg_abort_catch_fill_omp -#endif -#ifndef cimg_abort_init -#define cimg_abort_init -#endif -#ifndef cimg_abort_test -#define cimg_abort_test -#endif -#ifndef cimg_abort_test2 -#define cimg_abort_test2 -#endif -#ifndef std_fopen -#define std_fopen std::fopen -#endif - // Include display-specific headers. #if cimg_display==1 #include @@ -385,20 +428,6 @@ #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 -#define cimg_pragma_openmp(p) cimg_pragma(omp p) -#else -#define cimg_pragma_openmp(p) -#endif - // Configure OpenCV support. // (http://opencv.willowgarage.com/wiki/) // @@ -415,9 +444,23 @@ #undef False #define _cimg_redefine_False #endif +#ifdef Status +#undef Status +#define _cimg_redefine_Status +#endif #include -#include "cv.h" -#include "highgui.h" +#include +#if CV_MAJOR_VERSION>=3 +#define _cimg_fourcc cv::VideoWriter::fourcc +#define _cimg_cap_prop_frame_width cv::VideoCaptureProperties::CAP_PROP_FRAME_WIDTH +#define _cimg_cap_prop_frame_height cv::VideoCaptureProperties::CAP_PROP_FRAME_HEIGHT +#define _cimg_cap_prop_frame_count cv::VideoCaptureProperties::CAP_PROP_FRAME_COUNT +#else +#define _cimg_fourcc CV_FOURCC +#define _cimg_cap_prop_frame_width CV_CAP_PROP_FRAME_WIDTH +#define _cimg_cap_prop_frame_height CV_CAP_PROP_FRAME_HEIGHT +#define _cimg_cap_prop_frame_count CV_CAP_PROP_FRAME_COUNT +#endif #endif // Configure LibPNG support. @@ -464,6 +507,17 @@ extern "C" { } #endif +// Configure HEIF support +// (https://github.com/strukturag/libheif) +// +// Define 'cimg_use_heif' to enable HEIF support. +// +// HEIF library may be used to get a native support of '.heic' and '.avif' files. +// (see method 'CImg::load_heif()'). +#ifdef cimg_use_heif +#include +#endif + // Configure LibMINC2 support. // (http://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference) // @@ -531,7 +585,7 @@ extern "C" { // // Define 'cimg_use_board' to enable Board support. // -// Board library may be used to draw 3d objects in vector-graphics canvas +// 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::draw_object3d()'). #ifdef cimg_use_board @@ -546,11 +600,20 @@ extern "C" { // OpenEXR library may be used to get a native support of '.exr' files. // (see methods 'CImg::{load,save}_exr()'). #ifdef cimg_use_openexr +#if __GNUC__>=5 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wdeprecated-copy" +#pragma GCC diagnostic ignored "-Wshadow" +#endif #include "ImfRgbaFile.h" #include "ImfInputFile.h" #include "ImfChannelList.h" #include "ImfMatrixAttribute.h" #include "ImfArray.h" +#if __GNUC__>=5 +#pragma GCC diagnostic pop +#endif #endif // Configure TinyEXR support. @@ -610,18 +673,6 @@ extern "C" { #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. @@ -632,9 +683,9 @@ extern "C" { ------------------------------------------------------------------------------*/ // Macros to define program usage, and retrieve command line arguments. -#define cimg_usage(usage) cimg_library_suffixed::cimg::option((char*)0,argc,argv,(char*)0,usage,false) -#define cimg_help(str) cimg_library_suffixed::cimg::option((char*)0,argc,argv,str,(char*)0) -#define cimg_option(name,defaut,usage) cimg_library_suffixed::cimg::option(name,argc,argv,defaut,usage) +#define cimg_usage(usage) cimg_library::cimg::option((char*)0,argc,argv,(char*)0,usage,false) +#define cimg_help(str) cimg_library::cimg::option((char*)0,argc,argv,str,(char*)0) +#define cimg_option(name,_default,usage) cimg_library::cimg::option(name,argc,argv,_default,usage) // Macros to define and manipulate local neighborhoods. #define CImg_2x2(I,T) T I[4]; \ @@ -703,6 +754,55 @@ extern "C" { I##pcn = I##ccn = I##ncn = \ I##pnn = I##cnn = I##nnn = 0 +#define cimg_def2x2(img,x,y) \ + int _n1##x = x<(img).width() - 1?x + 1:(img).width() - 1, \ + _n1##y = y<(img).height() - 1?y + 1:(img).height() - 1 + +#define cimg_def3x3(img,x,y) \ + cimg_def2x2(img,x,y); \ + int _p1##x = x>1?x - 1:0, \ + _p1##y = y>1?y - 1:0 + +#define cimg_def4x4(img,x,y) \ + cimg_def3x3(img,x,y); \ + int _n2##x = x<(img).width() - 2?x + 2:(img).width() - 1, \ + _n2##y = y<(img).height() - 2?y + 2:(img).height() - 1 + +#define cimg_def5x5(img,x,y) \ + cimg_def4x4(img,x,y); \ + int _p2##x = x>2?x - 2:0, \ + _p2##y = y>2?y - 2:0 + +#define cimg_def6x6(img,x,y) \ + cimg_def5x5(img,x,y); \ + int _n3##x = x<(img).width() - 3?x + 3:(img).width() - 1, \ + _n3##y = y<(img).height() - 3?y + 3:(img).height() - 1 + +#define cimg_def7x7(img,x,y) \ + cimg_def6x6(img,x,y); \ + int _p3##x = x>3?x - 3:0, \ + _p3##y = y>3?y - 3:0 + +#define cimg_def8x8(img,x,y) \ + cimg_def7x7(img,x,y); \ + int _n4##x = x<(img).width() - 4?x + 4:(img).width() - 1, \ + _n4##y = y<(img).height() - 4?y + 4:(img).height() - 1 + +#define cimg_def9x9(img,x,y) \ + cimg_def8x8(img,x,y); \ + int _p4##x = x>4?x - 4:0, \ + _p4##y = y>4?y - 4:0 + +#define cimg_def2x2x2(img,x,y,z) \ + cimg_def2x2(img,x,y); \ + int _n1##z = z<(img).depth() - 1?z + 1:(img).depth() - 1 + +#define cimg_def3x3x3(img,x,y,z) \ + cimg_def2x2x2(img,x,y,z); \ + int _p1##x = x>1?x - 1:0, \ + _p1##y = y>1?y - 1:0, \ + _p1##z = z>1?z - 1: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) @@ -841,6 +941,7 @@ extern "C" { 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_rofoff(img,off) for (cimg_long off = (cimg_long)((img).size() - 1); off>=0; --off) #define cimg_for1(bound,i) for (int i = 0; i<(int)(bound); ++i) #define cimg_forX(img,x) cimg_for1((img)._width,x) @@ -1401,7 +1502,7 @@ extern "C" { (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); \ + 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], \ @@ -2169,6 +2270,7 @@ extern "C" { _p1##x = x++, ++_n1##x) #define cimglist_for(list,l) for (int l = 0; l<(int)(list)._width; ++l) +#define cimglist_rof(list,l) for (int l = (int)(list)._width - 1; l>=0; --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) @@ -2195,7 +2297,7 @@ extern "C" { /** 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 + Anyway, it should not happen often and you should reasonably start most of your \CImg-based programs with \code #include "CImg.h" @@ -2203,7 +2305,7 @@ extern "C" { \endcode to simplify the declaration of \CImg Library objects afterwards. **/ -namespace cimg_library_suffixed { +namespace cimg_library { // Declare the four classes of the CImg Library. template struct CImg; @@ -2212,12 +2314,12 @@ namespace cimg_library_suffixed { struct CImgException; // Declare cimg:: namespace. - // This is an uncomplete namespace definition here. It only contains some + // This is an incomplete 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. + // Define character 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 }; @@ -2256,7 +2358,7 @@ namespace cimg_library_suffixed { // '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) { + 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; @@ -2287,7 +2389,7 @@ namespace cimg_library_suffixed { return result; } - // Mutex-protected version of sscanf, sprintf and snprintf. + // Mutex-protected version of sscanf, snprintf and vnsprintf. // 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, ...) { @@ -2300,16 +2402,6 @@ namespace cimg_library_suffixed { 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; @@ -2339,7 +2431,7 @@ namespace cimg_library_suffixed { - \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 exception_mode(mode,true); } //! Return current \CImg exception mode. @@ -2347,7 +2439,18 @@ namespace cimg_library_suffixed { \note By default, return the value of configuration macro \c cimg_verbosity **/ inline unsigned int& exception_mode() { - return _exception_mode(0,false); + return exception_mode(0,false); + } + + inline unsigned int openmp_mode(const unsigned int value, const bool is_set) { +#if cimg_use_openmp!=0 + static unsigned int mode = 2; + if (is_set) { cimg::mutex(0); mode = value<2?value:2; cimg::mutex(0,0); } + return mode; +#else + cimg::unused(value,is_set); + return 0; +#endif } //! Set current \CImg openmp mode. @@ -2358,34 +2461,48 @@ namespace cimg_library_suffixed { - \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); + 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); + 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))) +#ifndef cimg_openmp_sizefactor +#define cimg_openmp_sizefactor 1 +#endif +#define cimg_openmp_if(cond) if ((cimg::openmp_mode()==1 || (cimg::openmp_mode()>1 && (cond)))) +#define cimg_openmp_if_size(size,min_size) cimg_openmp_if((size)>=(cimg_openmp_sizefactor)*(min_size)) +#ifdef _MSC_VER +// Disable 'collapse()' directive for MSVC (supports only OpenMP 2.0). +#define cimg_openmp_collapse(k) +#else +#define cimg_openmp_collapse(k) collapse(k) +#endif + +#if cimg_OS==2 +// Disable parallelization of simple loops on Windows, due to noticed performance drop. +#define cimg_openmp_for(instance,expr,min_size) cimg_rof((instance),ptr,T) *ptr = (T)(expr); +#else +#define cimg_openmp_for(instance,expr,min_size) \ + cimg_pragma_openmp(parallel for cimg_openmp_if_size((instance).size(),min_size)) \ + cimg_rof((instance),ptr,T) *ptr = (T)(expr); +#endif // 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); + 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); - } + } // namespace cimg { ... /*--------------------------------------- # @@ -2407,8 +2524,8 @@ namespace cimg_library_suffixed { This is probably one of the most thrown exception by \CImg. For instance, the following example throws a \c CImgArgumentException: \code - CImg 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. + CImg 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. @@ -2416,15 +2533,15 @@ namespace cimg_library_suffixed { - \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 img; // Define an empty image. - const float value = img.at(0); // Try to read first pixel value (does not exist). + const CImg 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. + - \b CImgIOException: Thrown when an error occurred 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 img("missing_file.jpg"); // Try to load a file that does not exist. + const CImg 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 @@ -2442,16 +2559,16 @@ namespace cimg_library_suffixed { 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 try { ... } catch () { ... } bloc, as in the following example: + using a usual try { ... } catch () { ... } block, as in the following example: \code #define "CImg.h" using namespace cimg_library; int main() { - cimg::exception_mode(0); // Enable quiet exception mode. + cimg::exception_mode(0); // Enable quiet exception mode try { - ... // Here, do what you want to stress CImg. + ... // 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. + std::fprintf(stderr,"CImg Library Error: %s",e.what()); // Display your custom error message ... // Do what you want now to save the ship! } } @@ -2464,16 +2581,16 @@ namespace cimg_library_suffixed { int size = cimg_vsnprintf(0,0,format,ap2); \ if (size++>=0) { \ delete[] _message; \ - _message = new char[size]; \ - cimg_vsnprintf(_message,size,format,ap); \ + _message = new char[(size_t)size]; \ + cimg_vsnprintf(_message,(size_t)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(); \ + if (cimg::exception_mode()>=3) cimg_library::cimg::info(); \ } \ } \ - va_end(ap); va_end(ap2); \ + va_end(ap); va_end(ap2); char *_message; CImgException() { _message = new char[1]; *_message = 0; } @@ -2494,7 +2611,7 @@ namespace cimg_library_suffixed { } //! Return a C-string containing the error message associated to the thrown exception. const char *what() const throw() { return _message; } - }; + }; // struct CImgException { ... // The CImgAbortException class is used to throw an exception when // a computationally-intensive function has been aborted by an external signal. @@ -2518,37 +2635,37 @@ namespace cimg_library_suffixed { } //! Return a C-string containing the error message associated to the thrown exception. const char *what() const throw() { return _message; } - }; + }; // struct CImgAbortException { ... // 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); } - }; + }; // struct CImgArgumentException { ... // 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); } - }; + }; // struct CImgDisplayException { ... // 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); } - }; + }; // struct CImgInstanceException { ... // 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); } - }; + }; // struct CImgIOException { ... // 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); } - }; + }; // struct CImgWarningException { ... /*------------------------------------- # @@ -2579,9 +2696,11 @@ namespace cimg_library_suffixed { static bool is_float() { return false; } static bool is_inf(const T) { return false; } static bool is_nan(const T) { return false; } + static bool is_finite(const T) { return true; } static T min() { return ~max(); } static T max() { return (T)1<<(8*sizeof(T) - 1); } static T inf() { return max(); } + static T nan() { return inf(); } 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"; } @@ -2589,13 +2708,18 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "bool"; return s; } + 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 is_finite(const bool) { return true; } static bool min() { return false; } static bool max() { return true; } static bool inf() { return max(); } + static bool nan() { return inf(); } 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"; } @@ -2604,13 +2728,15 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "unsigned char"; return s; } + static const char* string() { static const char *const s = "uint8"; 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 bool is_finite(const unsigned char) { return true; } static unsigned char min() { return 0; } static unsigned char max() { return (unsigned char)-1; } static unsigned char inf() { return max(); } + static unsigned char nan() { return inf(); } 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"; } @@ -2620,13 +2746,15 @@ namespace cimg_library_suffixed { #if defined(CHAR_MAX) && CHAR_MAX==255 template<> struct type { - static const char* string() { static const char *const s = "char"; return s; } + static const char* string() { static const char *const s = "uint8"; 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 bool is_finite(const char) { return true; } static char min() { return 0; } static char max() { return (char)-1; } static char inf() { return max(); } + static char nan() { return inf(); } static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } static const char* format() { return "%u"; } @@ -2635,13 +2763,15 @@ namespace cimg_library_suffixed { }; #else template<> struct type { - static const char* string() { static const char *const s = "char"; return s; } + static const char* string() { static const char *const s = "int8"; 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 bool is_finite(const char) { return true; } static char min() { return ~max(); } static char max() { return (char)((unsigned char)-1>>1); } static char inf() { return max(); } + static char nan() { return inf(); } 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"; } @@ -2650,13 +2780,15 @@ namespace cimg_library_suffixed { #endif template<> struct type { - static const char* string() { static const char *const s = "signed char"; return s; } + static const char* string() { static const char *const s = "int8"; 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 bool is_finite(const signed char) { return true; } 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 nan() { return inf(); } 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"; } @@ -2665,13 +2797,15 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "unsigned short"; return s; } + static const char* string() { static const char *const s = "uint16"; 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 bool is_finite(const unsigned short) { return true; } static unsigned short min() { return 0; } static unsigned short max() { return (unsigned short)-1; } static unsigned short inf() { return max(); } + static unsigned short nan() { return inf(); } 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"; } @@ -2680,13 +2814,15 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "short"; return s; } + static const char* string() { static const char *const s = "int16"; 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 bool is_finite(const short) { return true; } static short min() { return ~max(); } static short max() { return (short)((unsigned short)-1>>1); } static short inf() { return max(); } + static short nan() { return inf(); } 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"; } @@ -2694,13 +2830,15 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "unsigned int"; return s; } + static const char* string() { static const char *const s = "uint32"; 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 bool is_finite(const unsigned int) { return true; } static unsigned int min() { return 0; } static unsigned int max() { return (unsigned int)-1; } static unsigned int inf() { return max(); } + static unsigned int nan() { return inf(); } 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"; } @@ -2709,13 +2847,15 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "int"; return s; } + static const char* string() { static const char *const s = "int32"; 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 bool is_finite(const int) { return true; } static int min() { return ~max(); } - static int max() { return (int)((unsigned int)-1>>1); } + static int max() { return (int)(~0U>>1); } static int inf() { return max(); } + static int nan() { return inf(); } 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"; } @@ -2723,18 +2863,20 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "unsigned int64"; return s; } + static const char* string() { static const char *const s = "uint64"; 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 bool is_finite(const cimg_uint64) { return true; } static cimg_uint64 min() { return 0; } static cimg_uint64 max() { return (cimg_uint64)-1; } static cimg_uint64 inf() { return max(); } + static cimg_uint64 nan() { return inf(); } 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; } + static cimg_uint64 format(const cimg_uint64 val) { return val; } }; template<> struct type { @@ -2742,9 +2884,11 @@ namespace cimg_library_suffixed { 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 bool is_finite(const cimg_int64) { return true; } 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 nan() { return inf(); } static cimg_int64 cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; } @@ -2753,8 +2897,46 @@ namespace cimg_library_suffixed { static long format(const long val) { return (long)val; } }; +#if cimg_use_cpp11==1 && \ + (!(UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX)))) + template<> struct type { + static const char* string() { static const char *const s = "uint64"; 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 bool is_finite(const cimg_uint64) { return true; } + static cimg_uint64 min() { return 0; } + static cimg_uint64 max() { return (cimg_uint64)-1; } + static cimg_uint64 inf() { return max(); } + static cimg_uint64 nan() { return inf(); } + 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 cimg_uint64 format(const cimg_uint64 val) { return val; } + }; + + template<> struct type { + 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 bool is_finite(const cimg_int64) { return true; } + static long long min() { return ~max(); } + static long long max() { return (cimg_int64)((cimg_uint64)-1>>1); } + static long long inf() { return max(); } + static long long nan() { return max(); } + static long long 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; } + }; +#endif + template<> struct type { - static const char* string() { static const char *const s = "double"; return s; } + static const char* string() { static const char *const s = "float64"; return s; } static bool is_float() { return true; } static bool is_inf(const double val) { #ifdef isinf @@ -2773,6 +2955,13 @@ namespace cimg_library_suffixed { return (bool)isnan(val); #else return !(val==val); +#endif + } + static bool is_finite(const double val) { +#ifdef isfinite + return (bool)isfinite(val); +#else + return !is_nan(val) && !is_inf(val); #endif } static double min() { return -DBL_MAX; } @@ -2788,7 +2977,7 @@ namespace cimg_library_suffixed { #ifdef NAN return (double)NAN; #else - const double val_nan = -std::sqrt(-1.0); return val_nan; + const double val_nan = -std::sqrt(-1.); return val_nan; #endif } static double cut(const double val) { return val; } @@ -2798,7 +2987,7 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "float"; return s; } + static const char* string() { static const char *const s = "float32"; return s; } static bool is_float() { return true; } static bool is_inf(const float val) { #ifdef isinf @@ -2817,6 +3006,13 @@ namespace cimg_library_suffixed { return (bool)isnan(val); #else return !(val==val); +#endif + } + static bool is_finite(const float val) { +#ifdef isfinite + return (bool)isfinite(val); +#else + return !is_nan(val) && !is_inf(val); #endif } static float min() { return -FLT_MAX; } @@ -2831,7 +3027,7 @@ namespace cimg_library_suffixed { }; template<> struct type { - static const char* string() { static const char *const s = "long double"; return s; } + static const char* string() { static const char *const s = "float128"; return s; } static bool is_float() { return true; } static bool is_inf(const long double val) { #ifdef isinf @@ -2845,12 +3041,19 @@ namespace cimg_library_suffixed { return (bool)isnan(val); #else return !(val==val); +#endif + } + static bool is_finite(const long double val) { +#ifdef isfinite + return (bool)isfinite(val); +#else + return !is_nan(val) && !is_inf(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 nan() { const long double val_nan = -std::sqrt(-1.L); 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"; } @@ -2859,7 +3062,7 @@ namespace cimg_library_suffixed { #ifdef cimg_use_half template<> struct type { - static const char* string() { static const char *const s = "half"; return s; } + static const char* string() { static const char *const s = "float16"; return s; } static bool is_float() { return true; } static bool is_inf(const long double val) { #ifdef isinf @@ -2876,10 +3079,17 @@ namespace cimg_library_suffixed { } return cimg::type::is_nan((float)val); } + static bool is_finite(const half val) { +#ifdef isfinite + return (bool)isfinite(val); +#else + return !is_nan(val) && !is_inf(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 nan() { const half val_nan = (half)-std::sqrt(-1.); 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"; } @@ -2967,7 +3177,22 @@ namespace cimg_library_suffixed { template<> struct superset { typedef double type; }; template<> struct superset { typedef double type; }; template<> struct superset { typedef double type; }; +#if cimg_use_cpp11==1 && \ + (!(UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX)))) + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef cimg_int64 type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef double type; }; + template<> struct superset { typedef double type; }; +#endif + template<> struct superset { typedef double type; }; + template<> struct superset { typedef double type; }; template<> struct superset { typedef double type; }; + #ifdef cimg_use_half template<> struct superset { typedef float type; }; template<> struct superset { typedef float type; }; @@ -2991,12 +3216,13 @@ namespace cimg_library_suffixed { #define _cimg_Tt typename cimg::superset::type #define _cimg_Tfloat typename cimg::superset::type +#define _cimg_tfloat typename cimg::superset::type #define _cimg_Ttfloat typename cimg::superset2::type #define _cimg_Ttdouble typename cimg::superset2::type // Define variables used internally by CImg. #if cimg_display==1 - struct X11_info { + struct X11_static { unsigned int nb_wins; pthread_t *events_thread; pthread_cond_t wait_event; @@ -3007,13 +3233,14 @@ namespace cimg_library_suffixed { 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), + X11_static():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(); @@ -3021,6 +3248,7 @@ namespace cimg_library_suffixed { 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; @@ -3028,7 +3256,7 @@ namespace cimg_library_suffixed { #endif } - ~X11_info() { + ~X11_static() { delete[] wins; /* if (events_thread) { @@ -3041,65 +3269,75 @@ namespace cimg_library_suffixed { pthread_mutex_destroy(&wait_event_mutex); */ } - }; + }; // struct X11_static { ... #if defined(cimg_module) - X11_info& X11_attr(); + X11_static& X11_attr(); #elif defined(cimg_main) - X11_info& X11_attr() { static X11_info val; return val; } + X11_static& X11_attr() { static X11_static val; return val; } #else - inline X11_info& X11_attr() { static X11_info val; return val; } + inline X11_static& X11_attr() { static X11_static val; return val; } +#endif + +#elif cimg_display==2 + struct Win32_static { + HANDLE wait_event; + Win32_static() { wait_event = CreateEvent(0,FALSE_WIN,FALSE_WIN,0); } + }; // struct Win32_static { ... +#if defined(cimg_module) + Win32_static& Win32_attr(); +#elif defined(cimg_main) + Win32_static& Win32_attr() { static Win32_static val; return val; } +#else + inline Win32_static& Win32_attr() { static Win32_static val; return val; } +#endif #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) + struct Mutex_static { +#if cimg_OS==1 && (defined(cimg_use_pthread) || cimg_display==1) pthread_mutex_t mutex[32]; - Mutex_info() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); } + Mutex_static() { 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]); } +#elif cimg_OS==2 + HANDLE mutex[32]; + Mutex_static() { for (unsigned int i = 0; i<32; ++i) mutex[i] = CreateMutex(0,FALSE_WIN,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; } #else - Mutex_info() {} + Mutex_static() {} void lock(const unsigned int) {} void unlock(const unsigned int) {} int trylock(const unsigned int) { return 0; } #endif - }; + }; // struct Mutex_static { ... #if defined(cimg_module) - Mutex_info& Mutex_attr(); + Mutex_static& Mutex_attr(); #elif defined(cimg_main) - Mutex_info& Mutex_attr() { static Mutex_info val; return val; } + Mutex_static& Mutex_attr() { static Mutex_static val; return val; } #else - inline Mutex_info& Mutex_attr() { static Mutex_info val; return val; } + inline Mutex_static& Mutex_attr() { static Mutex_static val; return val; } #endif #if defined(cimg_use_magick) - static struct Magick_info { - Magick_info() { + struct Magick_static { + Magick_static() { Magick::InitializeMagick(""); } - } _Magick_info; + }; // struct Magick_static { ... + static Magick_static _Magick_static; +#endif + +#if defined(cimg_use_fftw3) && !defined(cimg_use_fftw3_singlethread) + struct FFTW3_static { + FFTW3_static() { + fftw_init_threads(); + } + }; // struct FFTW3_static { ... + static FFTW3_static _FFTW3_static; #endif #if cimg_display==1 @@ -3287,1475 +3525,2161 @@ namespace cimg_library_suffixed { #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). + 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 nynwkyw 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 { jw %xdxZwdw_wexfwYwkw 7yowoyFx=w " + "ry qw %wuw !xnwkwnwoyuwfwuw[wkwnwcwowrwpwdwuwoxuwpwkwnwoyuwRwkwnwbwpwNyoyoyoyoy;wdwnxpxtxowG|!ydwnwuwowtwow" + "pxswqxlwnxnxmwDwoyoxnyoymwp{oyq{pyoy>ypwqwpwp{oyqzo{q{pzrwrwowlwqwswpwnwqwsxswpypzoyqzozq}swrwrwqwtwswswtxsxswq" + "ws}qwnwkwnydwew_wfwdwkwmwowkw(w0wmwmwGwtwdxQw swuwnwo{q{pynwp|rwtwtwqydwcwcwcwmwmxgwqwpwnzpwuwpzoyRzoyoyexnynwd" + "z\\xnxgxrwsxrwsyswowmwmwmwmwmwmwo}ryp{q{q{q{nwmwnwmwozqxswpyoyoyoyoyeyuwswrwrwrwrwrwrwrwrwqwrwmwtwnwmwnwuwpwuyp" + "wuwoyZwmwnwuwowuwmwqwkwuwowuwoxnwuxowmwnwuwpwuypwuwZwmwnwuwowuwnwowmwtw\\wuwuwqwswqwswqwswqwswEwqwtweypzr~qyIw " + "rwswewnwuwowuwozswtwuwqwtwmwnwlwowuwuwowOxpxuxqwuwowswqwswoxpwlwjwqwswqwsw{+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])]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 `0`*^ $^.` <^F]F^F]G`G] " - " F\\S\\ ;b %a2a2a2a2a 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]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].] `K]>k]*]3]W]6^U^._V_;]Wa5]*]2\\V\\6]Wa7^V^ I]*]2\\V\\5^V^2]7]+^V^ @]W\\=v P[ 9\\1c_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] \\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]\\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 <] " - "\\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\\ ]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^ O]S]S]B]I]B]I]B]I]B]I]@]5^K^ @]4[ ;f8gAyAg] 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]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]]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[ 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_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_].].].].].].].].]-]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^].].].]+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_]-]E]Fm>k=]-rC].]" - ".b3].]U]S]U]H]T^R]D]C]G]M]?]C]G]N^^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<]]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]]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^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<]\\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]]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<]]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^]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<]]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 >] ]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[ ]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]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]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^^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^" - "_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]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^]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],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]]C]H]K`>kA])kA]J^Cm5" - "]2j7_2`M`K^J]9]8tC])].] PgX]>]Xf9h9fX]],fX]?]L]?].].]O^=].]M]M]N]L]qA^U]W]U^Di<]O`?k=]Xg:h3a7f>uCn?]/eSe;]:" - "]H]7]5k >] :a 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`>ki-]]C]H]K`]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`]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`y~}P{|k~-{|w~}k{|w~}Ww~|S{|k~X{|v~vv~|Y{|}k~}|Z{|y~" + "}y|xy|}w~| s{|}k~}|Z{|l~|V{}p~}\"{|y~}|w{|}w~|V{|}|u{|v~P{}x~} {{}h~} N{|~y}y|}x~|S{|v~}|y{|}w~}2{|w~y}x~|g{}x" + "~|k{|w~y}x~|g{}x~|kx}|w{|}w~}k{}x~}%{}t~|P{}t~|P{}t~|P{}t~|P{}t~|P{}t~}W{|[~}e{}f~}b{}c~|a{}c~|a{}c~|a{}c~|X{}w" + "~}M{}w~}M{}w~}M{}w~}Z{|d~}|`{}t~}kv~b{|g~}]{|g~}]{|g~}]{|g~}]{|g~}){|g~|{|w~|h{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f" + "{|v~h{}w~}f{|v~|j{|v~|b{}w~}L{|u~}|w{|}v~|W{|w~|Iw~}Qw~x{}x~|V{}y~}x{}s~|X{|v~|wv~}Vx~}v{|x~| D{}x~}I{}w~Q{}x~|" + "xw~U{}w~}w{|v~T{|w~|J{|w~Q{|x~}x{|x~|V{|v~vv~|T{}q~}|Wx~|x{}s~T{|w~I{|w~|R{|x~}x{}x~|Vx~}x{}s~|X{|v~vv~| Fw~}J{" + "|w~|R{|x~}x{|x~}Uv~|w{}w~}Q{|w~|Ww~}Hv~}w{}w~} Pw~}y{|x~}cY~ i{}y~|#{|w~}Qm~|`m~}w{|m~|\\{}v~| ;{}`~} -" + "{|r~x}t~}$v~}R{}x~}vw~}S{|w~t{|x~}U{|y~|_{|w~}w{}w~|n{}x~}_{|t~w}u~|Q{}x~}K{}w~N{}x~}Jx~ +{|w~Xs~y}s~|\\m~}X{}" + "f~\\{}g~}R{|s~}\\{|g~}Y{|i~|`{}c~|_{|s~w}s~}]{|s~x}s~ hr~}r~|[{|f~}Xs~}Y{}d~|\\{|c~}g{}b~|^{}c~|`{}e~_{|a~|g{" + "}w~}hv~|Y{}w~}M{}w~}W{}w~}n{|u~|_{}w~}V{}s~}jr~|h{}s~|lv~c{|p~}q~}^{}f~}_{|p~}q~}`{}e~[{}q~}p~dZ~g{|v~h{}w~}h{|" + "v~|f{|v~p{|v~m{|t~}m{}w~}m{|v~|m{}v~c{}v~jv~}e\\~]{|w~}Nw~}D{|w~|Sp~| ww~|!w~} `{|w~|${}w~}!w~}Cv~Lv~Tw~}Dv~ " + " Ov~ !{}w~}Mw~|N{|v~ :{}v~|s{|v~V{|t}|V{|t~s}w~| p{|v~ {{|v~|t{|v~|Vs~}W{}c~|_{}d~}c{|d~|W{|v~Y{}^~|iv~" + "}r{|v~qv~}f{|p~}q~}${}r~} v{}w~ v{}q~| ?y~}Ps~x}u~,v~k{}w~|Ww~|Su~}v|}w~X{|v~vv~|Z{}v~}y|wy|}v~}[{|}q{}x~} t{}" + "v~}y|wy|}v~}&{}w~|x{|w~}#y|r{}x~}Kw~|R{|w~ {{}p~}v|x~} H{}x~|S{}w~t{}w~|3x|x{}x~|h{|x~}j{|}|x{}x~|h{|x~}`{|w~l{" + "|w~$s~}Ps~}Ps~}Ps~}Ps~}Pr~W{}[~}g{|c~}c{}c~|a{}c~|a{}c~|a{}c~|X{}w~}M{}w~}M{}w~}M{}w~}Z{|b~}a{}s~|lv~c{|p~}q~}_" + "{|p~}q~}_{|p~}q~}_{|p~}q~}_{|p~}q~}+{|p~}q~}w~|g{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}e{}v~jv~}a{}w~}Lu~r{" + "|v~V{|w~J{}x~}Q{}x~|w{}x~Vx~|w{}u~}Vv|vv|U{}x~}x|}w~ Bw~|K{|w~|R{|x~}w{|x~}Vu|vv|S{|w~K{|w~|Qx~}v{}x~Uv|vv|T{|}" + "t~}|Tx~|w{|u~|S{}x~}Jw~}Qw~vw~Vx~|w{}u~}Vv|vv| Dw~|Kw~|Qw~v{}x~|Vv|vv|Pw~|Vw~}Hv|uv| G{|t}|P{|t}|P{|t}|P{|t}|P{" + "|t}|Lw~|xw~c{|[~} iy~}\"u~|S{|l~a{}l~|x{}l~]{}t~ ={|^~} .{|u~}|u{|}w~}$v~}R{}x~}vw~}S{}x~}t{}x~}Xy|y}y~y}x" + "|cw~}u{}w~o{|w~^u~}t{|}y~|Q{}x~}Kw~|N{|w~|T{}sx~s{} 4{}x~}Y{}v~}|v{}u~\\m~}X{}v~y}|wy|s~]{}x~}x|v{|}t~}Sr~}\\{" + "|v~k|Z{|t~}|v{|y}y~|`h|u~^t~|u{|}u~|^u~}|v{|}v~} iv~y|v{|t~]{|o~y}p~|[{|r~|Z{}w~}q|}s~]{|s~}|t{|}u~}g{}w~}r|y" + "}q~}_{}w~}h|_{}w~}j|`{|s~}|s{|}t~|g{}w~}hv~|Y{}w~}M{}w~}W{}w~}o{}u~|^{}w~}V{}r~k{|r~|h{}r~lv~d{|t~}|uy|s~_{}w~}" + "s|y}t~}a{|t~}|uy|s~a{}w~}s|y}s~]{}u~}|ty|}v~dn|}v~}n|g{|v~h{}w~}gv~}f{}w~}ov~|n{|t~}mv~|l{}v~|o{|v~|bv~}l{}v~dc" + "|u~}]{|w~}N{}w~D{|w~|T{}o~| x{|w~!w~} `{|w~|${}w~ w~} >w~}Dv~ Ov~ !{}w~|Mw~|M{}w~ :v~|q{}w~|Xp~}X{}v~|p{|" + "}| o{}w~| v~|r{|v~W{|r~|X{}v~}i|^{}w~}h|d{|s~}y|xy|}s~}[{|y}u~y}y|]{}w~}h|v~|iv~}r{|v~qv~}g{|t~}|uy|s~&{}p" + "~} w{}w~ w{}o~| @y~}Q{}v~}|u{|}y~,{|w~}m{|w~}Vw~|T{|v~|s{|}~({|w~}|o{|}w~|P{}x~| w{|w~}|o{|}w~|(x~}tw~ rw~K{}x" + "~|Rw~ {{}o~}w{|x~} H{}x~|T{|w~r{}x~}-{}x~|hw~|d{}x~|hw~|_{}x~|mw~|%{|r~|R{|r~|R{|r~|R{|r~|R{|r~|R{}r~|Y{|v~|y{|" + "v~}h|h{|s~}|t{|}u~}c{}w~}h|`{}w~}h|`{}w~}h|`{}w~}h|W{}w~}M{}w~}M{}w~}M{}w~}Z{|v~r|x}q~b{}r~lv~d{|t~}|uy|s~a{|t~" + "}|uy|s~a{|t~}|uy|s~a{|t~}|uy|s~a{|t~}|uy|s~-{|t~}|u{|}q~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}dv~}l{}v~`" + "{}w~}M{|v~p{}w~|V{}x~}L{}x~}Q{|x~|ux~}Wx~|v{|w~} {{}q~| Aw~|Lw~|Qw~u{}x~| y{|x~}Lw~|Q{}x~tx~}#{|}r~}Rx~u{|}y~}|" + "Q{}x~}L{}x~}Q{}x~|v{|x~}Wx~|v{}w~} j{|w~L{}x~}Q{}x~|u{}x~ x{}x~}Uw~} b{|}p~}|V{|}p~}|V{|}p~}|V{|}p~}|V{|}p~}|" + "P{|w~|xx|av~|fv~| j{|y~|#{}t~Sk~|c{|k~}y{|k~}_{|s~} ?{}t~}y| u{|u~|p{}y~}$v~}R{}x~}vw~}Sw~|tw~|[{|}m~}|h{" + "|w~sw~|p{}x~|_{}v~|q{|}|Q{}x~}L{}w~Lw~}U{}y~|ux~u{|y~}U{|x}| `w~|Z{|v~}s{|v~}]w~y}y|{}w~}X{}x~|p{|u~|^y}|n{|u~" + "|U{}x~y}w~}\\{|w~}K{|u~}o{}|Mv~|_{}v~}q{|u~_{}v~}r{|v~| jy~}|qu~|_{}t~}y|s{|}t~}\\{}w~}w~}Z{}w~}o{|u~}_{|t~|n" + "{|}x~}g{}w~}n{|}t~}`{}w~}L{}w~}P{|t~}m{|}w~|g{}w~}hv~|Y{}w~}M{}w~}W{}w~}p{}u~|]{}w~}V{}w~}w~|l{}r~|h{}r~|mv~e{|" + "u~}|p{|t~`{}w~}q{|}u~|c{|u~}|p{|t~b{}w~}p{}u~|_{|u~|n{|}y~W{|v~|Z{|v~h{}w~}g{|v~fv~|o{}w~}n{}x~}w~mv~|kv~}ov~}a" + "{|v~|n{|v~|M{}v~}\\{|w~}N{|w~|E{|w~|U{}v~}{|u~| x{|x~}\"w~} `{|w~|$v~ w~} >w~}Dv~ Ov~ !v~Lw~|M{}w~| <{|w~" + "}p{|w~}Xn~|Zv~ _{|v~ !{|w~}p{}w~}X{}w~}w~}W{}v~|M{}w~}R{|t~|p{|t~|_{|}l~}|`{}w~}hv~|iv~}r{|v~qv~}h{|u~}|p{|" + "t~({}n~} x{}w~ x{}m~| Ay~}R{|v~}p{}+{}w~|nv~Uw~|T{}w~| x{|w~|k{|w~|Q{|x~| x{|w~|k{|w~|*{|x~rx~|R{|w}Fw~Kw~|S{}" + "x~| {|n~}w{|x~} H{}x~|T{}x~}qw~|.{}x~|i{}x~}c{}x~|i{}x~}^{}x~|n{}x~}${}w~}w~}R{}w~}w~}R{}w~}w~}R{}w~}w~}R{}w~}w" + "~}Rv~|w~}Y{}w~}x{|v~U{|t~|n{|}x~}c{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~n{|}s~c{}r~|mv~e{|u~}|p{|" + "t~c{|u~}|p{|t~c{|u~}|p{|t~c{|u~}|p{|t~c{|u~}|p{|t~/{|u~}|p{}t~}e{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}d{|v" + "~|n{|v~|`{}w~}M{}w~}ow~}U{}x~|N{|w~Px~}t{|x~|Xx|sy| w{}s~| @{|w~M{}x~|Q{}x~|tw~ x{}x~}N{}x~|Q{|x~|t{|x~|&{}t~}v" + "~} t{}x~|N{|x~}Q{|x~}t{}x~|Xx|sy| g{|x~}N{|x~}Q{|x~}sx~} {{|x~}Tw~} d{|j~|Z{|j~|Z{|j~|Z{|j~|Z{|j~|R{|w~Z{}w~}" + "g{}w~} Ay|J{}y~#{|s~}Tk~}c{}j~|{}j~_q~| A{}u~} q{}v~|n{}~}$v~}R{}x~}vw~}Sw~t{|w~\\{|h~|i{}x~}s{}x~}q{|x~}^" + "v~|C{}x~}Lw~}L{}w~V{|v~|wx~w{|v~|V{}w~ a{|w~Yv~}q{|v~|^{}y|u{}w~}Xy}|m{|u~M{|v~}V{|w~|}w~}\\{|w~}Ku~|?{|v~^u~o" + "{}v~|a{|v~}p{}v~ j{~|nv~}`u~}|l{|}u~]v~{v~Z{}w~}mu~_u~}j{|y~}g{}w~}l{|}u~}a{}w~}L{}w~}Q{|u~}i{|}y~|g{}w~}hv~|" + "Y{}w~}M{}w~}W{}w~}q{}u~|\\{}w~}V{}w~|w~}lw~|v~|h{}q~mv~f{|u~}m{|u~}a{}w~}o{}v~}d{|u~}m{|u~}c{}w~}o{|u~_{}v~|j{|" + "W{|v~|Z{|v~h{}w~}fv~|h{}v~n{}w~}nw~|w~|o{|v~j{|v~}q{}v~_{}v~nv~}M{|u~[{|w~}Mw~}E{|w~|V{}v~}x{|u~| vw~} `{|w~|$" + "w~} w~} >w~}Dv~ Ov~ !v~Lw~|M{}w~| <{}w~|ow~}Xm~|[v~ ^v~| \"v~|p{|v~Xv~{v~V{}v~|N{}w~}Ru~}l{}u~|b{|g~}" + "|b{}w~}hv~|iv~}r{|v~qv~}i{|u~}m{|u~}*{}l~} y{}w~ y{}k~| By~}R{}v~ y{|w~}o{|w~}Uw~|T{}w~ x{|x~}g{}x~|R{|x~} y{|" + "x~}g{}x~|+{}y~}r{}y~}R{}w~Fx~}M{|}w~ Mm~}w{|x~} H{}x~|Tw~p{}x~|.{}x~|j{|w~b{}x~|j{|w~]w~n{|w~#v~{v~Rv~{v~Rv~{v~" + "Rv~{v~Rv~{v~S{|w~}{}w~|Zv~|x{|v~Uu~}j{|y~}c{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~k{}t~d{}q~mv~f{|" + "u~}m{|u~}e{|u~}m{|u~}e{|u~}m{|u~}e{|u~}m{|u~}e{|u~}m{|u~}1{|u~}m{|u~}e{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w" + "~}c{}v~nv~}_{}w~}Mv~n{}w~Tw}N{|x}P{|x}r{|x} F{|}x~}| ={|x}|O{|x}|Px}|s{|x}| xw|Nw|Pw|rw|'{|v~}|y{|v~} tw}Nw}P{|" + "x}rx}| 6w|Nw|Ox|rw| Nw~} e{}h~}\\{}h~}\\{}h~}\\{}h~}\\{}h~}S{|w~Z{|v~gv~| Ay~}L{|y~}${|q~}V{|j~ci~}|i~|a{}p~|" + "Oy|Uw|jw|Vu|Wv|kw|b{}v~} p{|v~|l{|}$v~}R{}x~}vw~}T{|x~}t{|x~}]{|g~|i{}x~|s{|w~qw~|^v~B{}x~}M{|w~|L{|w~}V{|}" + "w~}xx~x{}w~}|U{}w~ a{}w~Z{|v~o{}w~}U{}w~}X{|j{}v~|M{}v~Vw~}{}w~}\\{|w~}L{|v~|>v~}_{|v~|nv~}a{}v~nv~| \\{}w~}" + "b{|u~|h{|}v~|`{|w~}{}w~|[{}w~}m{|v~|a{}v~}gy}g{}w~}j{}u~|b{}w~}L{}w~}Q{}v~}f{|~|g{}w~}hv~|Y{}w~}M{}w~}W{}w~}r{}" + "u~|[{}w~}V{}w~y|w~m{|w~{v~|h{}w~}v~|nv~f{}v~}ju~|b{}w~}nu~d{}v~}ju~|d{}w~}n{}v~|`v~}D{|v~|Z{|v~h{}w~}f{}w~}hv~}" + "n{|v~o{|w~{}x~}o{}w~}i{}v~|s{|v~|^v~}p{}v~M{|u~|[{|w~}M{}x~}E{|w~|W{}v~|v{|u~| ww~} `{|w~|$w~} w~} >w~}Dv~ " + "Ov~ !v~Lw~|M{|w~| <{}w~|ow~}Xy~}w|}t~[v~| _{}w~} #{|w~}n{}w~|Z{|w~}{}w~|Vu~|O{}w~}S{}v~}j{}u~c{}d~|c{}w~" + "}hv~|iv~}r{|v~qv~}i{}v~}ju~|,{}v~y}w~|v~} {{}w~ {{}v~y}w~|u~| Cy~}R{}w~}R{|ey|_{}w~|pv~Tw~|T{}w~ y{|x~}e{}x~|\\" + "{|}p~} {{|x~}e{}x~|,{}y~}r{}y~}R{}w~G{}x~|Rq~| N{|m~}w{|x~} H{}x~|U{|w~p{|x~}.{}x~|j{}x~|b{}x~|j{}x~|_{|w~|n{}" + "x~|${|w~}{}w~|T{|w~}{}w~|T{|w~}{}w~|T{|w~}{}w~|T{|w~}{}w~|T{}w~|{|w~}[{|v~w{|v~V{}v~}gy}c{}w~}M{}w~}M{}w~}M{}w~" + "}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~j{|u~}e{}w~}v~|nv~f{}v~}ju~|f{}v~}ju~|f{}v~}ju~|f{}v~}ju~|f{}v~}ju~|c{}d{}|d{}v~}" + "k{}u~|f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}bv~}p{}v~^{}m~y}|Yv~o{|}w~ Py~}|u{|v~} 2w~} f{" + "}u~}x|{x|}t~^{}u~}x|{x|}t~^{}u~}x|{x|}t~^{}u~}x|{x|}t~^{}u~}x|{x|}t~T{|w~Yv~|i{|v~ A{}x~}M{}y~|$o~|W{|j~ch~}i~}" + "b{}n~T{|}t~y}|Zw~}kw~}X{}u~|X{}w~|m{}w~|d{|v~| ov~}j{|$v~}R{}x~}vw~}T{}x~}t{}x~}]u~}|{|y~|y{|y}x~|iw~|rw~r{" + "}x~}]v~B{}x~}Mv~Jv~T{|}w~|{x~{|w~}|S{}w~ aw~}Z{}w~}o{|v~U{}w~}Ev~}M{|v~W{}w~y{}w~}\\{|w~}Lv~}>{|v~|_{|v~m{}w~}" + "av~|n{|v~ 8{|y}6{|~|4{}v~c{|v~}d{|v~`{}w~|{|w~}[{}w~}lv~|b{|v~}e{|g{}w~}i{}u~b{}w~}L{}w~}R{|v~}dy|g{}w~}hv~|Y{}" + "w~}M{}w~}W{}w~}s{}u~Y{}w~}V{}w~|{w~|nw~}{v~|h{}w~y|v~nv~g{|v~}i{|u~b{}w~}n{|v~|f{|v~}i{|u~d{}w~}n{|v~|a{|v~C{|v" + "~|Z{|v~h{}w~}f{|v~|j{|v~|mv~|p{|w~{|x~}ov~|hv~}sv~}]{|v~|r{|v~|Mu~|Z{|w~}M{|w~E{|w~|X{}v~|t{|u~| xw~} `{|w~|$w" + "~} w~} >w~}Dv~ Ov~ !w~}Lw~|M{|w~| {|v~]{|v~m{}w~}b{|w~}l{}w~}W{|v}M{}v~D{}r~}6{|r~}|>{|v~|e{}w~|^{|w~|dv~w{|v~\\{}w~}lv~|c{}v~N{}w~}g{}v~|d{" + "}w~}L{}w~}S{}v~L{}w~}hv~|Y{}w~}M{}w~}W{}w~}vu~}V{}w~}V{}w~|yw~}pw~}yv~|h{}w~|y{}w~}pv~h{}v~e{}v~|d{}w~}mv~}g{}v" + "~e{}v~|f{}w~}mv~}a{|v~C{|v~|Z{|v~h{}w~}dv~|l{|v~k{|v~q{|w~x{}x~}q{}w~}e{}v~wv~}Y{|v~|v{|v~|N{|v~}W{|w~}L{|w~F{|" + "w~|[{}v~l{}v~ S{|}k~|Zw~}y{|o~}V{|k~|\\{|o~}y{|w~|\\{|m~}X{}k~}Y{|o~}y{|w~|`w~}y{|o~}Sv~Lv~Tw~}o{|v~}Wv~_w~}y{|" + "o~|v{|o~|ew~}y{|o~}Y{|}n~}|[w~}y{|o~}Y{|o~}y{|w~|Zw~}y{|r~|[{}j~[{}i~]{|w~|m{}w~|b{}w~|k{|w~}i{|w~}q{|u~|q{|w~|" + "h{|v~|o{|v~}b{}w~|k{|w~}`d~Uw~}Lw~|M{|w~| n{|o~}vw~|av~o{}w~|M{|v~[{|o~}|U{}k~}]w~}y{|o~}_u~|k{|w~}Wu~X{|w~|m{" + "}w~|dv~|h{|v~_{}x~}x{}s~}__~|dv~t{}w~t{|w~}\\{}n~}Y{|}e~}f{|`~b{|w~}l{}w~|\\v~w{|v~T{|u~R{}w~}U{}v~dv~}i{}u~u{|" + "v~u{|u~|g{}w~}hv~|iv~}r{|v~qv~|k{}v~e{}v~|c{~}I{|y~}w{}w~w{|y~}I{}~|U{}w~T{}~|k{}~|\\y~}w{}w~w{|y~| v~}P{}k~Z{|" + "v~S{|v~}x{|}v~}|y{|v~}^{|w~}u{|w~}Rw~|S{|u~}${}y~|v{}v~}|wy|}y~u{|y~}c{|x~}r{|x~}Q{|q{| W{}y~|uw~vy|v~u{|y~}-w~" + "|v{|w~Q{}w~K{|w~|I{|w~'{|w~|m{}w~|a{}m~}w{|x~} H{}x~|U{|x~}p{|x~}]{|q{|X{}x~|m{|w~_{}x~|m{|w~]{|}w~}q{|w~Pv~|Sv" + "~w{|v~Vv~w{|v~Vv~w{|v~Vv~w{|v~Vv~w{|v~W{|v~vv~^{|v~|v{|v~X{}v~J{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z" + "{|v~g{|v~}g{}w~|y{}w~}pv~h{}v~e{}v~|j{}v~e{}v~|j{}v~e{}v~|j{}v~e{}v~|j{}v~e{}v~|g{|u~l{}v~}g{}v~kw~}{}v~g{|v~h{" + "}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}`{|v~|v{|v~|\\{}w~}s|y}t~}_w~}u{|v~|Y{|}k~|Z{|}k~|Z{|}k~|Z{|}k~|Z{|}k~|Z{|" + "}k~|d{|}k~|v{|m~}_{|k~|[{|m~}W{|m~}W{|m~}W{|m~}Rv~Lv~Lv~Lv~Q{|}l~\\w~}y{|o~}Y{|}n~}|X{|}n~}|X{|}n~}|X{|}n~}|X{|" + "}n~}|S{}u~S{|}n~}{|x~}a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|b{}w~|k{|w~}aw~}y{|o~}^{}w~|k{|w~} X{|w~}" + "t{}w~t{|w~}f{|w~}h{|w~}f{|w~}yy|p{|}y{|w~}f{|w~}ly|y{|w~}f{|w~}h{|w~}X{}x~}X{|v~kv~| Cv~|Lx~&{|i~|Y{|m~}bU~|e{}" + "h~\\{|u~}|xy|}u~^w~}kw~}Yr~}X{}w~}ov~d{}w~ lv~| lv~}R{}x~}vw~}^{}Z~f{|w~|v{|y~|`w~|s{|w~tw~|[{|v~|D{}x~}Nw~" + "}H{}w~|Q{|t~|N{}w~ c{|w~|Zv~|lv~|W{}w~}E{}w~}M{}w~}Z{|w~|w{}w~}\\{|w~}N{|v~={}w~}\\v~|nv~|b{}w~}l{}v~W{}v~M{}v" + "~G{|}p~|6{|o~}@u~e{|w~|\\{}w~e{|w~}v{}w~|]{}w~}m{|v~|cv~}N{}w~}g{|v~}d{}w~}L{}w~}Sv~}L{}w~}hv~|Y{}w~}M{}w~}W{}w" + "~}x{|u~}U{}w~}V{}w~|y{}w~q{|w~|yv~|h{}w~|y{|v~pv~hv~}e{|v~}d{}w~}mv~}gv~}e{|v~}f{}w~}mv~}a{|v~|D{|v~|Z{|v~h{}w~" + "}d{}w~}l{}w~}jv~|r{|w~x{|x~}qv~|e{|v~}y{}v~W{}v~vv~}N{|u~V{|w~}Kw~|G{|w~|\\{}w~}j{}v~ T{}i~}[w~}{}m~}X{}j~|]{}m" + "~}{|w~|]{}j~Y{}k~}Z{}m~}{|w~|`w~}{|l~Tv~Lv~Tw~}p{}v~}Vv~_w~}{|m~|x{|m~|fw~}{|m~}[{|j~|\\w~}{}m~}[{}m~}{|w~|Zw~}" + "{|q~|\\{}i~[{}i~]{|w~|m{}w~|b{|w~}k{}w~|hw~}q{|u~}q{}w~|g{}v~ov~}a{|w~}k{}w~|`d~Uw~}Lw~|M{|w~| Gy|l{|Z{}m~}x{|w" + "~`v~p{|v~Kv~Z{|m~|X{}j~}]w~}{|l~`t~|l{}w~|X{|u~}Y{|w~|m{}w~|e{}v~f{}w~}b{|v~}y{|q~}`_~|dv~t{}w~t{|w~}^{|k~}[{|c" + "~}f{|`~b{}w~}l{}w~}]{|w~}vv~|T{|v~}S{}w~}Uv~}d{}v~j{|u~t{|v~t{|u~g{}w~}hv~|iv~}r{|v~r{|v~|kv~}e{|v~}dx~}I{|}v{}" + "w~v{|}I{}x~|V{}w~U{}x~|m{}x~|\\{|v{}w~vy| {{v~}R{|i~Z{|v~R{|v~}|q~}|v~}\\v~u{}w~Qw~|R{|t~|'{|y~}v{}w~}p{|t{}y~|" + "d{}x~|r{|x~}Ry}r{|~ X{|y~}tw~sw~|u{}y~|.{|w~}x|}w~|Q{}w~L{|w~|G{|x~}({|w~|m{}w~|a{}m~}w{|x~} H{}x~|U{|w~p{|x~}]" + "{~|r{|}Y{}x~|mw~|_{}x~|m{}x~|[{|w~|r{}x~|Pv~|T{|w~}v{}w~|X{|w~}v{}w~|X{|w~}v{}w~|X{|w~}v{}w~|X{|w~}v{}w~|X{}w~}" + "v{}w~}_{}w~}u{|v~Xv~}J{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~fu~g{}w~|y{|v~pv~hv~}e{|v~}jv~}e{|v~}" + "jv~}e{|v~}jv~}e{|v~}jv~}e{|v~}f{|u~n{}v~}fv~}l{}x~}y{|v~|h{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}_{}v~vv~}[" + "{}w~}q{|}u~|`w~}uv~W{}i~}[{}i~}[{}i~}[{}i~}[{}i~}[{}i~}e{}i~}x{}k~}a{}j~|\\{}j~Y{}j~Y{}j~Y{}j~Sv~Lv~Lv~Lv~R{}j~" + "}]w~}{|m~}[{|j~|Z{|j~|Z{|j~|Z{|j~|Z{|j~|T{}u~T{|f~`{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|b{|w~}k{}w~|a" + "w~}{}m~}_{|w~}k{}w~| Xw~}s{}w~s{}w~fw~}f{}w~fw~}y{|y~|r{|y~}y{}w~fw~}l{|y~}y{}w~fw~}f{}w~X{}x~}Wv~|m{|v~ C{}w~}" + "[{|}|o{|y~|&g~|Y{}n~|b{}V~e{|g~}]v~}r{|v~}_w~}kw~}Z{|r~}X{|v~p{|w~}dw~} pw|v~l| {{v~}R{}x~}vw~}^{}Z~f{|w~|v" + "{|y~|`{}x~}s{|x~}u{}x~}Y{}v~|E{}x~}O{|w~}H{}w~|S{|}r~}|P{}w~ c{|w~Yv~|lv~|W{}w~}Ev~|N{|v~|Zw~}v{}w~}\\{|w~}|}v" + "~y}|X{}w~}>{|v~|\\{}w~}o{|v~a{}w~}l{}v~W{}v~M{}v~J{|}p~}|2{|}p~}|D{}v~|e{}x~}p{|}w~}|vx|uw~|f{}w~|v{|w~}]{}w~}m" + "{}v~c{|v~|N{}w~}fv~}d{}w~}L{}w~}T{|v~|L{}w~}hv~|Y{}w~}M{}w~}W{}w~}y{|u~}T{}w~}V{}w~|y{|w~|r{}x~}xv~|h{}w~|x{}w~" + "}qv~i{|v~|dv~}d{}w~}mv~}h{|v~|dv~}f{}w~}n{|v~|`u~D{|v~|Z{|v~h{}w~}d{|v~m{|v~|j{}w~}r{}x~}x{|w~qv~|d{}v~y|v~|Vv~" + "}x{}v~Mu~|V{|w~}K{}x~}G{|w~|]{}w~}h{|v~ U{}u~v}s~}\\w~}|v~w}t~}Zr~v}v~|^{}t~w}v~}|w~|^{}t~v}t~Zv}v~s}[{}t~w}v~}" + "|w~|`w~}|u~x}t~}Uv~Lv~Tw~}q{}v~|Uv~_w~}|v~x}s~y{|v~x}s~fw~}|u~x}t~}]{|s~x}s~|]w~}|v~w}t~}]{|t~w}v~}|w~|Zw~}|t~}" + "x~|]{}t~u}u~[{|x}v~q}]{|w~|m{}w~|av~kv~g{}w~q{}t~qv~e{}v~q{}v~_v~|m{|v~_d~Uw~}Lw~|M{|w~| J{|}v~}r{}v~}|_{}u~w}u" + "~|y{}x~}`v~q{|v~}K{}w~|\\{}w~}p~}Z{}s~w}u~}]w~}|u~x}t~}as~m{|v~W{}t~Y{|w~|m{}w~|ev~|f{|v~c{|u~}yn~a_~|dv~t{}w~t" + "{|w~}_{|t~w}t~}]{|b~}f{|`~b{}w~|l{}w~}]{}w~|v{|w~}S{|v~}T{}w~}Uv~|d{|v~|k{}v~|t{|v~s{}v~|h{}w~}hv~|i{}w~}r{|v~r" + "{|v~|l{|v~|dv~}ev~}C{}w~C{}v~|W{}w~V{}v~n{|v~|W{}w~ sv~}S{|s~}y~x}v~Z{|v~Q{|e~}[{|w~}w{|w~}Qw~|R{}r~|){}y~|w{|w" + "~}g{|y~}dw~q{}x~}S{}~}s{}y~ X{}y~|tw~s{}x~}u{|y~}-{}p~}P{}w~M{|w~|F{|x~}({|w~|m{}w~|a{}m~}w{|x~} H{}x~|Tw~p{}x~" + "|]y~}s{|y~Z{}x~|n{|x~}^{}x~|n{|w~Y{|x~}s{|x~}Ov~|T{}w~|v{|w~}X{}w~|v{|w~}X{}w~|v{|w~}X{}w~|v{|w~}X{}w~|v{|w~}Xv" + "~u{|v~_v~|u{|v~Y{|v~|J{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~f{}v~g{}w~|x{}w~}qv~i{|v~|dv~}k{|v~|d" + "v~}k{|v~|dv~}k{|v~|dv~}k{|v~|dv~}e{|u~p{}v~}f{|v~|m{}w~wv~}h{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}^v~}x{}v" + "~Z{}w~}o{}v~}`w~}v{|w~|W{}u~v}s~}\\{}u~v}s~}\\{}u~v}s~}\\{}u~v}s~}\\{}u~v}s~}\\{}u~v}s~}f{}u~v}s~}{s~w}t~}cr~v}" + "v~|]{}t~v}t~[{}t~v}t~[{}t~v}t~[{}t~v}t~Tv~Lv~Lv~Lv~S{}h~|^w~}|u~x}t~}]{|s~x}s~|\\{|s~x}s~|\\{|s~x}s~|\\{|s~x}s~" + "|\\{|s~x}s~|U{}u~U{|s~x}q~|`{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|av~|m{|v~`w~}|v~w}t~}_v~|m{|v~ X{|w~" + "r{}w~rw~}h{|w~dw~}h{|w~y{|w~|t{|w~}yw~}h{|w~l{|w~}yw~}h{|w~dw~}Y{}x~}W{}w~}m{}w~} Xg|}v~s|e{|}x~}o{}y~&{}f~Y{|o" + "~}a{|V~f{|e~}_{|w~}p{|v~_w~}kw~}Z{}w~}v~Wv~|q{}w~}e{|w~ pc~} {{v~}R{|x}|v{|x}|^{}Z~f{|w~|v{|y~|`{|w~s{}x~}v" + "{|w~Wu~|F{|x}|O{}w~|H{|w~}U{|}w~|x~|w~}|R{}w~ c{}x~}Yv~|lv~|W{}w~}F{|v~N{|v~}Z{}w~u{}w~}\\{|k~}Z{}w~}x{|}u~y}|" + "L{}v~Zv~|pv~}a{|v~l{}v~|X{}v~M{}v~M{|}p~}|,{|}p~}|H{}v~|e{|w~q{|q~}y{}x~|v{|x~}fv~tv~]{}w~}n{}v~|c{|v~|N{}w~}f{" + "}v~d{}w~}L{}w~}T{}v~|L{}w~}hv~|Y{}w~}M{}w~}W{}w~}{|u~}S{}w~}V{}w~|xw~}rw~|xv~|h{}w~|x{|v~|rv~i{|v~|d{}v~d{}w~}n" + "{|v~|h{|v~|d{}v~f{}w~}n{}v~|`{}v~}|F{|v~|Z{|v~h{}w~}cv~|n{}v~i{}w~}rw~|ww~|s{|v~b{}q~}U{|v~|{|v~|N{}v~|U{|w~}K{" + "|w~G{|w~|^{}w~}f{|v~ V{}y~}|r{|u~|]r~|u{|u~}\\{}u~}s{|}y~|_{|u~|u{|}s~|_{}v~}|t{}v~}Vw~}T{|u~|u{|}s~|`r~|u{|u~|" + "Vv~Lv~Tw~}ru~|Tv~_r~|v{|}v~}{w~|u{}v~}gr~|u{|u~|^u~}|v{|}u~]r~|u{|u~|_{|u~|u{|}s~|Zr~}|v{|\\v~}|r{|}y~Wv~S{|w~|" + "m{}w~|a{}w~|m{|w~}g{}w~|rs~qw~}dv~}s{|v~|_{}w~}m{}w~|Nu~Uw~}Lw~|M{|w~| K{}r~u{|r~}a{|v~}|v{}v~yw~|`v~r{|u~|K{|w" + "~|]{}w~|xy|}t~}[u~}|s{|}~}]r~|u{|u~|ay|v~|n{}w~|X{|s~|Z{|w~|m{}w~|f{|v~dv~|e{|u~}|{|v~y|}v~}bx}u~q}u~x}|dv~t{}w" + "~t{|w~}_u~|u{|u~|_{|u~}|v{|}t~v}f{|q}u~p}b{}w~|l{|v~]v~tv~R{}v~}U{}w~}V{|v~|cv~}l{|v~}s{|v~s{|v~}h{}w~}hv~|i{}v" + "~r{|v~r{|v~|l{|v~|d{}v~fu~|C{}w~C{|u~|X{}w~W{}v~}m{}v~|X{}w~ sv~}T{|u~}|yy~}x{|}y~Z{|v~P{|g~}Y{}w~|xv~Pw~|T{|v~" + "}u~}*x~v{}w~ex~dw~qw~}U{|x~}t{}x~ Xx~sw~s{}x~}tx~,{|r~|O{}w~N{|w~|Dw~({|w~|m{}w~|a{|m~}w{|x~} H{}x~|T{}x~}qw~|]" + "x~}t{|x~|\\{}x~|nw~]{}x~|nw~|Xw~sw~|Ov~|Tv~tv~Xv~tv~Xv~tv~Xv~tv~Xv~tv~Y{|w~}tv~|a{|v~t{|v~Y{|v~|J{}w~}M{}w~}M{}" + "w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~f{|v~|h{}w~|x{|v~|rv~i{|v~|d{}v~k{|v~|d{}v~k{|v~|d{}v~k{|v~|d{}v~k{|v~|d{" + "}v~d{|u~r{}v~}e{|v~|n{}w~v{}v~h{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}^{|v~|{|v~|Z{}w~}nu~`w~}v{}w~V{}y~}|r" + "{|u~|]{}y~}|r{|u~|]{}y~}|r{|u~|]{}y~}|r{|u~|]{}y~}|r{|u~|]{}y~}|r{|u~|g{}y~}|r{|o~}|u{|}v~}e{}u~}s{|}y~|^{}v~}|" + "t{}v~}]{}v~}|t{}v~}]{}v~}|t{}v~}]{}v~}|t{}v~}Uv~Lv~Lv~Lv~T{}u~}|v{|}v~}^r~|u{|u~|^u~}|v{|}u~\\u~}|v{|}u~\\u~}|v" + "{|}u~\\u~}|v{|}u~\\u~}|v{|}u~U{}u~Uu~}|u{}u~|_{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{}w~}m{}w~|`r~|u{" + "|u~|`{}w~}m{}w~| Xw~|r{}w~r{|w~hw~|d{|w~hw~|yu~|v{|u~y{|w~hw~|m{|u~y{|w~hw~|d{|w~Y{}x~}Vv~mv~| XZ~}g{}t~oy~}'{}" + "e~}Y{}p~_W~|fc~|`v~n{}w~|`w~}kw~}Zv~|}w~|X{}w~}qv~|e{}x~} q{|c~| {{v~} y{|x~}t{}x~}]{|w~}v{|y~|_w~|u{|w~|vw" + "~|Wt~ p{}w~|H{|v~V{}w~}yx~y{}w~}S{}w~ cw~|Z{|v~k{}w~}W{}w~}Fv~}Qy|u~}Z{|w~|u{}w~}\\{|i~|\\v~|y{}p~}|Nv~}Z{|v~|" + "s{|v~}`{|v~lu~|X{}v~M{}v~P{|}p~}|b{|Z~}b{|}p~}|L{}v~}d{}x~|r{|n~{}x~|uw~|h{}w~}t{}w~|^{}w~}q{|}u~}b{}v~M{}w~}f{" + "}v~d{}w~}L{}w~}T{}v~K{}w~}hv~|Y{}w~}M{}w~}W{}w~}|u~}R{}w~}V{}w~|x{|w~s{}w~wv~|h{}w~|w{}w~}rv~i{}v~c{}v~d{}w~}n{" + "}v~|h{}v~c{}v~f{}w~}o{|u~_{|t~}|H{|v~|Z{|v~h{}w~}c{}v~nv~}i{|v~s{|w~|w{}x~}s{}w~}b{|q~S{}v~|v~}N{}v~}T{|w~}K{|w" + "~|H{|w~| s{}|m{}w~}]t~}q{}v~|^{}v~}ny|_u~q{}t~|`{|v~|q{|v~|Ww~}Tu~q{|t~|`t~}r{|v~}Vv~Lv~Tw~}t{|u~Rv~_t~}r{}v~}" + "y~}r{}v~gt~}r{|v~}_{}v~|r{|v~}^s~q{}v~_{}v~|r{}t~|Zs~T{|w~}m{|Wv~S{|w~|m{}w~|a{|w~}mv~|g{|w~}s{|s~|s{|w~|d{|v~|" + "u{|v~}]v~mv~N{}v~Tw~}Lw~|M{|w~| L{}p~w{|p~}bv~}s{}w~y|w~_v~wx|}t~}J{|w~}^{}w~r{}u~|]{|v~|Ot~}r{|v~}_{|v~nv~W{}s" + "~}Z{|w~|m{}w~|f{}w~}d{}w~}eu~}x{|w~|x{}v~|`{|w~}q{|w~}`v~t{}w~t{|w~}`{}v~q{}v~_u~}r{|v~}V{|w~}Wv~|l{|v~^{}w~}t{" + "}w~|R{}v~}V{}w~}V{|v~bv~}l{|v~|s{|v~r{}v~h{}w~}hv~|i{}v~r{|v~r{}v~k{}v~c{}v~gu~|B{}w~B{|u~|Y{}w~X{}v~}k{}v~|Y{}" + "w~ sv~}Tu~|wy~}u{|Z{|v~O{|u~}|x{|}v~}_{|p~}y{|p~}Ww~|Tw~}y{|t~|,y~}vw~|e{}y~dw~|s{}w~}V{|w~}u{}w~ Xy~}sw~s{}x~}" + "t{}y~*y}x~}|[m|}w~l|^{}w~C{|x~}({|w~|m{}w~|`m~}w{|x~} H{}x~|T{|w~|s{}x~}\\w~}u{|w~|]{}x~|o{}x~}]{}x~|o{}x~}Ww~t" + "{}x~}Nv~|U{}w~}t{}w~|Z{}w~}t{}w~|Z{}w~}t{}w~|Z{}w~}t{}w~|Z{}w~}t{}w~|Z{}w~|t{|w~}av~}t{|v~Y{}v~I{}w~}M{}w~}M{}w" + "~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~f{|v~|h{}w~|w{}w~}rv~i{}v~c{}v~k{}v~c{}v~k{}v~c{}v~k{}v~c{}v~k{}v~c{}v~c{|" + "u~t{}v~}d{}v~n{|w~|v{|v~h{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}]{}v~|v~}Y{}w~}n{|v~|aw~}vv~V{}|m{}w~}]{}|m" + "{}w~}]{}|m{}w~}]{}|m{}w~}]{}|m{}w~}]{}|m{}w~}g{}|m{}r~|q{|v~|g{}v~}ny|_{|v~|q{|v~|_{|v~|q{|v~|_{|v~|q{|v~|_{|v~" + "|q{|v~|Vv~Lv~Lv~Lv~U{|v~}q{|v~|_t~}r{|v~}_{}v~|r{|v~}^{}v~|r{|v~}^{}v~|r{|v~}^{}v~|r{|v~}^{}v~|r{|v~}V{}u~V{}v~" + "|r{|v~}_{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|`v~mv~_s~q{}v~_v~mv~ X{|w~q{}w~q{}x~|j{|w~b{}x~|j{|w~wu~" + "|x{|u~|x{}x~|j{|w~m{|u~|x{}x~|j{|w~b{}x~|Z{}x~}V{}w~|o{|v~ WZ~}gx~}w~|q{}y~|({|c~}_v|{}r~u|d{}X~f{}b~|b{|w~}mw~" + "}`w~}kw~}[{|v~{}w~}X{|w~}r{|v~d{}x~| q{}c~ yv~} y{}x~}t{}x~}\\v~}w{|y~|_{}w~|vw~}v{|x~}X{|r~ qv~Fv~X{}w~}|x" + "x~x{|}w~}U{}w~ d{|w~Y{|v~k{}w~}W{}w~}G{}v~|Xm~}Y{}x~}t{}w~}\\{|h~}]v~y|l~}P{|v~|Y{|u~u|}v~}_{|v~|n{|u~|X{}v~M{" + "}v~R{|o~}|`{|Z~}_{|}p~}|P{}v~}cw~r{|l~}x~|u{|x~|hv~|t{|v~^{}e~}a{}v~M{}w~}f{|v~|e{}d~|_{}g~|d{}v~K{}^~|Y{}w~}M{" + "}w~}W{}p~|Q{}w~}V{}w~|ww~|tw~}wv~|h{}w~|vv~|sv~i{}v~c{|v~|e{}w~}o{|u~g{}v~c{|v~|g{}w~}p{|u~|^{}q~y}|M{|v~|Z{|v~" + "h{}w~}c{|v~|p{|v~gv~|t{|w~v{|x~}sv~|a{|s~|Rq~}N{}v~}S{|w~}Jw~}H{|w~| bv~|^t~ov~}^v~}P{|v~|p{}u~|`v~|o{|v~Ww~}U" + "{|v~o{}u~|`u~}p{|v~Vv~Lv~Tw~}u{|v~}Qv~_u~}pt~}pv~|hu~}p{|v~`{|v~|p{|v~|_t~ov~}a{|v~|p{}u~|Zt~S{}w~Gv~S{|w~|m{}w" + "~|`v~|o{|v~ev~s{|x~y}x~}s{}w~|c{}v~uv~}\\{}w~|o{|w~}O{}v~|U{|w~}Lw~|M{|w~} M{|x~}x|}w~}xv~}x|}x~|d{}v~qw~y}x~}_" + "v~x{}q~}I{|w~}_{|w~|q{|u~]{}w~|Nu~}p{|v~^{}w~|p{|w~}X{|q~Z{|w~|m{}w~|fv~|d{|v~f{|v~}w{}w~|wu~`{|w~}q{|w~}`v~t{}" + "w~t{|w~}a{|v~ov~}a{|v~}p{}v~|W{|w~}Wv~}l|}v~^v~|t{|v~Q{}v~}W{}w~}V{|v~b{}w~}l{}v~r{|v~r{}v~|i{}w~}hv~|i{|v~|s{|" + "v~r{}v~k{}v~xi~}y{|v~|iu~|A{}w~A{|u~|Z{}w~Y{}v~}i{}v~|Z{}w~ sv}|U{}v~|vy~}S{|v~O{|w~}s{|v~_{|o~|{o~}Ww~|U{}x~}v" + "{}u~}.{|y~|w{|w~d{|y~|e{}w~t{}v~}W{|v~|v{}w~}cY|8{|y~|sw~sw~|t{|y~| `{|Z~}_{}x~}C{|w~}({|w~|m{}w~|`{|n~}w{|x~} " + "H{}x~|Sv~|u{}w~|\\{}v~v{|v~|^{}x~|p{|w~\\{}x~|p{|w~W{|x~}u{|w~Mv}|Uv~|t{|v~Zv~|t{|v~Zv~|t{|v~Zv~|t{|v~Zv~|t{|v~" + "Zv~rv~b{|v~s{|c~l{}v~I{}d~|`{}d~|`{}d~|`{}d~|W{}w~}M{}w~}M{}w~}M{}w~}Z{|v~ev~}h{}w~|vv~|sv~i{}v~c{|v~|l{}v~c{|v" + "~|l{}v~c{|v~|l{}v~c{|v~|l{}v~c{|v~|c{|u~v{}v~}c{}v~o{|w~|u{|v~|i{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}\\q~" + "}X{}w~}mv~}aw~}vv~Ev~|Mv~|Mv~|Mv~|Mv~|Mv~|Ws~|o{}w~}gv~}Ov~|o{|v~_v~|o{|v~_v~|o{|v~_v~|o{|v~Vv~Lv~Lv~Lv~Uv~}o{}" + "w~}_u~}p{|v~`{|v~|p{|v~|`{|v~|p{|v~|`{|v~|p{|v~|`{|v~|p{|v~|`{|v~|p{|v~|Wt|W{|v~|q{}u~|`{|w~|m{}w~|a{|w~|m{}w~|" + "a{|w~|m{}w~|a{|w~|m{}w~|`{}w~|o{|w~}_t~ov~}`{}w~|o{|w~} X{}x~}q{}w~q{|x~}j{}x~}b{|x~}j{}x~}vu~|yu~|w{|x~}j{}x~}" + "mu~|w{|x~}j{}x~}b{|x~}Z{}x~}V{|v~o{}w~} WZ~}g{}|yw~}qx~'a~|c{|}t~}k~}|fY~}g{}`~b{|w~|m{}w~`w~}kw~}[{|w~}{|v~Wv~" + "r{}w~}dw~| lv~| kv~| yw~|tw~|\\{}v~}|y{|y~|^v~}y|}v~uw~X{|p~ rv~Fv~Xw~|vx~v{|w~U{}w~ d{}x~}Y{|v~k{}w~}W{}w" + "~}H{|v~}Wo~}|Y{|w~|t{}w~}\\{|v~x}|x}s~}^v~|j~}Q{}w~}V{}l~}]v~}n{}u~}X{}v~M{|v}U{|}p~}|]{|Z~}\\{}o~|S{}v~}c{|x~}" + "rv~}|w{|}t~|tx~}i{|v~rv~|_{}h~}|_v~}M{}w~}f{|v~|e{}d~|_{}g~|dv~}K{}^~|Y{}w~}M{}w~}W{}q~|P{}w~}V{}w~|w{}w~u{|w~|" + "wv~|h{}w~|v{}w~}sv~iv~}c{|v~|e{}w~}p{|u~|gv~}c{|v~|g{}w~}sy|}u~}\\{}m~}|Q{|v~|Z{|v~h{}w~}bv~}p{}w~}g{}w~}t{}x~}" + "v{|w~sv~|`{}u~}Q{|r~|O{|u~R{|w~}J{}w~H{|w~| b{|w~}^u~|o{|v~_{}v~Ov~}nu~|a{}w~}m{}w~|Xw~}Uv~|nu~|`u~nv~|Wv~Lv~T" + "w~}v{}v~}Pv~_u~o{}u~|p{}w~}hu~nv~|a{}w~}n{}w~}_u~|o{|v~a{}w~}nu~|Zu~|S{}w~Gv~S{|w~|m{}w~|`{}w~}o{}w~}e{}w~s{}x~" + "}|w~sv~a{}v~w{}v~[{|w~}ov~|P{}v~|T{|w~}Lw~|M{|w~}:{|4x~|v{|w~}{}x~}u{}x~dv~}q{}s~|_v~x{}r~}S{|y}~y}|w{|w~}_w~}o" + "{|v~}^{}w~Mu~nv~|_{|w~}pv~|X{}w~}v~|[{|w~|m{}w~|g{|v~bv~|g{}v~v{}w~v{|v~|a{|w~}q{|w~}`v~t{}w~t{|w~}a{}w~|o{|v~a" + "{}v~nv~}W{|w~}W`~_{|v~rv~|Q{}v~|X{}w~}V{|v~b{}w~}lu~r{|v~r{|v~|i{}w~}hv~|hv~}s{|v~rv~}kv~}xi~}y{|v~|ju~|@{}w~@{" + "|u~|[{}w~Z{}v~}g{}v~|[{}w~ Gv~}uy~}S{|v~Ow~}q{|w~|`{|n~}o~}Ww~|Uw~|t{}u~|0{|y~|w{|x~}d{|y~|e{|v~}w|t~}X{|v~|vv~" + "}c{|Z~}8{|y~|sw~t{}w~s{|y~| `{|Z~}`{}x~}M{|~}|v{|}v~'{|w~|m{}w~|_{}o~}w{|x~}Vv}| s{}x~|S{|v~}|{y|}w~}Z{}v~|w{|v" + "~}_{}x~|pw~|o{}w~m{}x~|p{}x~|vy|}w~y}|g{|w~|u{}x~|o{}w~3{|v~rv~|\\{|v~rv~|\\{|v~rv~|\\{|v~rv~|\\{|v~rv~|\\{}w~}" + "r{}w~|c{}w~}s{|c~lv~}I{}d~|`{}d~|`{}d~|`{}d~|W{}w~}M{}w~}M{}w~}M{}w~}_{}i~}nv~}h{}w~|v{}w~}sv~iv~}c{|v~|lv~}c{|" + "v~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|b{|u~x{}v~}bv~}p{|w~}t{|v~|i{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}\\{|r~|" + "X{}w~}mv~}aw~}v{}w~}F{|w~}M{|w~}M{|w~}M{|w~}M{|w~}M{|w~}W{|u~}m{}w~h{}v~O{}w~}m{}w~|a{}w~}m{}w~|a{}w~}m{}w~|a{}" + "w~}m{}w~|Wv~Lv~Lv~Lv~V{}v~n{|v~_u~nv~|a{}w~}n{}w~}`{}w~}n{}w~}`{}w~}n{}w~}`{}w~}n{}w~}`{}w~}n{}w~},{}w~}q{}t~}`" + "{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|`{|w~}ov~|_u~|o{|v~`{|w~}ov~| X{}x~|q{}w~q{|w~j{}x~|b{|w~j{}x~|u" + "u~|u~|v{|w~j{}x~|nu~|v{|w~j{}x~|b{|w~Zw~}Uv~|q{|v~ VZ~}c{}w~r{|y~}({}`~d{}^~|h{|Z~g{|_~}c{}w~l{|w~`w~}kw~}[{}w~" + "|yv~|X{}w~|sv~|dV~} 2v~| k{}w~| {{|w~t{|w~Zs~y}y~|^{|o~|v{}x~}rx|e{|v~y}u~n{|w~},{|v~Fv~|Y{|~}tx~t{}~|U{}w~ " + " dw~|Y{|v~k{}w~}W{}w~}Hu~Vp~}|Y{|w~}s{}w~}\\{|~}|q{}t~|`{|q~}|xy|t~|Rv~|U{|}p~|[{}v~|ot~} V{|}p~}|Z{|Z~}Z{|}p~}" + "|W{}v~|b{}x~|s{}w~|s{|u~|tw~i{}w~}r{}w~}_{}g~}|`v~}M{}w~}f{|v~|e{}d~|_{}g~|dv~}K{}^~|Y{}w~}M{}w~}W{}q~O{}w~}V{}" + "w~|w{|w~|v{}w~vv~|h{}w~|uv~|tv~iv~}c{|v~|e{}w~}sy|s~fv~}c{|v~|g{}f~}Z{}k~}S{|v~|Z{|v~h{}w~}b{|v~pv~|g{}w~}tw~|u" + "w~|u{|v~_{}u~O{}t~|O{|u~|R{|w~}J{|w~|I{|w~| aw~}^v~}m{}w~}`v~|P{|v~m{}v~|av~l{|w~}Xw~}V{|v~m{|v~|`v~}n{}w~|Wv~" + "Lv~Tw~}w{}v~}Ov~_v~}o{|v~}o{|w~}hv~}n{}w~|av~|n{|v~|`u~mv~|bv~m{}v~|Zv~}R{}w~Gv~S{|w~|m{}w~|`{|v~ov~d{}w~|tw~|{" + "w~|u{|w~}`v~}y{|v~|Z{}w~|q{|v~P{}v~|Sv~|Lw~|Lv~|W{|y}w~}|iy}5{|y~}sw~|x~}s{}y~|f{|v~|ps~^v~x{}q~}|W{|r~|y{|w~}`" + "{}w~m{}v~^{}w~Mv~}n{}w~|^{}w~q{|v~Wv~y|w~}[{|w~|m{}w~|g{}v~b{}w~}h{|v~|v{}w~u{}w~}a{|w~}q{|w~}`v~t{}w~t{|w~}av~" + "mv~|c{|v~|n{|v~W{|w~}W`~_{}w~}r{}w~}Q{|v~}X{}w~}V{|v~b{}w~}lv~}r{|v~r{|v~|i{}w~}hv~|h{}v~s{|v~s{|v~|kv~}xi~}y{|" + "v~|ku~|?{}w~?{|u~|\\{}w~[{}v~}e{}v~|\\{}w~ H{}v~ty~}S{|v~P{|w~o{}w~_s|}r~s|Vw~|V{|w~r{|u~0{|y~v{}x~}d{|y~|d{}o~" + "|x~}Y{}v~v{|v~|b{|Z~}8{|y~rw~u}v~|s{|y~| `{|Z~}a{}l~|X{|m~|'{|w~|m{}w~|^o~}w{|x~}W{|v~| xm~}W{|n~}X{|v~|vv~}e{}" + "n~}v{}x~}o{|v~m{}x~|q{|w~w{|o~|t{|~}y|w{|}v~u{|x~}o{|v~3{}w~}r{}w~}\\{}w~}r{}w~}\\{}w~}r{}w~}\\{}w~}r{}w~}\\{}w" + "~}r{}w~}\\v~|r{|w~}cv~|s{|c~lv~}I{}d~|`{}d~|`{}d~|`{}d~|W{}w~}M{}w~}M{}w~}M{}w~}_{}i~}nv~}h{}w~|uv~|tv~iv~}c{|v" + "~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|a{|u~|}v~}av~}pw~}s{|v~|i{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}[" + "{}t~|W{}w~}mv~}aw~}v{}v~|Fw~}Lw~}Lw~}Lw~}Lw~}Lw~}Vu~l{|w~|iv~|Ov~l{|w~}av~l{|w~}av~l{|w~}av~l{|w~}Wv~Lv~Lv~Lv~V" + "v~|mv~|`v~}n{}w~|av~|n{|v~|av~|n{|v~|av~|n{|v~|av~|n{|v~|av~|n{|v~|-v~|r{|x~}v~`{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{" + "}w~|a{|w~|m{}w~|_{}w~|q{|v~^u~mv~|`{}w~|q{|v~ Ww~p{}w~pw~jw~yd|yw~jw~t{|p~|tw~jw~nu~|tw~jw~pv~}qw~Zw~|U{}w~}q{}" + "w~} F{}w~}W{|w~|s{}y~|){|_~}f{}\\~|h{}\\~|g{}^~c{}w~l{|w~|aw~}kw~}[v~x{}w~}X{|w~}t{|v~cV~} 2v~| k{}w~| {{|x~" + "}t{|x~}Z{|o~}y|`{|}r~|v{|w~t{}u~}|hv~}y{}u~o{|w~|,{|v~F{}w~|X{|sx~s{|T{}w~ e{|w~X{|v~k{}w~}W{}w~}Iu~|Vm~|[{}w~" + "r{}w~}L{}u~`{|r~|s{|u~S{}v~V{|}m~}|\\u~p{}t~} Y{|}p~}|VY|W{|}p~}|[{|v~|aw~rw~}q{|v~|t{}x~iv~q{|v~_{}e~}av~}M{}w" + "~}f{|v~|e{}d~|_{}g~|dv~}m{}n~|h{}^~|Y{}w~}M{}w~}W{}q~}P{}w~}V{}w~|vw~}vw~}vv~|h{}w~|u{}v~tv~iv~}bv~|e{}e~|fv~}b" + "v~|g{}g~}X{|}k~}U{|v~|Z{|v~h{}w~}av~|r{|v~f{|v~u{|w~|u{}x~}u{}w~}`{|t~|O{}v~}Nu~|Q{|w~}Iw~}I{|w~| a{}w~^v~|m{|" + "w~}a{|v~O{|w~}lv~|b{|w~}kv~Xw~}V{|w~}lv~|`v~|n{|w~}Wv~Lv~Tw~}x{}v~|Nv~_v~|nv~|nv~hv~|n{|w~}b{|v~lv~|`v~}m{|w~}c" + "{|w~}m{|v~|Zv~|R{}w~|Hv~S{|w~|m{}w~|_{}w~|q{|w~}d{|w~}u{|w~y{}x~|u{|w~|`{|v~y|v~}Y{|w~}q{}w~|Q{|v~}S{}v~Kw~|L{}" + "w~}Y{|p~}|n{|y~}5{}y~r{|t~qy~}f{}v~ot~}^v~x{}o~}Y{}p~|{|w~|`w~}lv~|_{|w~}Nv~|n{|w~}^{|w~|r{}w~|X{}w~}yv~[{|w~|m" + "{}w~|gv~}b{}v~h{|v~u{}w~u{|v~a{|w~}q{|w~}`v~t{}w~t{|w~}b{|w~}m{|w~}c{|v~lv~|X{|w~}W`~_v~|r{|v~Qu~W{}w~}V{|v~b{}" + "w~}lv~}r{|v~qv~|i{}w~}hv~|h{|v~|t{|v~s{}v~jv~}xi~}xv~|lu~[|]{}w~\\\\|u~|]{}w~\\{}v~}c|u~|]{}w~ H{}w~}ty~}X{}g~|" + "[{}x~}nw~Vs~|Nw~|V{}x~}pv~}1{}y~v{}x~}d{|y~}c{}r~}{|x~}Z{}w~}v{|v~|a{|Z~}8{}y~rn~}q{|y~} `{|Z~}a{}l~|X{|o~}|&{|" + "w~|m{}w~|]{}q~}w{|x~}W{|v~| xm~}V{|}q~|V{|v~|v{}w~}fm~}vw~o{|u~rm~}vw~|w{}n~|u{|m~|uw~|p{|u~3v~q{|v~\\v~q{|v~\\" + "v~q{|v~\\v~q{|v~\\v~q{|v~]{|v~pv~|e{}w~}r{|c~lv~}I{}d~|`{}d~|`{}d~|`{}d~|W{}w~}M{}w~}M{}w~}M{}w~}_{}i~}nv~}h{}w" + "~|u{}v~tv~iv~}bv~|lv~}bv~|lv~}bv~|lv~}bv~|lv~}bv~|`{|p~}`v~}q{}x~}qv~|i{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}" + "w~}Z{}v~}V{}w~}mv~}aw~}uu~}G{}w~L{}w~L{}w~L{}w~L{}w~L{}w~V{}w~}kw~}j{|v~O{|w~}kv~b{|w~}kv~b{|w~}kv~b{|w~}kv~Wv~" + "Lv~Lv~Lv~W{|v~l{}w~}`v~|n{|w~}b{|v~lv~|b{|v~lv~|b{|v~lv~|b{|v~lv~|b{|v~lv~|.{|v~r{|w~{}w~|a{|w~|m{}w~|a{|w~|m{}" + "w~|a{|w~|m{}w~|a{|w~|m{}w~|_{|w~}q{}w~|^v~}m{|w~}`{|w~}q{}w~| Ww~yd~|{w~jw~yd~|{w~jw~s{|r~|sw~jw~ou~|sw~jw~pv~}" + "qw~Zw~|U{|v~qv~| G{}w~}Uw~}sx~({}^~g{}Z~g]~}f{|_~|cw~}l{|w~|aw~}kw~}\\{|v~x{|v~Wv~t{}w~}cV~} 2v~| k{}w~| {{}" + "x~}t{}x~}Y{|}m~}`{|}w~}|tw~|v{|q~}j{}v~w{}u~p{}w~|,{|w~}F{}w~|Ox~Z{|Z~} t{}x~}X{|v~k{}w~}W{}w~}J{}v~|Ut|}t~}]{" + "|w~|r{}w~}K{}v~|a{|s~p{|v~}Tv~}W{}i~}]{}u~|t{|}s~} Z{|q~}| e{|}q~}\\v~}`x~}s{}w~ov~|t{}x~|k{|w~}p{}w~|`{}w~}p|}" + "t~|cv~}M{}w~}f{|v~|e{}w~}i|^{}w~}l|cv~}m{}n~|h{}w~}h|v~|Y{}w~}M{}w~}W{}w~}u~}Q{}w~}V{}w~|v{}w~w{|w~uv~|h{}w~|tv" + "~|uv~iv~}c{|v~|e{}f~|ev~}c{|v~|g{}i~}S{|}m~}V{|v~|Z{|v~h{}w~}a{}w~}rv~}ev~|v{|w~t{|w~uv~|`r~O{|v~|O{}v~}P{|w~}I" + "{}w~I{|w~| a{}w~^v~|lv~a{}w~}O{}w~|lv~|b{|w~|k{}w~Xw~}V{}w~|lv~|`v~m{|w~}Wv~Lv~Tw~}yu~|Mv~_v~mv~mv~hv~m{|w~}b{" + "}w~}l{}w~}`v~|m{|v~c{}w~|lv~|Zv~Q{}v~|Iv~S{|w~|m{}w~|_{|w~}q{}w~|cv~u{}x~}y{}x~}u{}w~^{}q~}Wv~qv~Q{|v~}Uy|}v~|K" + "w~|L{|u~}|^{|k~}|s{|}x~}5y~}q{}v~|q{}y~f{}w~}o{}u~|^v~ty|}s~[{|u~y}v~y|w~|a{|w~}l{}w~}^{}w~|Ov~m{|w~}]w~}rv~Wv~" + "|y{}w~}\\{|w~|m{}w~|gv~|b{|v~h{}w~}u{}w~tv~a{|w~}q{|w~}`v~t{}w~t{|w~}b{}w~|m{|v~c{}w~}l{}w~}X{|w~}W`~`{|w~}pv~|" + "S{}v~|W{}w~}V{|v~bv~}lv~}r{|v~r{|v~|i{}w~}hv~|gu~t{|v~t{|v~}jv~}xh|y{|v~|mT~]{}w~]T~|^{}w~]{}U~|^{}w~ Hv~|ty~}X" + "{}g~|[w~|nw~|W{}u~}Mw~|V{}w~ov~1{|y~v{}x~}d{|y~|ay}x~y}ww|[{}w~}v{|v~|`{|Z~}8{|y~ro~o{|y~| Q{}w~R{}l~|V{|y}v~y}" + "|${|w~|m{}w~|\\{|}s~}w{|x~}W{|v~| xm~}T{|y}w~}|S{|v~|v{}w~}gm~}w{}x~}oy~y}x~rm~}w{}x~}v{}~}y|w{|v~u{|o~}t{}x~}o", + "t~^v|V{|w~}p{}w~|^{|w~}p{}w~|^{|w~}p{}w~|^{|w~}p{}w~|^{|w~}p{}w~|^{}w~}p{}w~}ev~|r{|v~h|lv~}I{}w~}i|_{}w~}i|_{}" + "w~}i|_{}w~}i|V{}w~}M{}w~}M{}w~}M{}w~}_v}u~r}nv~}h{}w~|tv~|uv~iv~}c{|v~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|" + "_{|r~}_v~}r{}w~q{|v~|i{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}Z{|v~|V{}w~}mv~}aw~}u{|t~|I{}w~L{}w~L{}w~L{}w~" + "L{}w~L{}w~V{}w~|kv~j{}w~}O{|w~|k{}w~b{|w~|k{}w~b{|w~|k{}w~b{|w~|k{}w~Wv~Lv~Lv~Lv~W{}w~}l{|w~}`v~m{|w~}b{}w~}l{}" + "w~}b{}w~}l{}w~}b{}w~}l{}w~}b{}w~}l{}w~}b{}w~}l{}w~}eY|f{}w~}rw~y{|w~}a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|" + "m{}w~|^v~qv~]v~|m{|v~_v~qv~ Vw~yd~|{}x~|kw~yd~|{}x~|kw~r{|t~|r{}x~|kw~pu~|r{}x~|kw~pv~}q{}x~|[w~|T{}w~|s{|v~ G{" + "}v~T{}w~t{|y~}(]~|i{|Y~}h{|_~}d{|a~}bw~}kw~|aw~}kw~}\\{}w~}wv~|Xv~|u{}w~|cV~} 2v~| k{}w~| {{w~|tw~|W{|}m~}T{" + "}x~}v{|o~}l{|v~|v{}u~q{}w~+{|w~}F{}w~|Ox~Z{|Z~}+m| ww~|X{|v~k{}w~}W{}w~}K{}v~}K{|}v~}^w~}q{}w~}Ju~a{|t~|o{}v~U{" + "|v~|X{}u~}|wy|u~}]t~}y|{y|}q~} Z{|t~}| _{|}t~}\\v~`{|x~}s{}x~}o{|w~|t{}x~|kv~|p{|w~}`{}w~}n{|u~cv~}M{}w~}f{|v~|" + "e{}w~}L{}w~}Tv~}m{}n~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}|u~}R{}w~}V{}w~|v{|w~|x{}x~}uv~|h{}w~|t{|v~uv~iv~}c{|v~|e{}h~" + "}cv~}c{|v~|g{}h~}Qy|y}p~W{|v~|Z{|v~h{}w~}a{|v~s{|v~|e{}w~}v{}x~}t{|w~uv~|a{}r~}P{|v~|P{}v~}O{|w~}I{|w~|J{|w~| " + "n{|y}l~^v~kv~a{}w~|Ov~|l{}w~|b{}w~|k{}w~|Yw~}Vv~|l{}w~|`v~m{|w~}Wv~Lv~Tw~}|u~Kv~_v~mv~mv~hv~m{|w~}b{}w~|l{|v~`v" + "~kv~c{}w~|l{}w~|Zv~Pu~}|Kv~S{|w~|m{}w~|^v~qv~b{}w~u{}x~|y{|w~uv~]{}r~V{}w~|s{|w~}R{|v~}X{|q~}Jw~|K{|q~}c{}g~}w|" + "}u~}5y~}pw~}p{}y~fv~|o{}u~]v~p{|t~\\v~}w{|w~}w~|a{}w~|l{|w~}]{}w~}y|Rv~m{|w~}]{}w~s{}w~}X{}w~}x{|v~\\{|w~|m{}w~" + "|h{|v~|b{|v~|i{}w~|u{}w~tv~|b{|w~}q{|w~}`v~t{}w~t{|w~}bv~kv~c{}w~|l{|w~}X{|w~}Wv~jv~`v~|p{}w~}T{}v~|V{}w~}V{|v~" + "|cv~|lv~}r{|v~r{|v~|i{}w~}hv~|g{}v~}u{|v~tu~|jv~}c{|v~|n{|T~]{}w~]T~}^{}w~]T~}^{}w~ I{|v~sy~}X{}g~|[w~m{}x~|Vu~" + "|#{|w~|p{|w~|2{|y~|w{|x~}d{|y~|3v~}v{}v~|Aw~}8{|y~|sw~x{|w~}p{|y~| Q{}w~ p{|w~|m{}w~|Y{|}v~}w{|x~}W{|v~| jv~}" + "v{}v~|W{|w~o{}y~{}x~r{}n~}x{|w~uy|rw~|ty|t}|s{|w~o{}y~|}x~^{}w~|Wv~|p{|w~}^v~|p{|w~}^v~|p{|w~}^v~|p{|w~}^v~|p{|" + "w~}^v~|p{|v~f{|v~q{|v~Yv~}I{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~ev~}h{}w~|t{|v~uv~iv~}c{|v~|lv~}" + "c{|v~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|^{|t~}^v~}s{}w~p{|v~|i{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}Z{|v~|V{}w" + "~}n{|v~|aw~}t{}t~}W{|y}l~Y{|y}l~Y{|y}l~Y{|y}l~Y{|y}l~Y{|y}l~c{|y}l~j{}w~j{}w~|O{}w~|k{}w~|c{}w~|k{}w~|c{}w~|k{}" + "w~|c{}w~|k{}w~|Xv~Lv~Lv~Lv~W{}w~|l{|v~`v~m{|w~}b{}w~|l{|v~b{}w~|l{|v~b{}w~|l{|v~b{}w~|l{|v~b{}w~|l{|v~f{|Z~}f{}" + "w~|s{}x~|y{|w~}a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|^{}w~|s{|w~}]v~kv~_{}w~|s{|w~} Vw~yd~|{}x~|kw~yd" + "~|{}x~|kw~qt~|r{}x~|kw~qu~|q{}x~|kw~pv~}q{}x~|[w~|T{|w~}s{}w~} H{|v~|T{|w~|u{}y~({|]~}i{}X~g{|`~b{}b~aw~}kw~}aw" + "~}kw~}\\v~|w{}w~}X{}w~}uv~bw~}Z| 5x|v~}p| v{}w~| {|w~t{|w~S{|}n~|Vw~uv~|y{|}w~}m{}w~}t{}u~rw~}+{|w~}F{}w~|Ox" + "~Z{|Z~},{|m~ x{|w~|X{|v~k{}w~}W{}w~}L{}v~}H{}v~}`{}w~p{}w~}J{}v~`t~n{|v~|V{}v~X{}v~}q{}v~}^{|j~|v~| Z{|t~| ]{|}" + "u~}]{|w~}`{|x~|sw~|o{|w~|t{}x~|l{|v~nv~`{}w~}lv~}dv~}M{}w~}f{|v~|e{}w~}L{}w~}Tv~}m{}n~|h{}w~}hv~|Y{}w~}M{}w~}W{" + "}w~}{|t~S{}w~}V{}w~|u{}x~}y{|w~|uv~|h{}w~|sv~|vv~iv~}c{|v~|e{}k~}|av~}c{|v~|g{}w~}t|y}u~}M{|}s~}X{|v~|Z{|v~h{}w" + "~}`v~}t{}v~d{}w~}vw~|sw~|w{|v~a{|v~}v~|Q{|v~|Q{|u~N{|w~}Hw~|J{|w~| p{}h~|_v~k{}w~|bv~|Ov~k{}w~|bv~j}v~|Yw~}Vv~" + "k{}w~|`w~}m{|w~}Wv~Lv~Tq~}Jv~_w~}mv~mv~hw~}m{|w~}bv~|l{|v~`v~kv~|dv~k{}w~|Zv~P{}r~}y|Pv~S{|w~|m{}w~|^{}w~|s{|w~" + "}b{|w~|vw~|xw~|w{|w~}\\s~|Uv~sv~|Ru~W{|s~}|Iw~|I{|}t~}d{|u~}w|}g~}5{|y~|p{|x~|p{}y~fv~|o{|v~}]v~n{}v~|^{}w~|ts~" + "`v~|l{|v~\\{}p~}Xw~}m{|w~}]{|w~|tv~|Xv~|wv~|]{|w~|m{}w~|h{|v~|q{}x~}q{|v~|iv~|u{}w~t{}w~|b{|w~}q{|w~}`v~t{}w~t{" + "|w~}bv~kv~|dv~|l{|v~X{|w~}Wv~|l{|v~a{|v~nv~U{|v~}U{}w~}Uv~}d{|v~|l{}v~r{|v~r{|v~|i{}w~}hv~|fu~|v{|v~u{}v~}iv~}c" + "{|v~|n{|T~]{}w~]T~}^{}w~]T~}^{}w~ rw|V{|w~}sy~}X{|w}u~q}Zw~m{}x~|V{}v~\"{|v~ow~|2{|y~|w{|w~d{|y~|4{}w~}v{|v~?w~" + "}8{|y~|sw~vw~}q{|y~| Q{}w~ p{|w~|m{}w~|Ux~}w{|x~}W{|v~| i{}w~|v{|v~Ww~|p{|y~|{}x~`{}x~|j{|x~}bw~|p{|y~}{}x~^{" + "}w~|X{|v~nv~_{|v~nv~_{|v~nv~_{|v~nv~_{|v~nv~_{|w~}nv~|g{}w~}q{|v~Yv~}I{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}" + "M{}w~}Z{|v~ev~}h{}w~|sv~|vv~iv~}c{|v~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|lv~}c{|v~|]{}u~|^v~}t{|w~|p{|v~|i{|v~h{}w~}" + "f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}Z{|v~|V{}w~}n{}v~|aw~}s{|s~|[{}h~|\\{}h~|\\{}h~|\\{}h~|\\{}h~|\\{}h~|f{}h~j}v~" + "jv~|Ov~j}v~|cv~j}v~|cv~j}v~|cv~j}v~|Xv~Lv~Lv~Lv~Wv~|l{|v~`w~}m{|w~}bv~|l{|v~bv~|l{|v~bv~|l{|v~bv~|l{|v~bv~|l{|v" + "~f{|Z~}fv~|t{}x~|wv~a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|]v~sv~|]v~kv~|_v~sv~| Vw~yd~|{w~jw~yd~|{w~j" + "w~rr~|sw~jw~ru~|pw~jw~pv~}qw~Zw~|Sv~sv~ H{|v~|Rw~}uy~}({|]~}i{}X~|g{}b~|a{}d~|aw~}kw~}aw~}kw~}]{|v~v{|v~X{|v~v{" + "|w~}b{}x~} pf~ v{|w~ {{|w~t{|x~}P{|y~}r~W{}x~|v{}w~u{}w~mv~r{}u~t{|w~|+{|v~F{}w~|Ox~Z{|Z~},{|m~ x{}w~W{|v~k" + "{}w~}W{}w~}M{}v~}F{}v~a{|w~|p{}w~}Iv~|au~}mv~}Vv~|Y{|v~}o{|v~|]{}m~|{v~| Z{|r~}| c{|}r~}]{|w~}`{|x~|sw~|nw~|t{}" + "x~k{}w~}n{}w~}a{}w~}l{|v~|e{}v~M{}w~}f{}v~d{}w~}L{}w~}T{}v~mr|v~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}y{|t~T{}w~}V{}w~|u" + "{|w~y{}w~tv~|h{}w~|s{|v~vv~i{}v~c{|v~|e{}w~}r|]{}v~c{|v~|g{}w~}q{}v~}K{|t~|Y{|v~|Z{|v~h{}w~}`{}v~tv~|d{|v~w{|w~" + "|s{}x~}w{}w~}av~}{}v~Q{|v~|R{|u~M{|w~}H{}x~}J{|w~| r{|f~|_w~}k{}w~|bv~|Ov~k{}w~|b`~|Yw~}Vv~k{}w~|`w~}m{|w~}Wv~" + "Lv~Tq~Iv~_w~}mv~mv~hw~}m{|w~}bv~jv~`v~k{}w~|dv~k{}w~|Zw~}O{}o~}|Sv~S{|w~|m{}w~|^{|w~}s{}w~|b{|w~}w{|w~w{}x~}w{|" + "w~|\\{|u~}T{}w~|u{|w~}Ru~V{|s~}|Iw~|J{|}s~}d{|w~|s{|}k~|3y~}p{|x~}p{}y~fv~mv~|]v~m{}v~_{|w~}rt~`v~jv~Z{}r~}Xw~}" + "m{|w~}\\w~}u{|w~}X{|w~}v{}w~}]{|w~|m{}w~|h{|v~|q{}x~}pv~|iv~t{}w~t{}w~|b{|w~}q{|w~}`v~t{}w~t{|w~}bv~k{}w~|dv~jv" + "~X{|w~}W{}w~|l{|v~a{}w~}n{}w~}W{|u~T{}w~}U{}w~}d{}v~k{}v~|s{|v~r{}v~h{}w~}hv~|f{|u~|w{|v~v{}u~h{}v~c{|v~|n{|T~]" + "{}w~]T~|^{}w~]{}U~}^{}w~ s{|w~V{|w~}sy~}S{|v~Pw~|nw~|V{|w~}!{}v~|q{}x~|1y~}vw~|e{}y~ci|]{}w~u{|w~|?w~}7y~}sw~v{" + "|w~|r{}y~ P{}w~ p{|w~|m{}w~|Ux~}w{|x~}W{|v~| Fi|U{|w~|u{}w~X{}x~}p{|y~}y{}x~a{|w~i{|x~}c{}x~}p{|y~}y{}x~^{}w~|" + "X{}w~}n{}w~}`{}w~}n{}w~}`{}w~}n{}w~}`{}w~}n{}w~}`{}w~}n{}w~}`{}w~|n{}w~}h{|v~p{|v~Y{}v~I{}w~}M{}w~}M{}w~}M{}w~}" + "D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~f{|v~|h{}w~|s{|v~vv~i{}v~c{|v~|l{}v~c{|v~|l{}v~c{|v~|l{}v~c{|v~|l{}v~c{|v~|^{}s~|_" + "{}v~u{|w~|o{|v~|i{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}Z{|v~|V{}w~}o{|u~`w~}q{}t~|^{|f~|^{|f~|^{|f~|^{|f~|" + "^{|f~|^{|f~|h{|P~jv~|O`~|c`~|c`~|c`~|Xv~Lv~Lv~Lv~Wv~jv~`w~}m{|w~}bv~jv~bv~jv~bv~jv~bv~jv~bv~jv~f{|Z~}fv~|u{}x~}" + "vv~a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|]{}w~|u{|w~}\\v~k{}w~|_{}w~|u{|w~} Uw~yq}w~r}yw~jw~yd|yw~jw~" + "sp~|tw~jw~su~|ow~jw~pv~}qw~Zw~|S{}w~}u{|w~} Hv~|Q{}w~|w{|y~|({|\\~iW~|f{}d~|_e~|`w~}kw~}aw~}kw~|]{}w~}uv~Wv~|w{" + "}w~|b{}x~} q{|g~| v{|w~({}Z~X{|y~|{|}u~}Y{|w~uw~|tw~}o{|w~}q{}u~u{}w~*{|v~F{}w~|*m|}w~l|,{|m~ xw~}W{|v~k{}w" + "~}W{}w~}N{}v~}Dv~|bw~}o{}w~}Iv~|au~|m{}w~}W{|v~X{}v~m{}v~\\{|p~}xv~| Y{}p~}| i{|}p~}|]{}w~}`{|x~|sw~mw~|t{}x~kv" + "~}n|}v~a{}w~}kv~}e{}v~M{}w~}f{}v~d{}w~}L{}w~}T{}v~dv~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}x{|t~U{}w~}V{}w~|tw~|{w~}tv~|" + "h{}w~|rv~}wv~i{}v~c{}v~d{}w~}T{}v~c{}v~f{}w~}p{}v~|Ju~}Y{|v~|Z{|v~h{}w~}_v~|v{|v~bv~|x{|w~r{}w~wv~|b{}v~xv~}R{|" + "v~|Ru~|M{|w~}H{|w~J{|w~| s{|q~t}v~|_w~}k{}w~|bv~Nv~k{}w~|b`~|Yw~}Vv~k{}w~|`w~}m{|w~}Wv~Lv~Tp~Jv~_w~}mv~mv~hw~}" + "m{|w~}bv~jv~`w~}k{}w~|dv~k{}w~|Zw~}N{|m~|Uv~S{|w~|m{}w~|]v~t{|v~`v~w{}x~}w{|x~}w{}w~[{|u~|T{|w~}u{}w~|S{}v~|V{|" + "x}t~}Jw~|K{|s~y}|d{|y~}n{|}p~}1y~}p{}w~p{}y~fv~mv~\\v~lv~|`{}w~|r{|v~}`v~jv~\\{|p~}Xw~}m{|w~}\\{}w~u{}w~|Xv~|v{" + "|v~]{|w~|m{}w~|h{|v~p{}w~pv~}iv~t{}w~t{}w~|b{|w~}q{|w~}`v~t{}w~t{|w~}bw~}k{}w~|dv~jv~X{|w~}W{}w~|l{|w~}av~|n{|v" + "~Wu~|T{}w~}U{}v~dv~}k{|v~}s{|v~s{|v~}h{}w~}hv~|e{}u~|x{|v~w{}u~|h{}v~c{}v~l{|u~}\\|]{}w~][|u~|]{}w~\\{}v~}c|u~}" + "]{}w~ s{|w~V{|w~}sy~}S{|v~P{}x~}o{|w~`{|a~}+u~|rw~|1y~}v{}w~ex~d{|j~}]{}w~}v{|v~|@w~}7y~}sw~u{}w~rx~ P{}w~ p{|" + "w~|m{}w~|Ux~}w{|x~} w{|j~}V{|v~|v{}w~}Xw~oy~}x{}x~aw~|i{|x~|cw~ox~x{}x~^{}w~|Xv~}n|}v~`v~}n|}v~`v~}n|}v~`v~}n|" + "}v~`v~}n|}v~a{|b~h{}v~p|}v~Y{}v~I{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~f{|v~|h{}w~|rv~}wv~i{}v~c{" + "}v~k{}v~c{}v~k{}v~c{}v~k{}v~c{}v~k{}v~c{}v~^{}q~|`{}v~v{|w~}n{}v~h{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}Z{" + "|v~|V{}w~}p{|u~|`w~}p{|t~}`{|q~t}v~|_{|q~t}v~|_{|q~t}v~|_{|q~t}v~|_{|q~t}v~|_{|q~t}v~|i{|q~t}`~|kv~N`~|c`~|c`~|" + "c`~|Xv~Lv~Lv~Lv~Wv~jv~`w~}m{|w~}bv~jv~bv~jv~bv~jv~bv~jv~bv~jv~f{|Z~}fv~u{|x~}uv~a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m" + "{}w~|a{|w~|m{}w~|]{|w~}u{}w~|\\w~}k{}w~|_{|w~}u{}w~| U{}x~|q{}w~q{|w~j{}x~|b{|w~j{}x~|uu~|u~|v{|w~j{}x~|uu~|o{|" + "w~j{}x~|qv}|r{|w~[{|w~|S{|v~uv~| TZ~}a{|w~}wx~'{|\\~iW~|ee~|^{|g~}_w~}kw~}aw~}kw~|]v~|u{}w~|X{}w~}wv~|b{|w~| " + " r{}g~ u{|w~({}Z~X{|y~|w{}v~|Zw~|v{|w~s{|w~o{|w~}p{}u~vw~})v~Fv~| w{}w~ x{|m~ y{|w~|Vv~|lv~|W{}w~}O{}v~}C{}w~}" + "c{|w~n|}w~}v|N{}w~}au~l{|v~Wv~}Xv~}m{|v~|[{|y}w~y}|x{|v~ V{|}p~}|XY|X{|}q~}|Z{}w~}`{|x~|sw~mw~|tw~l{|b~|b{}w~}k" + "{}v~e{|v~|N{}w~}fv~}d{}w~}L{}w~}T{|v~|ev~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}w{|t~V{}w~}V{}w~|t{}w~|w~|tv~|h{}w~|r{|v~" + "wv~i{|v~|d{}v~d{}w~}T{|v~|d{}v~f{}w~}o{}v~J{|u~Y{|v~|Z{|v~h{}w~}_{}w~}v{}w~}b{}w~}x{}x~}r{|w~wv~b{|v~|x{|v~|S{|" + "v~|S{}v~|L{|w~}Gw~|K{|w~| t{|u~}|q{}w~|_v~k{}w~|bv~Nv~k{}w~|b`~|Yw~}Vv~k{}w~|`w~}m{|w~}Wv~Lv~Tw~}|u~Kv~_w~}mv~" + "mv~hw~}m{|w~}bv~jv~`w~}k{}w~|dv~k{}w~|Zw~}L{|}o~}Vv~S{|w~|m{}w~|]{}w~}u{}w~}`{}w~|xw~|w{|w~wv~\\{|s~Sv~uv~S{}v~" + "|O{}v~}Kw~|L{|v~}|_{|~|j{|y}x~y}|/x~q{|v~}qx~fv~m{}x~}\\v~l{}w~|`v~pv~}`v~jv~]n~}Xw~}m{|w~}\\{|w~|vv~X{|v~t{}w~" + "|^{|w~|m{}w~|h{|v~p{}w~pv~|iv~t{}w~t{}w~|b{|w~}q{|w~}`v~t{}w~t{|w~}bw~}k{}w~|dv~jv~X{|w~}W{}w~}l{}w~}b{|v~lv~|Y" + "{}v~|S{}w~}U{|v~}f{|v~|ju~|t{|v~s{}v~|h{}w~}hv~|dt~}y{|v~y{|t~|g{|v~|d{}v~k{|u~|?{}w~>u~|b{|v{}w~[{}v~|e{}v~}\\" + "{}w~ s{|w~V{|w~}sy~}S{|v~P{|w~o{}x~}`{|a~}+{|u~}|u{|w~0{}y~v{|w~}g{|y~}d{|j~}\\{}v~|w{|v~}Aw~}7{}y~sw~tw~}t{|y~" + "} P{}w~ p{|w~|m{}w~|Ux~}w{|x~} w{|j~}W{|v~|vv~}X{}x~|p{}y~|x{}x~b{}x~}hw~c{}x~}p{}y~|x{}x~^v~X{|b~|b{|b~|b{|b" + "~|b{|b~|b{|b~|b{}b~}id~Y{|v~|J{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~f{}v~g{}w~|r{|v~wv~i{|v~|d{}v" + "~k{|v~|d{}v~k{|v~|d{}v~k{|v~|d{}v~k{|v~|d{}v~_{}v~}u~|a{|v~|ww~}m{}v~h{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w~}f{|v~h{}w" + "~}Z{|v~|V{}w~}sy|s~_w~}n{}u~|b{|u~}|q{}w~|`{|u~}|q{}w~|`{|u~}|q{}w~|`{|u~}|q{}w~|`{|u~}|q{}w~|`{|u~}|q{}w~|j{|u" + "~}|q{}a~|kv~N`~|c`~|c`~|c`~|Xv~Lv~Lv~Lv~Wv~jv~`w~}m{|w~}bv~jv~bv~jv~bv~jv~bv~jv~bv~jv~.v~v{|w~tv~a{|w~|m{}w~|a{" + "|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|\\v~uv~[w~}k{}w~|^v~uv~ T{}x~}q{}w~q{|x~}j{}x~}b{|x~}j{}x~}vu~|{|u~|w{|x~}j{}" + "x~}vu~|n{|x~}j{}x~}b{|x~}[{|w~Qv~|w{|v~ SZ~}`v~x{|y~}'{|]~}iW~|e{|g~}\\{}i~}^w~}kw~}aw~}l{|w~|^{|v~t{|w~}X{|v~x" + "{|v~`w~} m{|v~ jw|({}Z~X{|y~|v{}w~}[{}x~}u{}x~}s{|w~o{}w~}o{}u~x{|w~|)v~Fv~ v{}w~ g{}w~Uv~|lv~|W{}w~}P{}v~" + "}B{|v~c{|_~|O{}w~}a{}v~l{|v~X{|v~|Y{|v~|lv~|N{|v~ S{|}p~|[{|Z~}[{|}p~}|X{}w~}`{|x~|sw~|nw~|u{|x~}l{}b~}b{}w~}k{" + "|v~e{|v~}N{}w~}g{|v~}d{}w~}L{}w~}T{|v~}ev~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}v{|t~W{}w~}V{}w~|t{|r~sv~|h{}w~|q{}w~}xv" + "~i{|v~}dv~}d{}w~}T{|v~}dv~}f{}w~}nv~}J{}v~Y{|v~|Z{|v~|i{}w~}_{|v~vv~|b{}w~}xw~|qw~|y{|v~bv~}v{}v~S{|v~|T{}v~}K{" + "|w~}G{}x~}K{|w~| tv~}n{}w~|_v~kv~|bv~|Ov~k{}w~|bv~Bw~}Vv~k{}w~|`w~}m{|w~}Wv~Lv~Tw~}{|u~|Mv~_w~}mv~mv~hw~}m{|w~" + "}bv~|kv~`v~k{}w~|dv~k{}w~|Zw~}Iy|}q~Wv~S{|w~|m{}w~|]{|v~uv~_{|w~|xw~uw~|y{|w~}\\r~}T{|w~|w{}w~}T{}v~|M{|v~Kw~|L" + "{}w~} O{}y~|rt~|s{|y~}fv~|nw~}\\v~l{|w~}`w~}p{}w~|`v~|kv~^u~}|Qw~}m{|w~}[w~}w{}w~}X{}w~|t{|w~}^{|w~|m{}w~|h{|v~" + "pv~pv~|iv~t{}w~t{}w~|b{|w~}q{|w~}`v~t{}w~t{|w~}bv~k{}w~|dv~|l{|v~X{|w~}W{|w~}l{}w~|b{}w~}l{}w~}Z{|v~}R{}w~}T{}v" + "~f{}v~i{|u~t{|v~t{|u~g{}w~}hv~|cr~}v~}s~}f{|v~}dv~}j{|u~|@{}w~?u~|b{}~|w{}w~vy~a{}v~|g{}v~}b{}~|w{}w~vy} {{}w~|" + "W{|w~}sy~}S{|v~Ow~}q{|w~|`{|a~}){}u~}vw~}0{|y~}v{}w~}p{|t{}y~|d{|j~}[{|v~|vv~}Bw~}7{|y~}tw~t{|w~|u{}y~| P{}w~ " + "p{|w~|m{}w~|Ux~}w{|x~} w{|j~}X{}v~v{|v~}X{|w~p{|y~|w{}x~bw~h{}x~|d{|w~p{|y~}w{}x~^v~X{}b~}b{}b~}b{}b~}b{}b~}b{" + "}b~}b`~j{}d~Y{|v~}J{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~fv~}g{}w~|q{}w~}xv~i{|v~}dv~}k{|v~}dv~}k" + "{|v~}dv~}k{|v~}dv~}k{|v~}dv~}`{}v~|{|u~|b{|v~}x{}x~}lv~}h{|v~|i{}w~}f{|v~|i{}w~}f{|v~|i{}w~}f{|v~|i{}w~}Z{|v~|V" + "{}e~|_w~}m{|u~bv~}n{}w~|`v~}n{}w~|`v~}n{}w~|`v~}n{}w~|`v~}n{}w~|`v~}n{}w~|jv~}n{}w~Tv~|Ov~Lv~Lv~Lv~Av~Lv~Lv~Lv~" + "Wv~|l{|v~`w~}m{|w~}bv~|kv~bv~|kv~bv~|kv~bv~|kv~bv~|kv~.v~vw~|u{|v~a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}" + "w~|\\{|w~|w{}w~}[v~k{}w~|^{|w~|w{}w~} T{|w~q{}w~q{}x~|j{|w~b{}x~|j{|w~wu~|x{|u~|x{}x~|j{|w~wu~|m{}x~|j{|w~b{}x~" + "|[{|w~Q{|w~}w{}w~} SZ~}`{}w~|y{}y~|'{|n~y}~|n~}i{}k~x}k~c{|i~}Z{}j~]w~}kw~}a{}w~l{|w~|^{}w~}sv~Wv~|y{}w~}`{}w~|" + " mv~| o{}Z~X{|y~|v{|w~}\\{|w~t{}x~|rw~|p{}w~}n{}u~yw~}(v~|Gv~ v{}w~ gw~}U{}w~}m{|v~V{}w~}Q{}v~}A{|v~c{|_~" + "|O{}w~}a{}v~l{|v~X{}v~X{|v~k{}w~}N{}w~} Q{|}p~}|^{|Z~}^{|}p~}|U{}w~}`{|x~}sw~|o{|w~|u{}x~|l`~b{}w~}k{|v~|eu~N{}" + "w~}g{}v~|d{}w~}L{}w~}Su~ev~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}u{|t~X{}w~}V{}w~|ss~}sv~|h{}w~|q{|v~|yv~hu~e{|v~|d{}w~}" + "Su~e{|v~|f{}w~}n{}v~|K{|v~|Z{|v~|Yv~|i{}w~}^v~|x{}v~a{|v~y{|w~|q{}x~}y{}w~}c{}v~tv~}T{|v~|U{|v~}J{|w~}G{|w~K{|w" + "~| u{|v~m{}w~|_v~kv~a{}w~|O{}w~|l{}w~|bv~|Cw~}V{}w~|l{}w~|`w~}m{|w~}Wv~Lv~Tw~}y{|u~|Nv~_w~}mv~mv~hw~}m{|w~}bv~" + "|l{|v~`v~kv~cv~|l{}w~|Zw~}D{|}u~}Xv~S{|w~|m{}w~|\\{}w~|w{|w~}^w~}y{|w~u{}x~}y{}w~|]{}q~|Tv~wv~|U{|v~}K{}w~|Lw~|" + "Lv~ N{|x~s{}x~{w~|tx~|fv~|o{|v~\\v~l{|w~}a{|w~|p{}w~_{}w~|l{|v~_{}v~|Ow~}m{|w~}[{}w~|xv~X{|v~rv~|_{|w~|m{}w~|h{" + "|v~|qv~pv~|iv~|u{}w~t{}w~|b{|w~}q{|w~}`v~t{}w~t{|w~|bv~kv~c{}w~|l{|v~X{|w~}Vv~l{}w~|bv~|l{|v~[{|v~}Q{}w~}T{|v~}" + "h{|v~|hu~}u{|v~u{|u~|g{}w~}hv~|b{}f~|du~e{|v~|i{|u~|A{}w~@u~|b{}x~|x{}w~ww~a{}v~|i{}v~}b{}x~|x{}w~w{}y~} {}w~|W" + "{|v~sy~}S{|v~O{|w~}s{}w~}^q|}v~q|'{}t~|{|w~}.x~u{}v~}|wy|}y~tx~/{|v~|v{}w~}Cw~}6x~tw~s{}w~ux~ O{}w~ p{|w~|m{}w" + "~|Ux~}w{|x~} B{}w~}v{|v~|Ww~|q{|y~}v{}x~c{}x~|i{}x~}cw~|q{|y~}v{}x~_{|v~X`~b`~b`~b`~b`~c{|`~|kc~Xu~J{}w~}M{}w~" + "}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~g{|v~}g{}w~|q{|v~|yv~hu~e{|v~|ju~e{|v~|ju~e{|v~|ju~e{|v~|ju~e{|v~|a{}" + "v~|x{|u~|bu~y{}w~l{|v~|gv~|i{}w~}ev~|i{}w~}ev~|i{}w~}ev~|i{}w~}Z{|v~|V{}f~|^w~}l{|v~|d{|v~m{}w~|a{|v~m{}w~|a{|v" + "~m{}w~|a{|v~m{}w~|a{|v~m{}w~|a{|v~m{}w~|k{|v~m{}w~T{}w~|Ov~|Mv~|Mv~|Mv~|Bv~Lv~Lv~Lv~W{}w~|l{|v~`w~}m{|w~}bv~|l{" + "|v~bv~|l{|v~bv~|l{|v~bv~|l{|v~bv~|l{|v~.v~|x{}x~|t{|v~a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|a{|w~|m{}w~|[v~wv~|[v" + "~kv~\\v~wv~| Sw~|r{}w~r{|w~hw~|d{|w~hw~|yu~|v{|u~y{|w~hw~|yu~|m{|w~hw~|d{|w~Z{|w~Pv~wv~| SZ~}_w~}yx~%n~{|~{|o~|" + "i{|l~}|}|l~}b{}j~Xk~|]w~}kw~}a{}w~l{}w~]v~|s{}w~|X{}w~}yv~|_w~} mv~} g{}x~}t{}x~}O{|y~|uw~}\\{}x~|t{}x~|rw" + "~|p{|w~}m{}u~}w~|({}w~|H{|w~} v{}w~ h{|w~|U{}w~}m{|v~V{}w~}R{}v~}@{|v~c{|_~|Ov~|a{|v~l{}w~}Xv~}X{|v~k{}w~}Nv~|" + " N{|}p~}|a{|Z~}a{|}p~}|R{|w}|_x~}s{}x~}o{}w~|v{|w~l{}`~|c{}w~}k{|v~|e{}v~|O{}w~}gu~c{}w~}L{}w~}S{}v~|fv~|h{}w~}" + "hv~|Y{}w~}M{}w~}W{}w~}t{|t~Y{}w~}V{}w~|s{}t~rv~|h{}w~|p{}w~}yv~h{}v~|f{}v~c{}w~}S{}v~|f{}v~e{}w~}mv~}K{|v~|Z{|v" + "~|Yv~|iv~|^{}w~}xv~}`v~|{|w~p{}w~yv~|d{|v~|t{|v~|U{|v~|V{|u~I{|w~}Fw~|L{|w~| u{}w~|mv~|_v~|m{|v~a{}w~}O{}w~|lv" + "~|b{}w~|Cw~}V{}w~|lv~|`w~}m{|w~}Wv~Lv~Tw~}x{|u~|Ov~_w~}mv~mv~hw~}m{|w~}b{}w~|l{|w~}`v~kv~c{}w~|lv~|Zw~}B{|u~Xv~" + "S{|w~|m{}w~|\\{|w~}w{}w~|^v~y{}x~}u{|x~}y{}w~]{|v~|}v~T{}w~|y{|w~}U{|v~}J{|w~}Lw~|M{|w~} Mx~}v{|w~|{|w~|v{}x~e{" + "}w~|o{}v~\\v~l{|w~}a{|w~|pw~}_{}w~|l{|w~}_v~Mw~}m{|w~}[{|w~}y{|w~}X{}w~|r{}w~}_{|w~|m{}w~|h{|v~|qv~|r{|v~|i{}w~" + "|u{}w~tv~|b{|w~}q{|w~}`v~t{}w~t{}w~|bv~kv~c{}w~|l{|w~}X{|w~}Vv~lv~b{}v~jv~|\\u~P{}w~}S{}v~|iu~g{|t~|w{|v~v{}u~}" + "f{}w~}hv~|a{}h~|c{}v~|f{}v~g{|u~|B{}w~Au~|b{}v~|y{}w~xu~a{}v~|k{}v~}b{}v~|y{}w~x{}w~}!{}w~|Vv~sy~}S{|v~O{|u~}y|" + "{y|u~}T{|w~}Lw}|P{|}p~}-{|y~}u{}l~u{}y~|.{|v~|v{}w~}Dw~}6{|y~}uw~rw~}w{}y~| O{}w~ p{|w~|m{}w~|Ux~}w{|x~} C{}w" + "~}v{|v~|W{}x~}px~u{}x~d{|w~i{}x~}c{}x~}px~u{}x~_{}w~}Y{}`~|d{}`~|d{}`~|d{}`~|d{}`~|d{}w~}j|}w~}l{|c~X{}v~|K{}w~" + "}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~gu~|g{}w~|p{}w~}yv~h{}v~|f{}v~i{}v~|f{}v~i{}v~|f{}v~i{}v~|f{}v~" + "i{}v~|f{}v~a{}v~|v{|u~|c{}v~|}w~|l{}v~fv~|iv~|ev~|iv~|ev~|iv~|ev~|iv~|Z{|v~|V{}h~}\\w~}k{}w~|d{}w~|mv~|a{}w~|mv" + "~|a{}w~|mv~|a{}w~|mv~|a{}w~|mv~|a{}w~|mv~|k{}w~|mv~|U{}w~}O{}w~|M{}w~|M{}w~|M{}w~|Bv~Lv~Lv~Lv~W{}w~}l{}w~}`w~}m" + "{|w~}b{}w~|l{|w~}b{}w~|l{|w~}b{}w~|l{|w~}b{}w~|l{|w~}b{}w~|l{|w~}.{}w~|y{}x~|s{|w~}a{|w~|m{}w~|a{|w~|m{}w~|a{|w" + "~|m{}w~|a{|w~|m{}w~|[{}w~|y{|w~}Zv~kv~\\{}w~|y{|w~} R{|w~r{}w~rw~}h{|w~dw~}h{|w~y{|w~|t{|w~}yw~}h{|w~y{|w~|lw~}" + "h{|w~dw~}Z{|w~P{}w~|y{|w~} Rs}v~g}|_{}w~{|y~}%{|p~|{|~yp~}g{}m~{}~{}m~|a{}l~|X{|m~}\\w~}kw~}a{|w~|mv~]v~r{}w~}X" + "{|v~{|v~^{}w~} n{}v~ gw~|tw~|O{|y~|uw~}]{|x~}sw~|rw~|p{|v~l{}r~}'{|w~}H{|w~} v{}w~ h{|w~T{|v~m{}w~}V{}w~}" + "S{}v~}?{|v~c{|_~|Ov~|`v~|m{}w~}Y{|v~W{|v~k{}w~}O{|v~ J{|}p~}|d{|Z~}d{|}p~}|-w~s{|w~ov~|v{}x~|lv~|j{|v~c{}w~}k{}" + "v~cv~}O{}w~}h{}v~|c{}w~}L{}w~}Rv~}fv~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}rt~Z{}w~}V{}w~|ru~}rv~|h{}w~|p{|v~|{v~h{|v~}g" + "{|v~}c{}w~}Rv~}g{|v~}e{}w~}m{|v~|L{|v~|Z{|v~|Y{}w~}j{|v~|^{|v~|{|v~_{}w~}{}x~}p{|w~yv~cv~}r{}v~U{|v~|W{|u~|I{|w" + "~}F{}x~}L{|w~| u{}w~|n{|v~|_v~}m{}w~}a{|w~}O{|w~}m{|v~|b{}w~}Cw~}V{|w~}m{|v~|`w~}m{|w~}Wv~Lv~Tw~}vu~|Pv~_w~}mv" + "~mv~hw~}m{|w~}b{|w~}l{}w~}`v~|m{|w~}c{|w~}lv~|Zw~}@v~|Yv~S{|w~}mv~|[v~wv~]{}w~|{w~|u{|w~yw~}]v~}y{}v~U{|w~}y{}w" + "~|V{|v~}I{|w~}Lw~|M{|w~} M{|w~x}v~}x{}v~x}w~|e{}w~}ou~|]v~l{|w~|a{|w~p{}w~|_{|w~}l{}w~}`{|w~}Mw~}m{|w~}Zv~y{}w~" + "|Y{|v~q{|v~_{|w~}m{}w~|gv~|r{|v~|r{|v~h{}w~}u{}w~tv~a{|w~}q{|w~}`v~t{}w~t{}w~|bv~|m{|w~}c{|w~}l{}w~}X{|w~}V{}w~" + "|n{|w~}bv~}j{}v~]{}v~|P{}w~}Ru~j{}v~|f{|t~}|y{|v~x{|t~}e{}w~}hv~|`{|}l~}`v~}g{|v~}f{|u~|C{}w~Bu~|`u~|{}w~yu~|`{" + "}v~|m{}v~}a{|u~|{}w~y{}v~}!{}w~|Vv~|ty~}S{|v~P{|g~}U{|w~}Lw~|N{|r~}+{}y~|u{|}o~}v{|y~}+v~}v{}v~Ew~}5{}y~|vw~r{|" + "w~|y{|y~} N{}w~ p{|w~}m{}w~|Ux~}w{|x~} Dv~}v{}v~|W{|w~p{}y~|u{}x~dw~|j{}w~c{|w~p{}y~|u{}x~`{}v~|Yv~|j{|v~dv~|" + "j{|v~dv~|j{|v~dv~|j{|v~dv~|j{|v~dv~|j{|v~l{}w~}n{|v~Wv~}K{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~h{" + "|v~}f{}w~|p{|v~|{v~h{|v~}g{|v~}i{|v~}g{|v~}i{|v~}g{|v~}i{|v~}g{|v~}i{|v~}g{|v~}b{}v~|t{|u~|cq~|l{|v~}f{}w~}j{|v" + "~|e{}w~}j{|v~|e{}w~}j{|v~|e{}w~}j{|v~|Z{|v~|V{}k~}|Zw~}k{}w~}d{}w~|n{|v~|a{}w~|n{|v~|a{}w~|n{|v~|a{}w~|n{|v~|a{" + "}w~|n{|v~|a{}w~|n{|v~|k{}w~|n{|v~}U{|w~}O{}w~}M{}w~}M{}w~}M{}w~}Bv~Lv~Lv~Lv~W{|v~lv~|`w~}m{|w~}b{|w~}l{}w~}b{|w" + "~}l{}w~}b{|w~}l{}w~}b{|w~}l{}w~}b{|w~}l{}w~}Xt|X{}w~}{}x~}r{}w~}a{|w~}mv~|a{|w~}mv~|a{|w~}mv~|a{|w~}mv~|[{|w~}y" + "{}w~|Zv~|m{|w~}\\{|w~}y{}w~| Qw~}s{}w~s{}w~fw~}f{}w~fw~}y{|y~|r{|y~}y{}w~fw~}y{|y~|l{}w~fw~}f{}w~Y{|w~P{|v~y{}w" + "~| Kv~}J{|w~|}y~|${}r~}y{}~y{|q~f{|n~|{}~yn~}_m~|V{|o~}[w~}kw~}`w~}n{|w~}^{|w~}r{|v~Wv~{}w~}]v~| o{|v~| hw" + "~t{|w~N{|y~|uw~}]w~|s{}x~|rw~|ov~|l{}s~&{|w~}H{}w~| v{}w~ h{}x~}Sv~|nv~|V{}w~}T{}v~}>{}w~}Q{}w~}J{}v~_{}w~}mv~" + "}Y{}w~}Vv~|lv~|Ov~} G{|}p~}|0{|}o~}*{}x~rw~}q{}v~|w{}w~l{|v~hv~|d{}w~}ku~c{}v~}P{}w~}i{}u~b{}w~}L{}w~}R{}v~|gv~" + "|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}qt~[{}w~}V{}w~|r{}v~|rv~|h{}w~|o{}w~}{v~g{}v~|hu~|c{}w~}R{}v~|hu~|e{}w~}lv~}L{}v~Y" + "{|v~|Y{}v~j{}v~\\{}w~}{}w~}_{|v~{w~|ow~y|v~d{}v~pv~}V{|v~|Wu~|H{|w~}F{|w~L{|w~| u{}w~m{}v~|_u~mv~|a{|v~Nv~|n{}" + "v~|b{|v~Cw~}Uv~|n{}v~|`w~}m{|w~}Wv~Lv~Tw~}uu~|Qv~_w~}mv~mv~hw~}m{|w~}b{|v~lv~|`v~}m{}w~}c{|v~m{|v~|Zw~}@{}w~|Yv" + "~S{|w~}mv~|[{}w~|y{|w~}]{|w~}{w~sw~y|w~}^{}w~}wv~}U{}w~yv~Uv~}Gw~}Lw~|M{|w~| L{|q~}v{}q~|d{|w~}p{|u~|]v~l{}w~|a" + "{|w~pv~^{|v~lv~|`{|w~|Mw~}m{|w~}Z{}w~y|v~X{}w~}pv~|`{|w~}mv~|gv~}r{|v~}r{}v~h{|v~u{}w~u{|w~}a{|w~}q{|w~}`{}w~|u" + "{}w~tv~av~}m{}w~}c{|v~lv~|X{|w~}V{|w~}n{}w~|c{|v~i{|v~|_{}v~}O{}w~}R{|v~}l{}v~|d{|r~y}v~y}s~}d{}w~}hv~|]{|}s~y}" + "|^{}v~|hu~|e{|v~}C{}w~C{}v~|^u~|}w~{}v~|^{}v~n{|v~}_{|u~|}w~{}v~} {}w~|V{}w~}ty~}S{|v~Q{}e~}V{|w~}Lw~|L{|t~*{|x" + "~|t{|y}u~}|u{|x~|*{}w~|v{|v~Fw~}5{|x~|ww|qw|y{|x~| >{|w~}mv~|Ux~}w{|x~} Ev~}v{|v~U{}x~|q{|y~}t{}x~e{}x~}j{}w" + "~b{}x~|q{|y~}t{}x~a{}v~}Y{|v~hv~|f{|v~hv~|f{|v~hv~|f{|v~hv~|f{|v~hv~|f{}v~hv~|n{|v~|n{|v~W{}v~}L{}w~}M{}w~}M{}w" + "~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~i{|u~|f{}w~|o{}w~}{v~g{}v~|hu~|h{}v~|hu~|h{}v~|hu~|h{}v~|hu~|h{}v~|hu~|c{}" + "v~|r{|u~|d{}s~|ku~|f{}v~j{}v~d{}v~j{}v~d{}v~j{}v~d{}v~j{}v~Y{|v~|V{}w~}r|Vw~}k{|w~|d{}w~m{}v~|a{}w~m{}v~|a{}w~m" + "{}v~|a{}w~m{}v~|a{}w~m{}v~|a{}w~m{}v~|k{}w~m{}u~U{|v~O{|v~M{|v~M{|v~M{|v~Bv~Lv~Lv~Lv~Vv~|n{|v~_w~}m{|w~}b{|v~lv" + "~|b{|v~lv~|b{|v~lv~|b{|v~lv~|b{|v~lv~|X{}u~X{|v~|x~}qv~|a{|w~}mv~|a{|w~}mv~|a{|w~}mv~|a{|w~}mv~|Z{}w~yv~Yv~}m{}" + "w~}[{}w~yv~ P{|w~}t{}w~t{|w~}f{|w~}h{|w~}f{|w~}yy|p{|}y{|w~}f{|w~}yy|l{|w~}f{|w~}h{|w~}Y{|w~Ov~y|v~ K{}w~}Hw~}y" + "~}\"{}t~}x{}~x{|s~d{|p~}y{}~y{|p~}]o~|T{}p~Zw~}kw~}`{}w~|o{}w~|^{}w~|qv~|X{}w~|v~|]{|v~| o{}v~j{} {|x~}t{|" + "x~}N{|y~|v{}w~}^{}x~}r{}x~}rw~|o{}v~k{}u~|%v~Hv~ u{}w~ hw~|S{}v~o{}v~U{}w~}U{}v~}>{|v~}Q{}w~}Ju~_{|v~n{|v~|Z{|" + "v~|Vv~}m{|v~|P{}v~ C{}o~}4{|o~}|({|x~}s{}w~}s{}u~|x{}w~|l{}w~}h{}w~}d{}w~}l{|v~}bu~|g{|}g{}w~}j{}u~|b{}w~}L{}w~" + "}R{|u~|hv~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}pt~\\{}w~}V{}w~|r{|v}qv~|h{}w~|nv~|v~g{|u~|j{}v~}b{}w~}R{|u~i{}v~}d{}w~}" + "l{|v~|M{}v~Y{|v~|Y{|v~|kv~}\\{|v~{v~|_{|v~|w~|o{}x~y}w~}e{|v~|p{|v~|W{|v~|X{}v~}G{|w~}F{|w~|M{|w~| u{}w~|nu~|_" + "u~}o{}v~_v~}O{}w~}o{|u~|av~}Dw~}U{}w~}o{|u~|`w~}m{|w~}Wv~Lv~Tw~}t{}v~|Rv~_w~}mv~mv~hw~}m{|w~}av~|n{|v~_u~mv~|bv" + "~|n{}v~|Zw~}@{}w~|Yv~Rv~n{}v~|[{|w~}y{}w~|\\w~}|x~}s{}x~y}w~|_{}v~v{|v~|V{|w~y}w~}Vu~Fw~}Lw~|M{|w~| K{|s~}t{}s~" + "|bv~p{}u~}]v~|mv~`{|w~q{}w~}]v~}n{}v~_{|w~|Mw~}m{|w~}Yw~y}w~|Xv~o{|w~}`{|v~n{|v~|g{}v~r{}u~rv~}gv~|v{}w~uv~|a{|" + "w~}q{|w~}`{|w~}u{}w~u{|v~au~mv~|bv~}n{}v~Vv~Uv~nv~b{}w~}hv~}`{|v~}N{}w~}Q{|v~}n{}v~}b{|c~}c{}w~}hv~|Z{|v~Z{|u~|" + "j{}v~}c{|w~B{}w~B{}x~|\\u~}w~}v~|\\{}x~|m{}x~}]{|u~}w~}v~} {{v~|V{|v~|uy~}S{|v~R{}v~y|q~}|u~W{|w~}Lw~|J{}u~*{|x" + "~|e{|x~|({}x~}u{|w~F{|x}|4{|x~|e{|x~| ={|v~n{|v~|Ux~}w{|x~} Ew~|u{|x~}U{|x~}p{}j~}iw~j{}w~b{|x~}p{}j~}f{}v~}" + "X{}w~}h{}w~}f{}w~}h{}w~}f{}w~}h{}w~}f{}w~}h{}w~}f{}w~}h{}w~}fv~}h{}w~}n{}w~}m{|v~Vu~|g{|}c{}w~}M{}w~}M{}w~}M{}w" + "~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~j{|u~}e{}w~|nv~|v~g{|u~|j{}v~}g{|u~|j{}v~}g{|u~|j{}v~}g{|u~|j{}v~}g{|u~|j{}v~}c{" + "}v~|p{|u~|e{|t~}k{}v~}e{|v~|kv~}d{|v~|kv~}d{|v~|kv~}d{|v~|kv~}Y{|v~|V{}w~}Mw~}k{}w~|d{}w~|nu~|a{}w~|nu~|a{}w~|n" + "u~|a{}w~|nu~|a{}w~|nu~|a{}w~|nu~|k{}w~|nt~|Uv~}Ov~}Mv~}Mv~}Mv~}Cv~Lv~Lv~Lv~V{}v~nv~}_w~}m{|w~}av~|n{|v~`v~|n{|v" + "~`v~|n{|v~`v~|n{|v~`v~|n{|v~W{}u~Wr~q{|v~_v~n{}v~|`v~n{}v~|`v~n{}v~|`v~n{}v~|Z{|w~y}w~}Yu~mv~|[{|w~y}w~} O{}w~}" + "u{}w~u{}w~}d{}w~}j{}w~}d{}w~}j{}w~}d{}w~}j{}w~}d{}w~}j{}w~}X{}w~O{|w~y}w~} L{}w~}G{}u~|!{|}x~}|w{}~v{}w~}b{|r~|" + "x{}~w{}s~|\\{|q~}Rq~|Zw~}kw~}`{|v~p{|v~]v~p{}w~}X{|q~[{}v~} p{|v~}ly}$v}|\"{}x~}t{}x~}Yy}|s{|y~|w{|v~|_{|w~" + "q{}x~}s{|w~n{|v~}l{|u~}%{}w~|Iw~} u{}w~L{}w~} tv}|P{|w~R{|v~|pv~}U{}w~}V{}v~}={}v~|Q{}w~}K{}v~|^v~|o{}v~Y{}v~U{" + "}v~m{}v~P{|v~}U{|v}M{}w~}F{|}q~}6{|q~}|G{|w}|^w~ru~y|x{|}t~y|}v~|kv~|h{|v~d{}w~}m{|u~|b{|u~|i{|~}g{}w~}l{|}u~}a" + "{}w~}L{}w~}Q{}u~|iv~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}ot~]{}w~}V{}w~|bv~|h{}w~|n{}q~f{}u~k{}u~a{}w~}Q{}u~k{|u~c{}w~}" + "kv~}c{|}h{|v~}Y{|v~|X{}v~l{}v~|[v~}v~]v~}w~n{}r~|ev~}n{}v~W{|v~|Y{}v~}F{|w~}Ew~}M{|w~| u{}w~|o{}u~|_t~|q{|v~}_" + "{|v~|P{|v~}pt~|a{|v~|Ew~}U{|v~|pt~|`w~}m{|w~}Wv~Lv~Tw~}s{}v~|Sv~_w~}mv~mv~hw~}m{|w~}a{}v~nv~}_u~}o{}v~a{}v~o{|u" + "~|Zw~}@{}w~|Y{}w~|Sv~|p{|u~|Zv~{|v~[v~}x~}s{|r~_{|v~|u{}v~Uq~V{}v~|Fw~}Lw~|M{|w~| I{|y}~y}|r{|}x~}|`{}w~}qs~]u~" + "n{|v~`{|w~r{|v~\\{|v~nv~}_{|w~}Mw~}m{|w~}Y{}r~X{}w~}nv~`{|v~|o{}v~|g{|v~|st~|t{|v~|g{}v~v{}w~v{|v~`{|w~}q{|w~}_" + "v~|v{}w~uv~}au~}o{}v~a{|v~nv~}Vv~U{|w~}p{}w~}bv~|h{|v~`u~M{}w~}P{|u~|q{}v~}_{}g~}|b{}w~}hv~|Z{|v~Y{}u~k{}u~a{|y" + "~A{}w~A{}~|Zl~|Z{}~|k{}~}[{|l~} yv~}Uv~}uy~}S{|v~S{}v~|x{|y}x~}|wu~X{|w~}Lw~|I{|v~}*{}x~|g{|x~}&{}y~}t{|x~ T{}x" + "~|g{|x~} <{|v~|o{}v~|Ux~}w{|x~} Ex~|t{|y~}Tw~|p{}j~}j{}x~|k{}x~}aw~|p{}j~}g{}v~}Wv~|h{|v~fv~|h{|v~fv~|h{|v~f" + "v~|h{|v~fv~|h{|v~g{|v~g{|v~|ov~|m{|v~V{|u~|i{|~}c{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~k{}t~d{}w~" + "|n{}q~f{}u~k{}u~e{}u~k{}u~e{}u~k{}u~e{}u~k{}u~e{}u~k{}u~c{}v~|n{|u~|e{}u~|l{}u~c{}v~l{}v~|c{}v~l{}v~|c{}v~l{}v~" + "|c{}v~l{}v~|Y{|v~|V{}w~}Mw~}kv~c{}w~|o{}u~|a{}w~|o{}u~|a{}w~|o{}u~|a{}w~|o{}u~|a{}w~|o{}u~|a{}w~|o{}u~|k{}w~|o{" + "}s~U{|v~|P{|v~|N{|v~|N{|v~|N{|v~|Dv~Lv~Lv~Lv~Uv~}p{}v~^w~}m{|w~}a{}v~nv~}`{}v~nv~}`{}v~nv~}`{}v~nv~}`{}v~nv~}W{" + "}u~W{}t~|qv~}_v~|p{|u~|`v~|p{|u~|`v~|p{|u~|`v~|p{|u~|Yq~Xu~}o{}v~Yq~ M{}w~}|w{}x~}v{}v~b{}w~}|m{}v~b{}w~}|m{}v~" + "b{}w~}|m{}v~b{}w~}|m{}v~W{}x~}Nq~| M{|v~F{|u~ py~|V{|}y~y}|vy~|w{|y}y~y}Y{|s~}Q{|s~|Yw~}kw~}_{}v~|s{}v~|^{|w~}p" + "{|v~X{|r~}Z{}u~} q{}v~}o{|y~}$v~}\"w~|tw~|Y{}y~}|u{|y~|x{|u~^{}x~|q{|w~s{}x~}mu~}n{|s~}&{|w~|J{|w~| u{}w~L{" + "}v~ u{|v~|P{}x~}Q{}v~|r{}v~T{}w~}W{}v~}O{}|k{}v~}P{}w~}]{}|l{}u~]{|v~|q{}v~|Yv~}U{|v~}o{}v~}Q{|v~}T{}v~M{}v~C{|" + "}t~}6{|t~}|D{}w~}^{}x~|s{|m~y}q~|k{|v~fv~|e{}w~}n{|u~}`{}u~|l{|}y~}g{}w~}n{|}t~}`{}w~}L{}w~}P{}u~}jv~|h{}w~}hv~" + "|Y{}w~}M{}w~}W{}w~}nt~^{}w~}V{}w~|bv~|h{}w~|mq~e{}u~|n{}u~|a{}w~}P{}u~|n{}u~|c{}w~}k{|v~|d{|y~}k{|u~|Y{|v~|X{|u" + "~n{|u~Z{}r~}]{}s~}n{|r~e{}v~lv~}X{|v~|Z{|u~E{|w~}E{}w~M{|w~| u{|v~p{|t~|_s~|s{|u~]u~|P{}v~}s{|s~|`u~|k{|Ww~}T{" + "}v~}s{|s~|`w~}m{|w~}Wv~Lv~Tw~}r{}v~}Tv~_w~}mv~mv~hw~}m{|w~}`v~}p{}v~|_t~|q{|v~|`v~}q{|t~|Zw~}Q{|kv~|Y{}w~}S{}v~" + "pt~|Z{}w~y}w~}[{}s~|rs~}_v~}s{}w~}V{}s~}W{}v~|Ew~}Lw~|M{|w~| r{|v~|s{}s~}^u~}ov~|_w~|s{}w~|[v~}pu~]v~|Nw~}m{|w" + "~}Y{|s~}Xv~m{}w~|a{|u~p{|u~|fv~}t{}x~}x~}t{}v~ev~}w{}w~w{|v~}`{|w~}q{|w~}_{}v~|w{}w~v{}v~`t~|q{|v~|`v~}p{}v~U{}" + "w~|Uv~|r{|v~b{|v~fv~|bu~|M{}w~}O{|u~}t{|u~}\\{|k~}|`{}w~}hv~|Z{|v~X{}u~|n{}u~|`{|@{}w~@{|Xn~|X{|i{|Y{|n~} xv~}U" + "{|v~}vy~}S{|v~T{|v~|jv~}Y{|w~}Lw~|H{|v~|*{}x~}i{}x~}${}~}s{|y~ S{}x~}i{}x~} ;{|u~p{|u~|Ux~}w{|x~} Ey~|s{|~}T" + "{}x~}o{}j~}k{|w~k{}x~}a{}x~}o{}j~}h{}v~}W{|v~fv~|h{|v~fv~|h{|v~fv~|h{|v~fv~|h{|v~fv~|h{}w~}f{}w~}p{|v~l{|v~U{}u" + "~|l{|}y~}c{}w~}M{}w~}M{}w~}M{}w~}D{}w~}M{}w~}M{}w~}M{}w~}Z{|v~n{|}s~c{}w~|mq~e{}u~|n{}u~|d{}u~|n{}u~|d{}u~|n{}u" + "~|d{}u~|n{}u~|d{}u~|n{}u~|d{}v~|l{|u~|et~|n{}u~|c{|u~n{|u~b{|u~n{|u~b{|u~n{|u~b{|u~n{|u~X{|v~|V{}w~}Mw~}x{|p{}v" + "~c{|v~p{|t~|a{|v~p{|t~|a{|v~p{|t~|a{|v~p{|t~|a{|v~p{|t~|a{|v~p{|t~|k{|v~p{|q~j{|gu~|Pu~|k{|_u~|k{|_u~|k{|_u~|k{" + "|Vv~Lv~Lv~Lv~U{|v~}r{}v~}^w~}m{|w~}`v~}p{}v~|_v~}p{}v~|_v~}p{}v~|_v~}p{}v~|_v~}p{}v~|W{}u~Vu~|q{}v~|_{}v~pt~|`{" + "}v~pt~|`{}v~pt~|`{}v~pt~|Y{}s~}Xt~|q{|v~|Y{}s~} Lu~}p{}u~|au~}p{}u~|au~}p{}u~|au~}p{}u~|au~}p{}u~|W{}x~}N{}s~} " + "M{|v~|Ev~} py~|Jy~|M{}t~O{|u~}Xw~}kw~}_{|t~}w|}u~}]{}w~}ov~|Xr~|Y{}t~}y| tt~|r{}x~}$v~}\"w~t{|w~X{}v~}y|y{|" + "y~y|}t~|_{|x~}ow~}tw~|m{|t~|r{|}q~}&w~}J{}w~ t{}w~L{}v~ u{|v~|Pw~|Pu~|t{}v~|\\s|}w~}r|a{}v~}Nx~}|p{}t~O{}w~}]{}" + "y~}|q{}t~|\\{}v~|s{}u~Y{|v~|T{}u~|r{}u~|_{~}|r{|}u~|T{}v~M{}v~@{|}w~}6{|w~}|A{}w~}^{|w~r{|o~}{}s~}iv~}f{}w~}e{}" + "w~}q|y}s~|_{}t~|p{|}w~}g{}w~}r|y}q~}_{}w~}g|`{}w~}O{}t~}o{|}u~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}mt~_{}w~}h|i{}w~|bv~" + "|h{}w~|m{}r~dt~}|r{|t~|`{}w~}Ot~}q{|t~}b{}w~}jv~}d{|w~}|p{|}u~}X{|v~|W{}u~|q{}u~|Z{|r~|]{|s~|mr~f{|v~|l{|v~|Y{|" + "v~|[{|u~}b|^{|w~}E{|w~|N{|w~| tv~}r{}s~|_w~y}x~}|w{|}u~|]{|u~|p{|}|^t~y|x{|}w~}w~|`{|u~|n{|y~|Xw~}St~y|x{|}w~}" + "w~|`w~}m{|w~}Wv~Lv~Tw~}q{}v~}Uv~_w~}mv~mv~hw~}m{|w~}`{}v~}r{}v~}^s~}s{|v~}_{}v~}s{|s~|Zw~}Qy~}|o{}w~}X{|v~}|U{|" + "v~}s{|s~|Z{|q~Z{|s~qs~|`{}w~}qv~}Vs~|X{}v~|Dw~}Lw~|M{|w~| qu~|u{}w~|v~}_s~|s{|u~^{}w~t{}w~}Z{|v~}|t{|}v~|]{}v~" + "|ny|^w~}m{|w~}Xs~|Y{}w~}m{|w~}a{|t~|s{}t~}f{}v~}v{|w~{w~|v{}v~}e{}v~}x{}w~x{}u~_{|w~}q{|w~}^u~}|y{}w~x{}u~}`s~}" + "s{|v~}_{|v~}|t{|}v~|U{|v~}|W{|v~|t{|v~|bu~f|v~}c{}v~}h|_{}w~}Vs}t~}v{}t~s}`{|y}t~y}|]{}w~}hv~|Z{|v~Wt~}|r{|t~|#" + "{}w~ vp~| {|p~} wv~}T{}v~}wy~}v{}~Z{|v~S{}x~|hx~}X{|w~}Lw~|G{}w~}){|w~|m{|w~|\"{|}q{} R{|w~|m{|w~| XY| ${|u~}r{" + "|t~}Ux~}w{|x~} E{}qy|T{|w~c{}x~gw~|lw~}a{|w~c{}x~e{}v~}Vv~}f{}w~}hv~}f{}w~}hv~}f{}w~}hv~}f{}w~}hv~}f{}w~}hv~|f" + "{|v~pv~}l{|v~}h|h{}t~|p{|}w~}c{}w~}g|a{}w~}g|a{}w~}g|a{}w~}g|X{}w~}M{}w~}M{}w~}M{}w~}Z{|v~r|x}q~b{}w~|m{}r~dt~}" + "|r{|t~|bt~}|r{|t~|bt~}|r{|t~|bt~}|r{|t~|bt~}|r{|t~|d{|v~|j{|v~}f{}s~}|r{|t~|a{}u~|q{}u~|a{}u~|q{}u~|a{}u~|q{}u~" + "|a{}u~|q{}u~|X{|v~|V{}w~}Mw~}xy~}y|wy|u~|bv~}r{}s~|`v~}r{}s~|`v~}r{}s~|`v~}r{}s~|`v~}r{}s~|`v~}r{}s~|jv~}r{}w~}" + "|u~|o{|}y~g{|u~|p{|}|_{|u~|n{|y~|`{|u~|n{|y~|`{|u~|n{|y~|`{|u~|n{|y~|Wv~Lv~Lv~Lv~T{}u~}|x{|}u~}]w~}m{|w~}`{}v~}" + "r{}v~}^{}v~}r{}v~}^{}v~}r{}v~}^{}v~}r{}v~}^{}v~}r{}v~}V{}u~V{|v~}r{}v~}^{|v~}s{|s~|`{|v~}s{|s~|`{|v~}s{|s~|`{|v" + "~}s{|s~|Xs~|Xs~}s{|v~}Ws~| K{}u~}x|{x|}t~^{}u~}x|{x|}t~^{}u~}x|{x|}t~^{}u~}x|{x|}t~^{}u~}x|{x|}t~U{}x~}N{|s~| M" + "{|w~|D{}w~| q{|y~}K{|y~}L{}v~|N{}v~Ww~}kw~}^{|j~}\\v~|o{}w~}X{}s~W{|^~} -s~}v|}v~}$v~}#{|w~t{|x~}X{}e~|^w~|o" + "{|w~|v{}w~k{|s~}v|}t~y}v~}'{}w~Jw~} t{}w~L{}v~ u{|v~|Q{|w~O{|u~}w|}u~}\\{|e~|ab~`u~w}|x}r~|O{}w~}]{}v~w}|x}s~}Z" + "t~}w|}t~X{}w~}S{|t~y}v|}t~}^v~y}y|y}s~|S{}v~M{}v~={|}~}6{|y~}|?{}w~}]w~}q{}r~|y{}u~}h{|v~|f{|v~e{}c~|]{}s~y}v|y" + "}t~}g{}b~|^{}c~}`{}w~}N{}r~y}v|y}r~|h{}w~}hv~|Y{}w~}M{}w~}W{}w~}lt~`{}d~}i{}w~|bv~|h{}w~|lr~cr~}v|}s~}_{}w~}Ns~" + "y}v|}s~}a{}w~}j{|v~|e{|s~}u|}r~W{|v~|Vs~}v|y}t~|Xs~}\\{|s~|m{}t~}fv~}j{}v~Y{|v~|[{}\\~}^{|w~}Dw~}N{|w~| t{}u~y" + "|x{|}w~y}w~|_w~}{k~|[{|t~}|wy|}x~|^{|k~y|w~|_{|t~}|vy|y}w~|Xw~}S{|k~y|w~|`w~}m{|w~}Wv~Lv~Tw~}p{}v~}Vv~_w~}mv~mv" + "~hw~}m{|w~}_{}u~}|x{|}u~}]w~y}w~y|yy|}u~|^{}u~}|x{|}w~}w~|Zw~}Qv~}y|v{|}u~|Wm~[{}u~}w|}v~}w~|Y{}s~}Yt~}q{}t~|a{" + "}v~p{|v~|W{}t~W{}d~Uw~}Lw~|M{|w~| q{|u~}|y{|}v~{|s~br~}y|yy|}u~|^{|w~}v{}v~X{}u~}y|{y|}u~}\\{|t~}|vy|y}y~}^w~}" + "m{|w~}X{}t~Xv~|lv~|b{|s~}v|}q~x}hu~}|{y|w~}{}w~}|{|}u~c{}u~}|}w~|}t~|_{|w~}q{|v~}_{|s~}v~}s~}_w~y}w~y|yy|}u~|^{" + "}u~}y|{y|}u~}S{}r~}Z{}v~}|x{|}v~}b{|Z~c{}c~}_{}w~}Vk~v{}l~|^{|v~Y{}w~}hv~|Z{|v~Vr~}v|}s~|\"{}w~ ur~| y{|r~} vv~" + "}St~}y|y~}{y|}x~`{}b~}a{}~|f{~}W{|w~}Lw~|G{|w~}({|v~}|s{|}v~| E{|v~}|s{|}v~| X{|Z~} ${|s~}y|{y|}q~}|}Xx~}w{|x~" + "} l{}x~|c{}x~h{}x~}m{|w~|`{}x~|c{}x~f{|v~}V{|v~|f{|v~i{|v~|f{|v~i{|v~|f{|v~i{|v~|f{|v~i{|v~|f{|v~i{|v~dv~|r{|" + "v~k{|b~g{}s~y}v|y}t~}c{}c~}a{}c~}a{}c~}a{}c~}X{}w~}M{}w~}M{}w~}M{}w~}Z{|b~}a{}w~|lr~cr~}v|}s~}`r~}v|}s~}`r~}v|}" + "s~}`r~}v|}s~}`r~}v|}s~}b{|x~|h{|x~}f{}o~}v|}s~}_s~}v|y}t~|_s~}v|y}t~|_s~}v|y}t~|_s~}v|y}t~|W{|v~|V{}w~}Mw~}xk~}" + "a{}u~y|x{|}w~y}w~|`{}u~y|x{|}w~y}w~|`{}u~y|x{|}w~y}w~|`{}u~y|x{|}w~y}w~|`{}u~y|x{|}w~y}w~|`{}u~y|x{|}w~y}w~|j{}" + "u~y|x{|}u~y{|t~}|vy|}v~f{|t~}|wy|}x~|^{|t~}|vy|y}w~|_{|t~}|vy|y}w~|_{|t~}|vy|y}w~|_{|t~}|vy|y}w~|Wv~Lv~Lv~Lv~S{" + "}j~}\\w~}m{|w~}_{}u~}|x{|}u~}\\{}u~}|x{|}u~}\\{}u~}|x{|}u~}\\{}u~}|x{|}u~}\\{}u~}|x{|}u~}U{}u~V{}t~}|x{|}u~}\\{" + "}u~}w|}v~}w~|_{}u~}w|}v~}w~|_{}u~}w|}v~}w~|_{}u~}w|}v~}w~|X{}t~Ww~y}w~y|yy|}u~|W{}t~ I{}h~}\\{}h~}\\{}h~}\\{}h~" + "}\\{}h~}T{}x~}Ms~ K{|y~}C{|w~ p{}x~K{}x~Kw~|L{}x~|Ww~}kw~}]{|l~}\\{|v~n{|w~}X{|s~U{}`~} -{|h~|$v~}#{}x~}t{}x" + "~}X{|}g~|^{}x~}m{}w~}y|}v~|j{|g~}y{}v~}({|w~|L{|w~| t{}w~L{}v~ u{|v~|Q{}x~}N{}k~}[{|e~|ab~`e~|N{}w~}]{}g~}Y{|i~" + "|Xv~|R{|g~}]i~|R{}v~M{}v~;y|5{|<{}w~}]{|w~|p{|v}|w{|x}|e{}v~dv~}f{}e~}|[{}d~|g{}d~}\\{}c~}`{}w~}M{}c~}|g{}w~}hv" + "~|Y{}w~}M{}w~}W{}w~}kt~a{}d~}i{}w~|bv~|h{}w~|l{|s~b{}f~|^{}w~}M{}f~|`{}w~}iv~}e{|c~V{|v~|Uf~}W{}t~|[s~l{}t~|g{}" + "v~hv~}Z{|v~|[{}\\~}^{|w~}D{}w~N{|w~| sj~{}w~|_w~}{|m~|Y{}i~|]{|m~|{|w~|^{|f~|Xw~}R{|m~|{}w~|`w~}m{|w~}Wv~Lv~Tw" + "~}o{}v~}Wv~_w~}mv~mv~hw~}m{|w~}^h~\\w~}{k~|\\k~y|w~|Zw~}Qg~}V{|n~Zk~{}w~|Y{|s~|Y{}u~}q{|t~a{|v~|o{}v~W{|u~|W{}d" + "~Uw~}Lw~|M{|w~| p{|l~|ys~be~}\\{}v~x}u~V{}j~}Z{|h~}^w~}m{|w~}X{|u~|Y{|w~}k{}w~}b{|w~}m~|s~h{|m~xm~|b{}g~|^{|w~" + "}pr~a{|f~}^w~}{k~|\\{}j~}R{|r~}Y{}l~}a{}Z~}d{}c~}_{}w~}Vk~v{}l~|^{|v~Y{}w~}hv~|Z{|v~U{}f~|!{}w~ tt~| w{|t~} uv~" + "}R{}i~`{}b~}`{|?{|w~}Lw~|Fw~}&{}t~w}t~} A{}t~w}t~} V{|Z~} ${|w~}m~|s~Xx~}w{|x~} m{|x~}b{}x~hw~lk~k{|x~}b{}x~" + "fv~}U{}v~dv~}j{}v~dv~}j{}v~dv~}j{}v~dv~}j{}v~dv~}j{}w~}d{}w~}r{}w~}k{|b~f{}d~|c{}c~}a{}c~}a{}c~}a{}c~}X{}w~}M{}" + "w~}M{}w~}M{}w~}Z{|d~}|`{}w~|l{|s~b{}f~|^{}f~|^{}f~|^{}f~|^{}f~|`{|~|f{|~}f{|w~|}f~|]f~}]f~}]f~}]f~}V{|v~|V{}w~}" + "Mw~}xl~}_j~{}w~|_j~{}w~|_j~{}w~|_j~{}w~|_j~{}w~|_j~{}w~|ii~w{|f~e{}i~|]{|f~|^{|f~|^{|f~|^{|f~|Wv~Lv~Lv~Lv~R{}l~" + "}[w~}m{|w~}^h~Zh~Zh~Zh~Zh~){|f~Zk~{}w~|^k~{}w~|^k~{}w~|^k~{}w~|X{|u~|Ww~}{k~|V{|u~| H{|j~|Z{|j~|Z{|j~|Z{|j~|Z{|" + "j~|S{}x~}M{}u~} I{}Ax~} pw~|Lw~|L{|y~|Jy~|Vw~}kw~}[{}o~|[{}w~}mv~Wt~}T{|}b~} +{}l~}\"v~}#w~|tw~|U{|}l~}]{|w~" + "ko~|h{|j~}|w{}u~({}w~L{}w~ s{}w~Lv~| u{|v~|Qw~}M{|m~}Z{|e~|ab~`g~}|M{}w~}]{}h~|W{|k~W{}v~P{|i~|\\k~}P{}v~Mv~| " + "i{}w~}\\{}w~Jv~}d{}v~f{}g~}|X{|}h~}e{}g~}|Z{}c~}`{}w~}L{|}g~}|e{}w~}hv~|Y{}w~}M{}w~}W{}w~}jt~b{}d~}i{}w~|bv~|h{" + "}w~|ks~a{|i~}\\{}w~}L{|i~}^{}w~}i{|v~|e{}f~}U{|v~|T{}i~|Ut~Z{}u~}l{|t~g{|v~|h{|v~|[{|v~|[{}\\~}^{|w~}D{|w~N{|w~" + "| s{|l~|{}w~|_w~}x{}q~}|W{|j~|[{}p~|y{|w~|]{|g~|Xw~}P{}q~}|y{}w~|`w~}m{|w~}Wv~Lv~Tw~}n{|v~}Xv~_w~}mv~mv~hw~}m{" + "|w~}]{}l~}[w~}{|m~|Zm~|{|w~|Zw~}Qh~|T{|o~Z{|m~|{}w~|Xs~X{}u~|pu~}av~}m{}w~}Wu~V{}d~Uw~}Lw~|M{|w~| o{|n~|w{}u~b" + "f~}Z{}p~}T{}l~}X{|i~}^w~}m{|w~}Wu~Xv~|k{|v~b{|w~y|o~|{}t~g{|o~|x{|o~}`{}i~|]{|w~}p{}s~_{}j~}|]w~}{|m~|Z{}l~}P{|" + "s~}X{}n~}`X~d{}c~}_{}w~}Vk~v{}l~|^{|v~Y{}w~}hv~|Z{|v~T{|i~} {{}w~ sv~| u{|v~} tv~}Q{}j~`{}b~}#{|w~}Lw~|G{|w~}${" + "}m~} ={}m~} T{|Z~} ${|w~y|o~|{}t~Xx~}w{|x~} mw~|b{}x~i{}x~|lk~kw~|b{}x~g{|v~Tv~}d{}v~jv~}d{}v~jv~}d{}v~jv~}d" + "{}v~jv~}d{}v~k{|v~|d{|v~rv~|k{|b~e{|}h~}a{}c~}a{}c~}a{}c~}a{}c~}X{}w~}M{}w~}M{}w~}M{}w~}Z{|g~}|]{}w~|ks~a{|i~}[" + "{|i~}[{|i~}[{|i~}[{|i~}/{|w~|y{|i~}Z{}i~|[{}i~|[{}i~|[{}i~|U{|v~|V{}w~}Mw~}xm~|^{|l~|{}w~|_{|l~|{}w~|_{|l~|{}w~" + "|_{|l~|{}w~|_{|l~|{}w~|_{|l~|{}w~|i{|l~}u{|g~d{|j~|\\{|g~|]{|g~|]{|g~|]{|g~|Wv~Lv~Lv~Lv~Q{|}p~}|Zw~}m{|w~}]{}l~" + "}X{}l~}X{}l~}X{}l~}X{}l~}){|w~}l~}Y{|m~|{}w~|^{|m~|{}w~|^{|m~|{}w~|^{|m~|{}w~|Wu~Vw~}{|m~|Tu~ E{|}p~}|V{|}p~}|V" + "{|}p~}|V{|}p~}|V{|}p~}|Qw~}Lu~| i{}y~| q{|w~}M{|w~}K{|}I{|}Uw~}kw~}Y{|y}w~y}|Yv~|m{}w~|X{}u~|Q{|}e~} *{|}p~" + "}|!v~}#w~t{|w~Py|x}y~x}y|[w~|j{}r~|e{|n~}|t{}u~){|w~|N{|w~| s{}w~Lv~ t{|v~|R{|w~|L{|}p~|Y{|e~|ab~`y|}l~}|K{}w~}" + "]{|}k~|S{}o~|Vv~}N{|m~}Z{}n~}|O{}v~Mv~ h{}w~}[v~L{|v~|d{|v~|g{}k~y}y|T{|}m~}|c{}m~x}y|W{}c~}`{}w~}J{|}k~}|c{}w" + "~}hv~|Y{}w~}M{}w~}W{}w~}it~c{}d~}i{}w~|bv~|h{}w~|k{|t~_{|m~}|[{}w~}J{|l~|]{}w~}h{}w~}c{|}k~}|T{|v~R{|}m~|S{}v~}" + "Z{|u~|kt~gv~}f{}v~[{|v~|[{}\\~}^{|w~}Cw~|O{|w~| q{}p~}x{}w~|_v}vy}w~y}|S{}m~}Xy}w~y}|w{|w}|[{|l~}|Vw~}N{|}w~y}" + "|w{}w~|`v}lw}|Wv~Lv~Tv}m{|u}Yv}_w~}mv~mv~hw~}m{|w~}\\{|n~|Zw~}x{}q~}W{}q~}|y{|w~|Zw~}Q{|}l~}P{|y}s~X{}q~}x{}w~|" + "X{}u~}X{|u~o{}v~|b{}w~}kv~}X{}w~}V{}d~Uv~Lw~|M{|w~| n{|}q~}u{|}w~bv~{}o~}|X{|r~|R{|}p~}|U{}l~}|^w~}m{|w~}W{}w~" + "}Xw}|i{|w}b{|w~|{|q~|y{|t~f{|q~|v{|q~|^{|l~}[{|w~}os~]{|}o~}|[w~}x{}q~}W{|}p~}|M{|}v~}W{|p~|`{|X~|e{}c~}_{}w~}V" + "k~v{}l~|^{|v~Y{}w~}hv~|Z{|v~R{|m~}| y{}w~ rx~| s{|x~} sv~}P{|}n~}|`{}b~}#{|w~}Lw~|Ty|pv~|\"y|}u~}y| 9y|}u~}y| " + "R{|Z~} ${|w~|{|q~|y{|t~Xx~}w{|x~} y}| q{}x~}aw}j{|w~kk~l{}x~}aw}gv~}U{|v~|d{|v~|l{|v~|d{|v~|l{|v~|d{|v~|l{|v~|" + "d{|v~|l{|v~|d{|v~|l{|v}bv}|t{}w~}j{|b~c{|}m~}|_{}c~}a{}c~}a{}c~}a{}c~}X{}w~}M{}w~}M{}w~}M{}w~}Z{|m~x}y|Z{}w~|k{" + "|t~_{|m~}|X{|m~}|X{|m~}|X{|m~}|X{|m~}|.w~}v{|}n~}|X{|}m~|X{|}m~|X{|}m~|X{|}m~|S{|v~|V{}w~}Mv|wy|}u~y}|Z{}p~}x{}" + "w~|]{}p~}x{}w~|]{}p~}x{}w~|]{}p~}x{}w~|]{}p~}x{}w~|]{}p~}x{}w~|g{}o~|r{|l~}|a{}m~}Y{|l~}|Y{|l~}|Y{|l~}|Y{|l~}|U" + "v~Lv~Lv~Lv~O{|y}v~y}|Xw~}m{|w~}\\{|n~|V{|n~|V{|n~|V{|n~|V{|n~|(w~|{|n~|V{}q~}x{}w~|\\{}q~}x{}w~|\\{}q~}x{}w~|\\" + "{}q~}x{}w~|W{}w~}Vw~}x{}q~}R{}w~} B{|t}|P{|t}|P{|t}|P{|t}|P{|t}|Nw~} 3{|~} ;f| '{|y}w~}y| 8{|y~|X{|x~}" + "h{|}w~}|ay|y}w~y}| rw~}N{}w~ ?{|w~| D{}w~I{|y}w~y}|%b|\\{|x}u~y}|!y|y}u~y}y|O{|y}w~y}| {{y|}u~y}|Vy|y}v~}y| u{|" + "w~| B{|v~| 1{|y}u~y}| o{|x}u~y}y| Fv~| 7y|y}v~y}| {{y|y}q~|#y|y}u~y}y| {{|y}v~y}y| a{|w~}C{}x~}O{|w~| oy}" + "v~}|vv|!{|}t~y}|!{|y}t~y}|Sv|Av~\"v|Lv~ Rv|mv|mv|hv|lv|Z{|y}u~}|Xw~}v{|}w~y}|T{|}w~y}|w{|w~|Zv|Ny|y}u~y}| {{|y}" + "w~}|uw|W{|u}|Wv}|o{|v}av|ju|Xv~| sv~Lw~|M{}w~| ly|}v~}|Uv~yy|}v~y}|S{|y}~y}|N{|y}v~y}|Qy|y}v~x}|[v|m{|w~}W{|w~" + "|#{|w~|x{|}w~}|v{|}y~y}c{|y}x~y}ry}x~y}|Z{|y}s~}y|G{}w~}|Zy|v~}|Ww~}v{|}w~y}|T{|y}v~y}| x{|y}w~}| Ry|y}v~y}|" + " Zy| rv~}M{|y}u~}|]`| Iw~|T{|y~}|u{|u~ 5{|w~|x{|}w~}|v{|}x~}Wx~}w{|x~} {}y~} r{|y}|Kw~|L{|y}|Hv~| E" + "{|y}u~y}| qy|y}v~y}|Sy|y}v~y}|Sy|y}v~y}|Sy|y}v~y}|Sy|y}v~y}|+{|y~}r{|y}v~y}|R{|y}v~y}y|S{|y}v~y}y|S{|y}v~y" + "}y|S{|y}v~y}y| oy}v~}|vv|Zy}v~}|vv|Zy}v~}|vv|Zy}v~}|vv|Zy}v~}|vv|Zy}v~}|vv|d{|}v~y}|n{|y}u~y}y|\\{|}t~y}|U{|y}" + "t~y}|T{|y}t~y}|T{|y}t~y}|T{|y}t~y}|Rv|Lv|Lv|Lv|!v|lv|Z{|y}u~}|R{|y}u~}|R{|y}u~}|R{|y}u~}|R{|y}u~}|'{}x~|w{|y}u~" + "}|S{|y}w~}|uw|Z{|y}w~}|uw|Z{|y}w~}|uw|Z{|y}w~}|uw|Vv~|Vw~}v{|}w~y}|Qv~| Mw~| K{|y~| e{|w~Nw~" + "| ?{}w~ Cw~} .{}w~ @{|v~|d{}| Kv~| !u~| J{|w~}C{|w~O{|w~| 9w~} Iv~ bw~}9{|w~| X{|v~ rv" + "~Lw~|M{}w~| w~| D{|w~| .w~| ?{|v~}g{|x~| M{|v~ {|u~| K{|w~}Bw~|P{|w~| :{}w~} Iw~} bw~}9{" + "|w~| X{}w~| r{}w~|Mw~|Mv~ ;v~ S{|w~}W{|w~|#{|w~| j{}w~ s{}w~Uw~} )v~}Iy~} gw~|T{|l~| 4{|w~" + "|Ax~}w{|x~} {{}y~} /v~| ?x~| f{|x~ M{} %{}w~|Uw~}D{}w~| Lw~| K" + "{|y~| d{|w~Pw~| ?{|w~ C{}w~ .{|w~ ={|u~}|l{|u~| N{}v~ {{|u~| L{|q~}H{}x~}V{}q~| :v~| Iw~}" + " bw~}9{|w~| Xv~ q{}w~}Mw~|N{|v~ ;v~ S{|w~}W{|w~|#{|w~| j{}w~ s{}w~Uw~} )v~}Iy~} gw~|T{|}o~}| " + " 3{|w~|Ax~}w{|x~} {{|x~| 0v~}m{} N{|x~ e{}y~} Rv~Tw~}Dv~ S{}x~x{|w~| " + " K{|y~| c{}x~}R{}x~} >{|x~| Cw~} .{|x~| ;{}t~}|sy|}t~| N{|v~} y{|u~| M{|q~}H{|w~V" + "{}q~| ;{}v~ I{|w~} bw~}9{|w~| Y{}w~} q{|v~}|Ow~|P{|}v~} ;v~ S{|w~}W{|w~|#{|w~| j{}w~ s{}w~Uw~} " + " )v~}Iy~} gw~|Q{|y}v~y}| 1{|w~|Ax~}w{|x~} yx~| 0{}v~|p{|~} N{|x~| f{|x~ " + " S{}w~}Tw~}E{}w~} S{}x~|y{|w~ J{|y~| bw~|Sw~| >{}y~} K{}y~} 9{|p~x}q~}| N{|u~" + "| x{|u~ M{|q~} y{}q~| K{|}|p{|u~| I{}w~| bw~}9{|w~| Z{|v~ o{}q~}Tw~|U{|p~ :v~ S{|w~}W{|w~|#{|" + "w~| j{}w~ s{}w~Uw~} )v~}Iy~} gw~| W{|w~|Aw|vx| y{|x~} 0{|u~|s{}x~} N{|x~| " + " f{|x~| U{|v~Sw~}F{|v~ R{|x~}y{}w~ J{|y~| b{|x}|T{|x}| w{}g~}| Q" + "x|y}u~} v{|u~ N{|p} yp}| K{|x~}y|wy|}u~} J{|}v~ aw~}9{|w~| \\{|}v~} nq~}Tw~|U{|q~| :v~ S{|w~}" + "W{|w~|#{|w~| j{}w~ s{}w~Uw~} )v~}Iy~} gw~| W{|w~| :{|}w|}w~| /t~y}x|y}v~} U{|}|x{|w~| " + " f{}x~| W{|}v~}Sw~}H{|}v~} Qq~| J{|y} *{|}l~}| O{}q" + "~ tt| `{|i~} Lr~| aw~}9{|w~| `{}q~ l{}s~}Tw~|U{|s~}| 9v~ S{|w~}W{|w~|#{|w~| j{}w~ s{}w~Uw~" + "} )v~}Iy~} gw~| W{|w~| :{|q~ .{|i~} U{|q~ ly}w|}w~| [{}q~Rw~}" + "L{}q~ P{}r~ M{|y}u~y}y| L{}r~| R{|j~} Ks~} `w~}9{|w~| " + " `{}r~| jy|v}|Tw~|U{|u}| 6v~ S{|w~}W{|w~|#{|w~| j{}w~ s{}w~Uw~} )v~}Iy}| gw~| W{|w~| :{|r~| " + " -{|k~}| U{|r~} l{}r~} Z{}r~|Rw~}L{}r~| O{}t~ " + " k{}t~} -{|`}| `{|}m~}| Jt~} _w~}9{|w~| `{}s~| :w~| cv~ S{|w~}W{|w~|#{|w~| j{}w~ s{}" + "w~Uw~} )v~} d{|w~| 9y}w~y} ){}o~}| S{|}u~}| k{}r~ Y{}s~|Qw~" + "}L{}s~| M{}w~} j{}w~}| +{}`~} ]{|x}v~y}| Gw~y} ]w~}9{|w~" + "| `{}v~}| 8w~| cv~ S{|w~}W{|w~|#{|w~| j{}w~ s{}w~Uw~} g{|w~| 8{|}v~y}| Ly| " + " g{|y}w~}| X{}v~}|Ow~}L{}v~}| Iy| " + "l{}`~} Ww~| " + " L{}`~} Ww}| " + " r{" }; - // 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 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 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 TEY ;XHX V ,V 2UEU 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 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 " - " W H_ AW ,W ,W ,W ,W " - " L] GV +] ;a #[ F^ " - " 8XGX +W BTEU " - " *R 9a :W MW 6\\ 6ZET ?XHX 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 W $a MY EW 5W >W Kb AW ,W ,W" - " ,W ,W !a GV +a Ch =f ^ Mf 2Z @" - "x Mx 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 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 V \"V 5VLV 9XGX IV \"V 4VMV 9XGX ,ZHY A_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 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 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 XHX ;UEU 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 \\ 2V 2\\ GX KS 1j #W2W LV -j ?XGX +ZEZ)VGY " - "5ZDZ)i 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 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 TEZ 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 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 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 W W 2W 4" - "W ,W HW 3W :V MW KX=W Cc ;X7P HX (" - "WR !X8X JV /X

W W " - " 2W 4W ,W HW 3W :V MW KWU.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 IYW W 2W 4W ,W HW 3W :V MW KW;W De =W " - " -X *W:W V$X 1V &W +W 5XITIX +V EV 4X[ JX -XNW8WNX0a9X#Y" - "3Y(X9Y JY3Y(X9Y NX LX 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 ,ZW 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 8XU1V 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'YVKX DX -X BX IX8X NX7W KP 1P =X 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 JWU2V 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 ^ AW4W ?Z >W6W KY \"Y 0X 2VFV &VCW#[LSKZ K" - "V?V@V\"W 0V 'W )W 1XNTNX &V FW 6Y:Y 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 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 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 Z 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 W\"W.XJX FX6X X -X.Y)X -X -X -X/X'X -X" - " -XCZ DX -XLV:VLX0XLX^4WG_ 9` @WG^ 9^GW MWG\\ ;f Gm ^BV\"W:W 3X ?^ 0e AWG_ KV.X ?X 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*XLXa'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 f CWIb =bIW MWI^ =j Im U4V LU4V\"`:V GX /WHW ;WHW ;WHW ;WHW ;WHW ;WHW :XEX MY -X -X -X -X -X " - "-X -X -X ,X-Y+XKWf ;f ;f ;f ;f +Z >eJU NW6W MW6W MW6W MW6W\"W" - "2W MWIb IW2W NWAVAW(W,W(WJRU5V KU5V GXTKW)W4TKW" - ")W+W\"V 9X3X 2X 5T :k ?i\"~`$m Jn IW4W A^ ?X:X MW NY .X 2VFV 7~X2XFS" - " 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 Y =W6W JW-W&YJb }!WCWCW Hk Dx&{ W4W CWFW P JSCVAVDS :WEV " - "$V W6W NiGU KU 6V;V BP>P /U5V KU5V EW=V FX 0XHX =XHX =XHX =XHX =XHX =XHX W:X MW NX -X 2VFV 7~X2WES WKX0XJX>X(Y)X,X7X!Y)X,X7X!Y LX VIV>X1YKY BXFX +Z IW .W " - " W 2Y1Y 2o EWMj Dn DjMW Hn Nl 7jMW MWLi IW ,W ,WW6W NiGU KU 6V;V BQ" - "?Q 0U6V JU6V BU>V EX 0WFW =WFW =WFW =WFW =WFW =WFW 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 VJX0XIW>X(X" - "'X-X7X!X'X-X7X!Y LX 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 \\>W0X=X LW5X u 6W :V " - " MW EkJV Wj 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` ?XX -XJW@WJX0XIX?X(X'X-X7X!X'X-X8Y Y MX " - "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 " - "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` >WXJX +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^ 5YY .W AXJa IZW2W EWDW 9Y =X /X'X/YAXAY,X0X)X?X?X/X'X%X JV KX 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 WX !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[ 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 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 aEa)X@XNW NWA[ ,W DW?[ LX +[=X KW:X =] ?W6W MW'W-XGWGX MW=W JWCWC" - "W MXZ 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 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 _ @W6W MW'W.YGWFX NW=W JWCWCW NX:X NYW2W 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 A` J~\\(y%W8W MW4W CXMW >W>W MV $x DX $VCV\"XFS 9X" - "IXEV H_ 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 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'SW6W 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 NVV&V 4W:X %~X2TNVW \"W ;WFTFW -V JV 3X4X >X #Y ?f AWBX Dp IXNm 4X ` @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 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 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 W ,X8X8W=X8X X6X MY7X\"X7Y MX 0W )W ,W6W MXXMW 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 1Vh =W6W JeGU IX 4g :g :" - "YFX DgEV:XhCV: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!XX7X 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 " - "LWS >}%~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 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 LWX >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 2WY BSFV'S9bMV ;XFY D~X .S@h>S @~W2i >g 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 X Lu MX *X*X,w Lq IX6f+~R'X -X -c " - "8X -XFVFVFX0XDXDX)X%X.u MX%X.r ?l :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 LWW 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 e :W6W GbGU IX 4g 8c 5XFX FgFV" - ":YX 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 9UAWX 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 >| IWX 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 W GX5W MW>W " - "LVLuKU/VLuKU/V?\\?U/V?YX 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;WW )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[" - " =iW" - "2W IWX X *X -X -X -X -X -X -X -X ,X*X-XCXEX)X%X1X%X1X%X1X" - "%X1X%X H_ LX@Wi >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:WAUXIW CW6W!W!W3WCWCW!W=W JWCWCW\"W2W%W3X ?W >W" - "2W JW;X ~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:WAUX 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 V&V 1XAW 9" - "X @WDT ?~S+~Z)}!y'W4W W4W FWFW >WDW J~^ *r ?V &VBV Eh *VEXIXX +Y $Z NWXHX DW6W!WW2W 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 JWV&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 X -XDVJVDX0XAXGX)X%X.i AX%X.X>Z ,\\ ?X XGW DW6W!WW2W 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 DXWEW :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 KXWDS >~T-~\\(y Mw&W4W W4W GXFX ?XFX JV " - " #r >V 'WCV 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 XFX EW6W!WW2W KW8W @Y ] 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-XXFX EW6W!WV-V%V-VGYIZHV-VGY7V-V%V%V " - "/WDX ;X ~T-~\\'v Is$W4W W4W GWDX ?XGW HV %r =V 6~X JSJ[ 0VEV" - "AV?WX ?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 WDW EW6W!WV>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 XDX FW" - "6W!WV?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 Q.X $T>Z?T0W8W HW5W\"WWCX FW6W!WXFX >V ,SBVBWCS &V \"W6W :UGU *m 8XFX .VWIX(X'X/X'X/X'X/X'X/X'X KZMZ XHW6X-X" - "/X'X/X'X/X'X/X GX 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 XBW FW6W!WXJX(Y)X.Y)X.Y)X.Y)X.Y)X KZKZ!YJW6X,X/X'X/X'X/X'X/X GX |\"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 XBX GW6W!WW 9X =\\KW >SEWWJX 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 W WJW DW" - " MX .VCV :SDW 6VBV?V@W6b )W #W !V !V +X8X X4X /X Ad L~X Ld AX 8VAV:WDU=|\"X3X$Y -X-Y*X -X .Y+X+X/X'X -X -XA[ GX -XA\\AX0XWKVDVKW\"XLX 9WJW =Z #X" - " :V MX AUEVKVDU/X:Y IW5W#WX@W GW6W!W=Y=W2WDWDW W=W JWCWCW\"X4W#W4" - "W >W X4X 0X =d ~X" - " d LUAWX2X#X3X#X -X.Y)X -X -X+X+X/X'X -X -X@[ HX -X@Z@X0XW " - ",W7W7W=W6W W4W MX5W\"W5X MW BX FW ,W7X FWHW >WLVBVLW#YKX :WJW =Y !W :V MW @VHXJWHV-W:" - "Y IW5W#WY>W1WDWDW W=W JWCWCW\"X4W#W4W >W 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 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 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 W >WLW BX NY 1VBV :SDW 8V@V?V?W4] &V $W " - " V \"V *Y:Y 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 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 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 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 ?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[JXW 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 )VWNX @Y !Z6Q VBV K" - "P>SEW 9V>WAW>X3Z &W %W V #V 'XU?" - "ZH^MZ\\ 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 ` >Y !Y8S MX +VBV KQ?SFX 9V=VAV=Y6] &V &W" - " NV BX 1X 1V 'Y>Y :X X:W JY;Z NXB]BX.XGWGX MW=W HXFWFX [:X NX:X ;W :WX HXX 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 MYX 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" - "\\=Y B^ 7r Gr Gr Gr Gr KV (_ BX )Y S 8RBSCR <] 2\\ GW4W KZBZ HX;W >_ <[ " - " $[=U MX ,VBV JUCSHY :V;WCW<[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(UU 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 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 _ FX0X ?X =\\?\\ >V 5b 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\\>\\'XP @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 \\ 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 X :V HW 3X=X )X\\ " - " /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 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\\ 5ZZ @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 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 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 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 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 Y BS .V,W#Z ;V -V " - " 7W ;W EX ;\\ 6] +Z 5\\ 5Z 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 W NW 0W =W " - " 3S GV /h AW HUGU ?T " - " %T NT " - " )X 9W X KV " - " 6S 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 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 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 104x128 binary font (huge sans). + static const char *const data_font_huge[] = { + " " + " " + " " + " " + " " + " " + " " + " " + " FY AY " + "'Z ;W @Y @Y 'Z Y @Y (Z :Y ?Y (Z 0Y ?Y (Z >X " + " " + " " + " " + " " + " )X AX '\\ )XAV 7YDY -] BY BY '[ +YEY 2X AY (\\ -YDY 'XAU 3Y AY (\\ )XAV 8YD" + "Y LY AY (\\ ,YEY #Y " + " " + " " + " " + " (X CX '^ +[CU 6ZEY .` C" + "X CY '] -ZEZ 2X CY (^ .ZEZ )[CU 2Y CY (] *[CU 7ZEZ LY CY (] -ZEZ %Y " + " " + " " + " " + " " + " 'Y EY '^ ,^FV 6ZEY /b CX DX '_ .ZEZ 2Y DX '_ /ZEZ +_FV 1X CX (_ ,^FV 7ZEZ " + " KX CX (_ .ZEZ &Y " + " " + " " + " " + " %Y GY '` .aHV 6ZEY 1e DY FX" + " 'a /ZEZ 1Y FX '` /ZEZ +aHV 0X EX '` .aHV 7ZEZ JX EX (a /ZEZ &X " + " " + " " + " " + " " + " #X GX 'XNX 0dKW 6ZEY 1f DY HX &WMX 0ZEZ 0X GX 'XMW 0ZEZ ,dLX /X GX 'WMX 0dLX 7ZEZ" + " IX GX 'WMX 0ZEZ 'X :T " + " " + " " + " " + " ;X IX 'XLX 1o 5ZEY 2ZLY " + " CX IX &WKW 0ZEZ /X HX (XLX 1ZEZ ,o .Y HX (WKX 1o 6ZEZ IY IY (WKW 0ZEZ (X X MX &WH" + "W 3VHa 4ZEY 3WDW CX LX 'WGW 2ZEZ -X LX 'WHW 2ZEZ -VHa +X KX (XHW 3VHa 5ZEZ GX KX (WGW 2ZEZ )X " + " ?b " + " " + " " + " " + " ?W MW &WFW 4VF^ 3ZEY 4WBV BW MX 'WEW 3ZEZ ,W M" + "X 'WFW 3ZEZ -VF^ )X MX 'WFW 4VF^ 4ZEZ FX MX 'WFW 3ZEZ *X ?d " + " " + " " + " " + " " + " ?W X 'WDW 5UC[ 2ZEY 4VAV AW X &WDW 4ZEZ +W NW 'WDW 4ZEZ -UC[ 'W MW 'WDW 5UC[ 3ZEZ " + "EW MW 'WDW 4ZEZ +X ?f " + " " + " " + " " + " @X \"X 'WBW 6UAW 0ZEY 4V@V B" + "X !W &WBV 4ZEZ +X !W 'WBW 5ZEZ .VAW $W W 'WBW 6UAW 1ZEZ DW W 'WBV 4ZEZ +W >f " + " " + " " + " " + " " + " ?X #W 'W@W U?V AX #W &W@V NX #W &V@W 9W \"W 'W@V .W " + "\"W 'W@V !W >XHX " + " 3Y " + " " + " " + " 6W $W &V>V U?V @W $W &W>V " + " NW $X 'V>V 8W $X (W>V /X $W 'W>V #W >XFX " + " 5Z " + " " + " ,Z " + " GZ " + " #U?V NY 7Z ,X CVCW MY " + " 7Z ,X $Z 7Z ,X >Z 6Y ,X 4Z 7Y +W 7Y @Z " + " " + " +Z " + " " + " HY \"U?V " + " MY 8Y ,Y CVBV LY 9Z ,Y #Z 9Z ,Z >Z 8Y ,Y 3Y 8Z ,Y 9Y " + " ?Z " + " *Y " + " " + " IY !U?V " + " LY :Y ,[ $R>U ,V@V MZ :Y +Z #Y 9Y +Z ?R" + ">U 8Y 9Y +Z %S?U HY :Z ,[ ;Y ?[ " + " " + " )Y " + " 8U " + " 9Y V@U JY Y @Y /X 0Y K` .X " + " ^ =ZEY @Y " + " NVAV

q \"Z 1ZCU -Z ,[JaI[ 6Z X F\\ :W#V 1" + "V?V 7W#W @[5[ 1WEW LV?V 1X7s,W-Y JX7t%\\ 6Z&Z&Z'Z%Z&Z&Z'Z%Z&Z&Z&Y4Y0Z 5\\(T NZ !Z Z " + "!Z >Z !Z !Z \"Z :Z.^!Y3e#\\.\\!\\.\\#].\\#]/]#\\.\\ N\\2[ ]/]![0[ L[0[ M[0[ N\\1[ 6Z 2Z #Y-Y NY5] HY5] IZ6] GY" + "5] HY5] HY5]-Y5a 3[ %[ \"[ \"[ \"[ >Y Y NX Y 3Z5[ GX0X GZ5Z F[6[ G[6[ GZ5Z F[5Z 5\\ 4^9Z FY6\\ FY6\\ FY6\\ " + "FY6] 9c 9]6Z :d )[CXBZ Z-Z NZ-[ [-Z Z-Z 7X $YNZ %Z D] $VCSDW G`FSG` ;d +c :X.Y F[9Z CZ8Y 6d =\\ " + " '\\3T -Z (W?X ;Sd c @Z EW<_Ks-Z&Z\"Z1] J^,V'Z/_ IZ MZ )]*Z'Z(Z :Z \"Z 4Z5] CZ 2Y JY(Y2d#]0\\ IZ (]1] NZ-" + "Z NS*\\ 6Y 6[1[ Z 4c 5[ @Y X Y HS3V FZZ%ZEYF[ EY9Y GZCXD[ J^7Z H[7[ 1Y 1Z:Z KZ&Z K[ !Z %];] Bx IZ(Z :Z 9]1] HS 8X 8R :n :R+R U 6W%W ?[6\\ 1WEW LU>U 0W6s-X.X HW6t&\\ 5Z&Z'Z" + "%Z&Z&Z'Z%Z&Z&Z&Z&Z6Z0Z 4],V NZ !Z Z !Z >Z !Z !Z \"Z :Z0`!Y2d\"\\0]!]0\\!]0\\!]1]!]1] \\0[ ]1] N[2\\ L\\2[ L\\" + "2[ L[1[ 6Z 2Z #Y.Y MZ7^ HY6^ HY6] GZ6] HZ7^ HZ7^-Y6c 3[ %[ \"[ \"[ \"[ ?Y Y NX Y 3[7[ FX0X G[7[ E[7[ FZ7[ F" + "[7[ E[7[ 5\\ 4]9[ FZ8] FZ8] FZ8] FZ7] 9c 9]7[ 9b '[DXD[ N[/Z LZ/[ M[0[ N[/Z 6X $d %Z C\\ ?S 2\\ETD" + "\\ 9b )a 9X.Y E[<[ BY7Z 7c ;\\ '\\5U -Z (W?W :U>TE[ CX8X?X3\\3b 1Y IY GX NZ GZ (" + "X )[;[ /Z 5[ %Q-\\ &Z BQ/] AZ9\\ 9Z 0[6\\ (\\ /Z \"[ ;a ` =Z EX[ 4b 6[ ?Y X Y " + "FZ=b E]7Z EX=Z <[9\\ D[ %Y1Y 8a 6Y3Y H\\8]#[@WNW@[%[FYG\\ EY9Y G[DXD[ J_9[ G[9[ /Y 1Z;Z LZ%Z L\\ !Z $]=\\ >t GZ" + "(Z :Z 8]3] FQ 7X 7P 8l 8P)P :m Z 0[EU -Z .[?P?[ 8Z X D[ 9W(W -T\\8] 1WEW " + " LSZ !Z !Z \"Z :Z2a Y2d\"^3] N]3^ ]3" + "] N]3] N]3] \\.[!^3] M\\4\\ J\\4\\ K\\4\\ L\\4\\ 5Z 2Z #Y.Y MZ8_ HZ8_ HZ8^ FZ8^ HZ8_ HZ8_-Z8e-Q)\\ &\\-Q G\\-Q " + "G\\-Q G\\-Q 5Y Y NX Y 2[9\\ FX0X F[9[ D\\9[ E[8[ E[9[ D\\9[ 4\\ 3[9[ EZ9^ FZ9^ FZ9^ F[9^ 9b 8^9[ 8b &[2[" + " L\\3\\ K[2[ K[2[ L\\3\\ 6X #c &Z B\\ ?S /UATAT 4a '_ 8X.Y E\\>\\ BY6Y 7c :] (\\7V " + "-Z )X@X :W@TF[ BW7X?X3]6e 1X IY GX NZ GZ (X ([=[ .Z 6[ $S1^ &Z BS3^ @\\<\\ 8Z 0]9] FR6] .Z \"[ 8^ " + " ^ ;Z DW;lMc+Z$Z#Z4_ G_2Y'Z5c GZ MZ '^/\\'Z(Z :Z \"Z 4Z3] EZ 2Y JY(Y1c!^6^ HZ '^6^ LZ,Z X1] 5Y 5]6\\ :c Ab2a" + "\"Z0[ 7Z ;\\ >X @X NY MZ:` F_:[ B\\3P D[;` E\\1S 7Y 0\\>a GY1Y 5Y NX 0X;\\ 0Y FY3Y2Y+Y1Y F[:[ E_;\\ " + "F[;_ ;Y *S1Y 6Z .[;_ :e ;`;` G[<[ 5a 6[ >Y X Y F[?YNY F_:[ DX?Z :[;\\ B[ &Y1Y 8a 7Z3Y H]:^#\\BXNWA[#[" + "GYH\\ DY9Y F\\FXF\\ I`;[ F\\;\\ /Z 2[=Z KZ$Z N\\ Z #^A] :n DZ(Z :Z 7]5] +X Mj (k NZ 0\\FUBP ;Z /[,[ " + "9Z X CZ 8X+W *R;R 4X+X =]:^ 1WEW LR;R /X5s.W.X GW5t(\\ 4Z$Z(Z%Z'Z$Z(Z$Y'Z$Z(Z$Z" + "8Z/Z 3_2Y NZ !Z Z !Z >Z !Z !Z \"Z :Z5c NY1c!^6^ L^6^ M^6^ M]5] M^6^ \\,[#a7^ K\\6] I\\6\\ J]6\\ J\\6] 5Z 2Z #" + "Y/Z LZ:` H[:` H[:_ FZ:` GZ:` GZ:`-[:YN\\0S(\\4Q C\\0S F\\0S F\\0S F\\0S 5Y Y NX Y 1[:[ EX0X F\\;\\ C\\;[ C[:" + "[ D\\;\\ C\\;\\ 4\\ 3[:\\ DZ;_ EZ;_ EZ;_ EZ;` 8a 8_;\\ 7a %\\6\\ J\\5\\ I\\6\\ I\\6\\ J\\5\\ 5X #c 'Z " + "@[ @T JT _ %] 7X.Y D^D^ BZ6Y 6b 9_ *];X -Z )X@X :ZCTH] CX7YAX1^:h 2Y JY GX NZ" + " GZ (X (\\?\\ .Z 7\\ $W7_ %Z BV8` ?\\>] 9[ /];] ET9] -Z \"[ 5[ [ 8Z DX;jLb*Z$Z#Z7a E`7\\'Z9f FZ MZ &`4^" + "'Z(Z :Z \"Z 4Z2] FZ 2Y JY(Y1c _:_ GZ &_9^ KZ,[![6^ 4Y 4]9] 8b @a2a#[/Z 7Z ;[ =X @X NY M[\\ @]7R" + " D\\=a E]4U 7Y /]Bc GY1Y 5Y NX 0X:\\ 1Y FY3Y2Y+Y1Y E\\>] E`=\\ E\\=` ;Y *U5[ 6[ /\\>a 9c :_:` GZ:Z 4` 6[ >Y " + "X Y E[AYMZ G`<[ CX@Z 9\\=\\ A\\3Q EY1Y 7` 7Y2Z I^<_\"[BWMXC\\#]IYI\\ CY9Y F]GXG] Ia=\\ E\\=\\ .[ 2[?Z J" + "Z$Z N[ NZ \"^C^ 7g @Z(Z :Z 7_9_ +X Lh &i MZ /]HUDR ;Z .Y*Y 8Z X BZ 8Y/X (Q:Q 2X/Y " + " <^<` 2WEW LQ:Q .W MV(X/X GX NW\"\\ 3Z$Z)Z#Z(Z$Z)Z#Z(Z$Z)Z#Z8Z/Z 2`7\\ NZ !Z Z !Z >Z !Z !Z \"Z :" + "Z9f MY0b _:_ J_:_ K_:_ L_9_ L_9^ N[*[$c:^ J^:^ H^:^ I^:] H]9] 4Z 2Z #YIP7[ L[] C\\=\\ A\\=\\ 3\\ 2\\=\\ C[=` E[=` E[=" + "` E[=a 8a 8`=\\ 6` #]:] H]9] G]:] G]:] H]9] 4W !a 'Z ?Z ?U KT N] $] 7X.Y Cv AZ6Z 7a 7a " + " -_?Z -Z )W?X :^GTK_ CX5XAX0_>k 3Y JX FX NZ GZ )Y ']C] ?} I~S IZ=b %Z BZ>a =]B^ 8Z ._?^ DX" + "@_ ,Z \"[ 3Y X 5Z CW:gJ`)Z\"Z$~T Cb=_'~W E~S FZ %b:a'Z(Z :Z \"Z 4Z1] G~Q)Y JY(Y0b N`>` FZ %a?` JZ+Z!^_ 8b @a2a$[.[ 8Z <~` AX ?X Y L\\@c Fb@] ?^` H`>` I`>` Ja?a Ja?` LY(Y$f?` H_>_ F_>_ G_>_ H_>" + "_ 3Z 2Z #YIS;[ K\\?c G\\?c G\\?b E\\@c F\\@c G\\?c,\\?[L^9Y'^} I~S I~ $Z B| ;^F_ 7Z -aEa Dv +Z \"[ 0V U 2Z CX9dI^'Z\"Z$~S AfGd'~U C~S FZ $gGg&Z(Z :Z \"Z 4Z0] H" + "~Q)Y JY(Y0b McGd EZ $dGc IZ+[\"cEd 3Y 3cGc 7a ?`1a$Z,[ 9Z =~a AX ?X Y L^DZNY FYNZF_ =`CY B^EZNY CaB] 7" + "Y .qMY GY1Y 5Y NX 0X8\\ 3Y FY3Y2Y+Y1Y D_F_ CYNYE_ B^EZNX ;Y *]A^ 4k >^G[NY 8a 9_9^ H[8[ 5^ 6~P 2Y X Y " + " D^H[La NfH` AYD[ 6^E_ ?`?X EY1Y 7_ 7Y0Y IcFk(]HZLZI^ `Nk BY9Z E~Q GYNZE^ B_E_ ,e ;]G] J~c!~T FZ 3oDo @Z :Z(Z :" + "Z 5dGd )X Jd \"e KZ -`MUKY H~U IU&U 6Z X AY 5Z7Z LZ7Z ;~d 3cFk 8WEW " + " BW LV)X0X FW LW$\\ 2Z\"Z+[#Z)Z\"Z*Z\"Z*Z\"Z*Z\"Z:Z.~T*fGd N~T J~T I~S I~S 7Z !Z !Z \"Z :~U JY/a MdGc FcGd GcGd" + " HdGd HdGc JW&W$kGc FbFb DbFb FcFb FcGc 3Z 2Z #YIWB] I^DZNY F]D[NY F]D[NX E^DZNY F^DZNY F^E[NY+]D]J`@]&`BY AaA]" + " DaA] DaA] DaA] 5Y Y NX Y /_F_ CX0X D_E_ ?_F_ ?_F_ @_E_ ?_F_ 7aF_ @^FZMX D^FZMX D_GZMX D_G[NY 7_ 7YNYE_ 4^" + " dLd CdMd BdLd CdLd DeMd 2X !` %X =Y ?U LV MZ !Y 5X.Y As AZ4Y 6` 5~] )x -Z " + "*X@X 9} BX3YFZ-{L] 4Y LY FX NZ GZ )X $t >} I~S I} #Z B{ :v 7[ ,{ Cu *Z \"[ -S S 0Z BW8aG[%[\"Z$~R" + " ?~S'~T B~S FZ #~V%Z(Z :Z \"Z 4Z/] I~Q)Y JY(Y/a L~ DZ #~ HZ*Z\"~R 2Y 2} 5` ?`0_$[+Z 9Z =~a AX ?X Y KsN" + "Y FYNr ;u AqMY B{ 7Y -oLY GY1Y 5Y NX 0X7\\ 4Y FY3Y2Y+Y1Y Cv BYNr ArMX ;Y *y 2j >qMY 8a 8^9^ I[6Z 5^ 6~P 2Y X " + " Y CpK` N} ?YF[ 5w =x EY1Y 6] 7Z0Z J~Y(nJm M{ AY9\\ F~ FYMq @w *d ;r J~d!~T FZ 3oDo @Z :Z(Z :Z 4~ 'X " + " Ib c JZ ,u H~U HS$S 5Z X AY 4\\>\\ I]>\\ :~d 3~Y 8WEW CW KV)W0X FX LW" + "$[ 2[\"Z+Z!Z*Z\"Z+Z!Z*Z!Z,Z!Z:Z.~T)~S N~T J~T I~S I~S 7Z !Z !Z \"Z :~T IY/a L~ D~ E~ F~ E~ HU$U$~X D| B| D} D} " + "2Z 2Z #YIr HrMY FsMY FsMX DsNY ErMY FsMY+uH|%v @| C| C| C| 5Y Y NX Y .v BX0X Cw =w >v >w =w 8{ ?qMX CqMX C" + "qMX CqMY 6] 6YNr 3^ My Ay @y @z Ay 1X _ $V X !" + "Y JqMY FYMp 9t ApLY Az 7Y ,mKY GY1Y 5Y NX 0X6\\ 5Y FY3Y2Y+Y1Y Bt AYMp ?pLX ;Y *x 1j =oLY 8a 8]8^ IZ4Z 6" + "] 5~P 2Y X Y CoI_ N} ?[K] 3u ;w EY1Y 6] 7Y.Y JvM_'mJm Ly @Y9b K| EYLp ?u (c :p I~e\"~T FZ 3oDo @Z :Z(Z" + " :Z 2{ &X H` Ma IZ +t H~U GQ\"Q 4Z X AY 2aLb FaKa 8~d 3YNlN_ 8WEW " + "DX KV*W0o-W KW%[ 1Z Z,Z!Z+Z Z,Z!Z+Z Z,Z!Z;Z-~T'~P M~T J~T I~S I~S 7Z !Z !Z \"Z :~R GY.` K| B| C{ B{ B{ FS\"S$YM" + "{ Bz @z B{ B{ 1Z 2Z #YIq GqLY EqLY EqLX CqMY ErMY EqLY*sF{$u ?{ B{ B{ B{ 5Y Y NX Y -t AX0X Bu ;u pLX CpLX CpLX BoLY 6] 6YMp 1] Lv >w =v =v >w 0X _ #T ;X ?W MV LW LV 4X.Y ?n >Y3Z 7_ 1~Z " + " 't +Z *W?X 8y @X1j)vG] 5X MY EX NZ GZ *X !p <} I~S Iz Z By 6r 5Z )w As (Z \"[ " + " 5Z AX HZ Z%~ 9|$~P >~S FZ ~P\"Z(Z :Z \"Z 4Z-] K~Q)Y JY(Y.` Jy AZ x EZ)Z#~P 0Y /x 3_ =_0_%Z([ ;Z =~a AX " + ">X !Y JpLY FYLn 7s @nKY @y 7Y +kJY GY1Y 5Y NX 0X5\\ 6Y FY3Y2Y+Y1Y Ar @YLn =nKX ;Y *w /i x ?x @y 0Z 2Z #YIp EoKY DoKY DoKX BoLY DpLY DoKY)qCy#t =y @y @y @y 5Y Y NX Y ,r @X0X As 9s :r :s 9s 7z <" + "nKX BnKX BnKX BnKY 6] 6YLn 0\\ Jt ;s :t ;t ;s .X N] !R 9V >W NX LU KU 3X.Y >l =Y2Y 7_ /~X " + " %p )Z *W?W 4u @X/i(tE] 6Y NX DX NZ GZ *X m :} I~S Iy NZ Bw 2o 5Z 'u @r 'Z \"Z " + " 4Z AY J[ Z%} 6x\"} <~S FZ N| Z(Z :Z \"Z 4Z,] L~Q)Y JY(Y.` Hv @Z Mu DZ)[$~ /Y .u 0^ =^/_&['Z ;Z =~a AX >X" + " !Y InKY FYKl 5r ?lJY >w 7Y )hIY GY1Y 5Y NX 0X4\\ 7Y FY3Y2Y+Y1Y @p ?YKl ;lJX ;Y *v -h ;kJY 7_ 7]7\\ J[2" + "[ 7\\ 5~P 2Y X Y AkE] Nz :i .p 7u EY1Y 5[ 7Y,Y KYMiL_%iGj Hu >Y8a Hv BYJl :p $a 7k H~f\"~T FZ 3oDo @Z " + ":Z(Z :Z /u #X F\\ I] GZ )r H~U *Z X AY /p >o 4~d 3YMiK^ 8WEW EX " + "JV+W/o/X JW&Z 0[ Z-Z NZ-[ [.Z NZ,Z NZ.Z NZ=Z,~T$x I~T J~T I~S I~S 7Z !Z !Z \"Z :| BY-_ Hv p %Z \"Z " + " 4Z @X JZ MZ&{ 3u z 9~S FZ Lx MZ(Z :Z \"Z 4Z+] M~Q)Y JY(Y-_ Fr >Z Lr BZ(Z!y -Y -s /] <^.]&[&[ m >YJj 8iIX ;Y *u *f :iIY 7_ 6\\7" + "\\ K[0Z 6Z 4~P 2Y X Y ?hC\\ NYMm 8f +m 3s EY1Y 5[ 8Z,Y KYLgJ^$gEh Fs =Y8a Fr @YIi 7m !` 6i G~g#~T FZ 3o" + "Do @Z :Z(Z :Z .s \"X EZ G[ FZ 'p H~U *Z X AY ,k :k 2~d 3YLgJ^ 8WEW " + " EW IV,X/o/W IW&Z 0Z MZ/[ NZ-Z MZ.Z N[.Z MZ.Z MZ>Z,~T\"t G~T J~T I~S I~S 7Z !Z !Z \"Z :y ?Y-_ Fr 8r 9r :s :r " + " AXEr :r 8r :s :s -Z 2Z #YIn AkIY BkIY BkIX @jIY BkIY BkIY'l=t Mq :t ;t ;t ;t 3Y Y NX Y *m =X0X >m 3m 5n 5m" + " 3m 6XLm 7iHX @iHX @jIX @jIY 5[ 5YJj -Z El 3k 2l 3l 4l *X N\\ 5U ?Y Y KR HQ 1X.Y 9b 9Y1Z 7" + "] )~S \"j &Z +X@X -h ;X+c!l?\\ 6X Y DX Z FZ +X Kh 8} I~S Fr JZ As ,i 3[ $n ;m " + "#Z \"Y 3Z ?X KZ MZ&x -p Mu 4~S FZ Js JZ(Z :Z \"Z 4Z*] N~Q)Y JY(Y-_ Dn gB[ NYLj 5d (j 0q EY1Y 5Z 7Y+Z LYKdG]\"dBd Bo ;Y7` Dn >YHg 4i L^ 4e " + "E~g#~T FZ 3oDo @Z :Z(Z :Z ,n NX DX EY EZ %m G~U *Z X BZ )e 4e /~d 3YKeH] 8" + "WEW FW HV,W.o0X IW'Z /Z MZ/Z LZ.Z MZ/[ MZ.Z MZ/[ MZ>Y+~T p E~T J~T I~S I~S 7Z !Z !Z \"Z :u ;Y,^ Dn 4" + "n 5n 6o 6n @XBm 5n 4n 6o 6o +Z 2Z #YIl =gGY AhGY AhGX ?hHY @hHY @gGY%i:o Hm 7p 6o 6p 7p 1Y Y NX Y (i ;X0X " + "fGX >fGX >fGY 4Y 4YHf +Z Bg /g .g -g /g (X M[ 5T ?Z !Z JP 'X.Y 5" + "[ 6Y0Y 7] &~P Ne $Z +W?X '] 6W)a Mh<\\ 7Y !X CX Y EZ +X Id 6} I~S Cm HZ =l 'e " + "1Z i 6h !Z #Z 3Z ?Y M[ M['s &k Jo .~S FZ Gm GZ(Z :Z \"Z 4Z)] ~Q)Y JY(Y,^ Bi 9Z Gl AZ'Z Jm (Y (i )\\ " + ";].]'[#Z =Z =~a AX =X \"Y DdFY FYFb *h 6cFY 8j 0Y \"YAY GY1Y 5Y NX 0X1\\ :Y FY3Y2Y+Y1Y ;f :YFb 1cFX ;Y" + " $k ` 7cFY 6] 5[5Z KZ-[ 8Y 3~P 2Y X Y ;b=X NYJe 0` $e +l BY1Y 4Y 7Y*Y LYIaE[ b@a >k 9Y6_ Ah ;YFc 0e " + "FZ 2a D~i$~T FZ 3oDo @Z :Z(Z :Z )i LX CV CW DZ #h D~U *Z X -R9Z #[ *[ *~d 3" + "YIaE\\ 8WEW GX HV-W-o0W HW'Z 0Z L[0Z LZ/[ LZ0Z LZ/[ LZ0Z LZ?Z+~T Lj B~T J~T I~S I~S 7Z !Z !Z \"Z :o " + "5Y,^ Ai /h 0i 0i 0i >W?i 1j 0j 1j 1i (Z 2Z #YGh 9cEY ?dEY ?dEX =dFY >dFY >cEY#d5j Ch 1j 1j 1j 1j -Y Y NX Y" + " &e 9X0X :e ,f -f -e ,f 4XFe 0cEX a NU CZ N` 9X -T<[ " + " LYG]BX 5WEW %U HW NX MX GZ (d +b (b )b )a )b 9V;a " + ")c *c *c *c =a 4_ &^ %^ $^ &_ &_ :_/c RM] !R Z 5\\ " + " 9X ;X $Y HY NY 0Y 'X NY BY X !Y " + ":Y 8Y 4Y *Y 1Y EX 3Y CZ IU 3X -p " + " IY 8WEW #V &Z MV " + " 0U 'P ;Y 2Y >Z 8X " + " MT *X &X 9X DX " + " 5X ?\\%W ?Z 4\\ :X ;X $Y " + " IZ NY 0Y 'X NY BZ !X !Y :Y 8Y 4Y *Y 1Y EX 3Y " + " CZ IU 3X -o HY 8WEW \"V " + " 'Z LU 0V " + " CZ 2Y >Y 7X " + " MT )X 'X 9X DX 5W <\\(X ?" + "Z 3\\ ;Y e GX 2f KZ LY 0Y 'X !Y >" + "\\ %X &] 9Y 8Y 4Y *Y 1Y EX 3Y CZ IU 3" + "X $^ @Y 8WEW !V '\\:V ;V " + " 1W GZ 0Y @Z " + " FWHX LT 'X +W 7W " + " V 5b?c A[ -\\ ?e !f " + " f /X 0g 9Y 8Y 4Y *Y " + " 1Y EX 3Y CZ IU 3X 5Y " + " NV &\\=X ;V " + "1W GY /Y AZ EWHX " + " LT &W ,X 7V V 3~T " + " A] ,\\ @e !f d " + " %e -Y Nd @c " + " (m @c " + " +u $b -Y 'X 0d 2^ /X 0_ 1Y 8Y 4Y *Y " + " 1Y EX 3Y CZ IT 2X 5Y " + "-c !q Hd >c " + " $d ,Y Nd ?b " + " %g =" + "b *t #a ,Y 'X 0d " + " ,X /X 0Y +Y 8Y 4Y *Y 1Y EX 3Y CZ '" + "X 5Y -c Nm Fc " + " =c $c +Y Nc " + " >a " + " M\\ 8a \"~Y 1" + "r !` +Y 'X 0c 1X 1Y 8Y 4Y *Y 1Y EX 3Y " + " CZ &W 5Y -b Lj " + " Db /dev/null 2>&1"); // Make command silent. + std::strcpy(ncommand + l," >/dev/null 2>&1"); // 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; + STARTUPINFOA si; std::memset(&pi,0,sizeof(PROCESS_INFORMATION)); std::memset(&si,0,sizeof(STARTUPINFO)); - GetStartupInfo(&si); + GetStartupInfoA(&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); + const BOOL res = CreateProcessA((LPCSTR)module_name,(LPSTR)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 { + char* lpMsgBuf; + + // Get the error message. + DWORD errorCode = GetLastError(); + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 0,errorCode,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),(LPSTR)&lpMsgBuf,0,0); + cimg::warn("cimg::system() : Command '%s' (module name '%s) failed with error %lu: %s", + module_name==0?"(null)":module_name, + command==0?"(null)":command, + errorCode,lpMsgBuf); + return -1; + } #else return std::system(command); #endif @@ -5000,6 +5937,9 @@ namespace cimg_library_suffixed { } } } + inline void invert_endianness(bool* const, const cimg_ulong) {} + inline void invert_endianness(unsigned char* const, const cimg_ulong) {} + inline void invert_endianness(char* const, const cimg_ulong) {} //! Reverse endianness of a single variable. /** @@ -5012,21 +5952,21 @@ namespace cimg_library_suffixed { return a; } - // Conversion functions to get more precision when trying to store unsigned ints values as floats. - inline unsigned int float2uint(const float f) { + // Conversion functions to get more precision when trying to store 'unsigned int' values as 'float'. + inline unsigned int float2uint(const float value) { int tmp = 0; - std::memcpy(&tmp,&f,sizeof(float)); - if (tmp>=0) return (unsigned int)f; + std::memcpy(&tmp,&value,sizeof(float)); + if (tmp>=0) return (unsigned int)value; 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. + std::memcpy(&u,&value,sizeof(float)); + return ((u)<<2)>>2; // set sign & exponent 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). + inline float uint2float(const unsigned int value) { + if (value<(1U<<19)) return (float)value; // Consider 'uint32' safely stored 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. + const unsigned int v = value | (3U<<(8*sizeof(unsigned int)-2)); // set sign & exponent bit to 1 // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler. std::memcpy(&f,&v,sizeof(float)); return f; @@ -5036,28 +5976,31 @@ namespace cimg_library_suffixed { /** \note The timer does not necessarily starts from \c 0. **/ - inline cimg_ulong time() { + inline cimg_uint64 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); + return (cimg_uint64)st_time.tv_sec*1000 + (cimg_uint64)st_time.tv_usec/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))); + ULARGE_INTEGER ul; + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + ul.LowPart = ft.dwLowDateTime; + ul.HighPart = ft.dwHighDateTime; + return (cimg_uint64)ul.QuadPart/10000; #else return 0; #endif } // Implement a tic/toc mechanism to display elapsed time of algorithms. - inline cimg_ulong tictoc(const bool is_tic); + inline cimg_uint64 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() { + inline cimg_uint64 tic() { return cimg::tictoc(true); } @@ -5065,14 +6008,14 @@ namespace cimg_library_suffixed { /** \return Time elapsed (in ms) since last call to tic(). **/ - inline cimg_ulong toc() { + inline cimg_uint64 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. + \note This function frees the CPU resources during the sleeping time. It can be used to temporize your program properly, without wasting CPU time. **/ inline void sleep(const unsigned int milliseconds) { @@ -5088,12 +6031,12 @@ namespace cimg_library_suffixed { #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; + inline unsigned int wait(const unsigned int milliseconds, cimg_uint64 *const p_timer) { + if (!*p_timer) *p_timer = cimg::time(); + const cimg_uint64 current_time = cimg::time(); + if (current_time<*p_timer || current_time>=*p_timer + milliseconds) { *p_timer = current_time; return 0; } + const unsigned int time_diff = (unsigned int)(*p_timer + milliseconds - current_time); + *p_timer = current_time + time_diff; cimg::sleep(time_diff); return time_diff; } @@ -5105,116 +6048,112 @@ namespace cimg_library_suffixed { \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) { + inline unsigned int wait(const unsigned int milliseconds) { cimg::mutex(3); - static cimg_ulong timer = 0; - if (!timer) timer = cimg::time(); + static cimg_uint64 timer = cimg::time(); cimg::mutex(3,0); - return _wait(milliseconds,timer); + return cimg::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 + // Custom random number generator (allow re-entrance). + inline cimg_uint64& rng() { // Used as a shared global number for rng + static cimg_uint64 rng = 0xB16B00B5U; + return rng; + } -#include + inline unsigned int _rand(cimg_uint64 *const p_rng) { + *p_rng = *p_rng*1103515245 + 12345U; + return (unsigned int)*p_rng; + } - // Use a custom RNG. - inline unsigned int _rand(const unsigned int seed=0, const bool set_seed=false) { - static cimg_ulong next = 0xB16B00B5; + inline unsigned int _rand() { cimg::mutex(4); - if (set_seed) next = (cimg_ulong)seed; - else next = next*1103515245 + 12345U; + const unsigned int res = cimg::_rand(&cimg::rng()); cimg::mutex(4,0); - return (unsigned int)(next&0xFFFFFFU); + return res; } - inline unsigned int srand() { - unsigned int t = (unsigned int)cimg::time(); -#if cimg_OS==1 - t+=(unsigned int)getpid(); -#elif cimg_OS==2 - t+=(unsigned int)_getpid(); -#endif - return cimg::_rand(t,true); - } - - inline unsigned int srand(const unsigned int seed) { - return _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 unsigned int srand() { - const unsigned int t = (unsigned int)cimg::time(); + inline void srand(cimg_uint64 *const p_rng) { #if cimg_OS==1 || defined(__BORLANDC__) - std::srand(t + (unsigned int)getpid()); + *p_rng = cimg::time() + (cimg_uint64)getpid(); #elif cimg_OS==2 - std::srand(t + (unsigned int)_getpid()); -#else - std::srand(t); + *p_rng = cimg::time() + (cimg_uint64)_getpid(); #endif - return t; } - inline unsigned int srand(const unsigned int seed) { - std::srand(seed); - return seed; + inline void srand() { + cimg::mutex(4); + cimg::srand(&cimg::rng()); + cimg::mutex(4,0); } - //! 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; + inline void srand(const cimg_uint64 seed) { + cimg::mutex(4); + cimg::rng() = seed; + cimg::mutex(4,0); + } + + inline double rand(const double val_min, const double val_max, cimg_uint64 *const p_rng) { + const double val = cimg::_rand(p_rng)/(double)~0U; 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); + inline double rand(const double val_min, const double val_max) { + cimg::mutex(4); + const double res = cimg::rand(val_min,val_max,&cimg::rng()); + cimg::mutex(4,0); + return res; } - //! Return a random variable following a gaussian distribution and a standard deviation of 1. - /** - **/ - inline double grand() { + inline double rand(const double val_max, cimg_uint64 *const p_rng) { + const double val = cimg::_rand(p_rng)/(double)~0U; + return val_max*val; + } + + inline double rand(const double val_max=1) { + cimg::mutex(4); + const double res = cimg::rand(val_max,&cimg::rng()); + cimg::mutex(4,0); + return res; + } + + inline double grand(cimg_uint64 *const p_rng) { double x1, w; do { - const double x2 = cimg::rand(-1,1); - x1 = cimg::rand(-1,1); + const double x2 = cimg::rand(-1,1,p_rng); + x1 = cimg::rand(-1,1,p_rng); w = x1*x1 + x2*x2; - } while (w<=0 || w>=1.0); + } while (w<=0 || w>=1.); 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); + inline double grand() { + cimg::mutex(4); + const double res = cimg::grand(&cimg::rng()); + cimg::mutex(4,0); + return res; + } + + inline unsigned int prand(const double z, cimg_uint64 *const p_rng) { + if (z<=1.e-10) return 0; + if (z>100) return (unsigned int)((std::sqrt(z) * cimg::grand(p_rng)) + z); unsigned int k = 0; const double y = std::exp(-z); - for (double s = 1.0; s>=y; ++k) s*=cimg::rand(); + for (double s = 1.; s>=y; ++k) s*=cimg::rand(1,p_rng); return k - 1; } + inline unsigned int prand(const double z) { + cimg::mutex(4); + const unsigned int res = cimg::prand(z,&cimg::rng()); + cimg::mutex(4,0); + return res; + } + //! Cut (i.e. clamp) value in specified interval. template inline T cut(const T& val, const t& val_min, const t& val_max) { - return valval_max?(T)val_max:val; + return val<=val_min?(T)val_min:val>=val_max?(T)val_max:val; } //! Bitwise-rotate value on the left. @@ -5297,7 +6236,7 @@ namespace cimg_library_suffixed { //! Return hyperbolic arcosine of a value. inline double acosh(const double x) { -#if defined(cimg_use_cpp11) && !defined(_MSC_VER) +#if cimg_use_cpp11==1 && !defined(_MSC_VER) return std::acosh(x); #else return std::log(x + std::sqrt(x*x - 1)); @@ -5306,7 +6245,7 @@ namespace cimg_library_suffixed { //! Return hyperbolic arcsine of a value. inline double asinh(const double x) { -#if defined(cimg_use_cpp11) && !defined(_MSC_VER) +#if cimg_use_cpp11==1 && !defined(_MSC_VER) return std::asinh(x); #else return std::log(x + std::sqrt(x*x + 1)); @@ -5315,10 +6254,10 @@ namespace cimg_library_suffixed { //! Return hyperbolic arctangent of a value. inline double atanh(const double x) { -#if defined(cimg_use_cpp11) && !defined(_MSC_VER) +#if cimg_use_cpp11==1 && !defined(_MSC_VER) return std::atanh(x); #else - return 0.5*std::log((1.0 + x)/(1.0 - x)); + return 0.5*std::log((1. + x)/(1. - x)); #endif } @@ -5329,10 +6268,10 @@ namespace cimg_library_suffixed { //! Return base-2 logarithm of a value. inline double log2(const double x) { -#if defined(cimg_use_cpp11) && !defined(_MSC_VER) +#if cimg_use_cpp11==1 && !defined(_MSC_VER) return std::log2(x); #else - const double base2 = std::log(2.0); + const double base2 = std::log(2.); return std::log(x)/base2; #endif } @@ -5343,16 +6282,37 @@ namespace cimg_library_suffixed { return val*val; } + // Return inverse of error function. + template + inline T erfinv(const T& val) { + const T + sgn = val<0?-1:1, + x = (1 - val)*(1 + val), + lnx = std::log(x), + tt1 = (T)(2/(cimg::PI*0.147) + 0.5*lnx), + tt2 = lnx/(T)0.147; + return sgn*std::sqrt(-tt1 + std::sqrt(tt1*tt1 - tt2)); + } + //! Return cubic root of a value. template 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); + return x>=0?std::pow((double)x,1./3):-std::pow(-(double)x,1./3); #endif } + template + inline T pow3(const T& val) { + return val*val*val; + } + template + inline T pow4(const T& val) { + return val*val*val*val; + } + //! Return the minimum between three values. template inline t min(const t& a, const t& b, const t& c) { @@ -5365,6 +6325,17 @@ namespace cimg_library_suffixed { return std::min(std::min(a,b),std::min(c,d)); } + //! Return the minabs between two values. + template + inline t minabs(const t& a, const t& b) { + return cimg::abs(b) + inline t minabs(const t& a, const t& b, const t& abs_b) { + return abs_b inline t max(const t& a, const t& b, const t& c) { @@ -5377,16 +6348,27 @@ namespace cimg_library_suffixed { return std::max(std::max(a,b),std::max(c,d)); } + //! Return the maxabs between two values. + template + inline t maxabs(const t& a, const t& b) { + return cimg::abs(b)>cimg::abs(a)?b:a; + } + + template + inline t maxabs(const t& a, const t& b, const t& abs_b) { + return abs_b>cimg::abs(a)?b:a; + } + //! Return the sign of a value. template inline T sign(const T& x) { - return (T)(x<0?-1:x>0); + return (T)(cimg::type::is_nan(x)?0:x<0?-1:x>0); } //! Return the nearest power of 2 higher than given value. template - inline cimg_ulong nearest_pow2(const T& x) { - cimg_ulong i = 1; + inline cimg_uint64 nearest_pow2(const T& x) { + cimg_uint64 i = 1; while (x>i) i<<=1; return i; } @@ -5399,16 +6381,25 @@ namespace cimg_library_suffixed { **/ template inline T mod(const T& x, const T& m) { + if (!m) { + if (cimg::type::is_float()) return cimg::type::nan(); + else throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + } const double dx = (double)x, dm = (double)m; - return (T)(dx - dm * std::floor(dx / dm)); + if (!cimg::type::is_finite(dm)) return x; + if (cimg::type::is_finite(dx)) return (T)(dx - dm * std::floor(dx / dm)); + return (T)0; } inline int mod(const bool x, const bool m) { - return m?(x?1:0):0; + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + return x?1:0; } inline int mod(const unsigned char x, const unsigned char m) { + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); return x%m; } inline int mod(const char x, const char m) { + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); #if defined(CHAR_MAX) && CHAR_MAX==255 return x%m; #else @@ -5416,22 +6407,28 @@ namespace cimg_library_suffixed { #endif } inline int mod(const unsigned short x, const unsigned short m) { - return x%m; + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + return (int)(x%m); } inline int mod(const short x, const short m) { - return x>=0?x%m:(x%m?m + x%m:0); + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + return (int)(x>=0?x%m:(x%m?m + x%m:0)); } inline int mod(const unsigned int x, const unsigned int m) { + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); return (int)(x%m); } inline int mod(const int x, const int m) { - return x>=0?x%m:(x%m?m + x%m:0); + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + return (int)(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; + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + return (cimg_int64)(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); + if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + return (cimg_int64)(x>=0?x%m:(x%m?m + x%m:0)); } //! Return the min-mod of two values. @@ -5450,6 +6447,11 @@ namespace cimg_library_suffixed { return (T)std::floor((_cimg_Tfloat)x + 0.5f); } + template + inline int uiround(const T x) { + return cimg::type::is_float()?(int)(x + 0.5f):(int)x; + } + //! Return rounded value. /** \param x Value to be rounded. @@ -5875,8 +6877,8 @@ namespace cimg_library_suffixed { inline double _fibonacci(int exp) { double - base = (1 + std::sqrt(5.0))/2, - result = 1/std::sqrt(5.0); + base = (1 + std::sqrt(5.))/2, + result = 1/std::sqrt(5.); while (exp) { if (exp&1) result*=base; exp>>=1; @@ -5900,8 +6902,8 @@ namespace cimg_library_suffixed { 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)1304969544928657ULL, - fn2 = (cimg_uint64)806515533049393ULL, + fn1 = ((cimg_uint64)303836)<<32 | 3861581201UL, // 1304969544928657ULL (avoid C++98 warning with ULL) + fn2 = ((cimg_uint64)187781)<<32 | 2279239217UL, // 806515533049393ULL fn = 0; for (int i = 75; i<=n; ++i) { fn = fn1 + fn2; fn2 = fn1; fn1 = fn; } return (double)fn; @@ -5915,7 +6917,7 @@ namespace cimg_library_suffixed { return b; } - //! Convert ascii character to lower case. + //! Convert character to lower case. inline char lowercase(const char x) { return (char)((x<'A'||x>'Z')?x:x - 'A' + 'a'); } @@ -5928,7 +6930,7 @@ namespace cimg_library_suffixed { if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = lowercase(*ptr); } - //! Convert ascii character to upper case. + //! Convert character to upper case. inline char uppercase(const char x) { return (char)((x<'a'||x>'z')?x:x - 'a' + 'A'); } @@ -5942,6 +6944,24 @@ namespace cimg_library_suffixed { if (str) for (char *ptr = str; *ptr; ++ptr) *ptr = uppercase(*ptr); } + //! Return \c true if input character is blank (space, tab, or non-printable character). + inline bool is_blank(const char c) { + return (unsigned char)c<=' '; + } + + // Return \c true if specified argument is in set \c [a-zA-Z0-9_]. + inline bool is_varchar(const char c) { + return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_'; + } + + //! Return \c true if argument \p str can be considered as a regular variable name. + inline bool is_varname(const char *const str, const unsigned int length=~0U) { + if (*str>='0' && *str<='9') return false; + for (unsigned int l = 0; l0?x/y:0; } - //! Compare the first \p l characters of two C-strings, ignoring the case. + //! Compare the first \p length characters of two C-strings, ignoring the case. /** \param str1 C-string. \param str2 C-string. - \param l Number of characters to compare. + \param length 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; + inline int strncasecmp(const char *const str1, const char *const str2, const int length) { + if (!length) return 0; if (!str1) return str2?-1:0; const char *nstr1 = str1, *nstr2 = str2; - int k, diff = 0; for (k = 0; kp && (signed char)str[q]<=' '; ) { --q; if (!is_iterative) break; } + for (p = 0; pp && is_blank(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; } @@ -6083,14 +7104,15 @@ namespace cimg_library_suffixed { } } - //! Replace escape sequences in C-strings by their binary ascii values. + //! Replace escape sequences in C-strings by character 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)) { + + unsigned char 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); @@ -6103,16 +7125,89 @@ namespace cimg_library_suffixed { 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++); + val = (unsigned char)(*(ns++) - '0'); + if (*ns>='0' && *ns<='7') (val<<=3)|=*(ns++) - '0'; + if (*ns>='0' && *ns<='7') (val<<=3)|=*(ns++) - '0'; + *nd = (char)val; + break; + case 'x' : { + char c = lowercase(*(++ns)); + if ((c>='0' && c<='9') || (c>='a' && c<='f')) { + val = (unsigned char)(c<='9'?c - '0':c - 'a' + 10); + c = lowercase(*(++ns)); + if ((c>='0' && c<='9') || (c>='a' && c<='f')) { + (val<<=4)|=(c<='9'?c - '0':c - 'a' + 10); + ++ns; + } + *nd = (char)val; + } else *nd = c; + } break; + case 'u' : { // UTF-8 BMP + char c1, c2, c3, c4; + if ((((c1 = lowercase(ns[1]))>='0' && c1<='9') || (c1>='a' && c1<='f')) && + (((c2 = lowercase(ns[2]))>='0' && c2<='9') || (c2>='a' && c2<='f')) && + (((c3 = lowercase(ns[3]))>='0' && c3<='9') || (c3>='a' && c3<='f')) && + (((c4 = lowercase(ns[4]))>='0' && c4<='9') || (c4>='a' && c4<='f'))) { + c1 = (c1<='9'?c1 - '0':c1 - 'a' + 10); + c2 = (c2<='9'?c2 - '0':c2 - 'a' + 10); + c3 = (c3<='9'?c3 - '0':c3 - 'a' + 10); + c4 = (c4<='9'?c4 - '0':c4 - 'a' + 10); + const unsigned int ival = + ((unsigned int)c1<<12) | ((unsigned int)c2<<8) | ((unsigned int)c3<<4) | c4; + if (ival<=0x007f) *nd = (char)ival; + else if (ival<=0x07ff) { + *(nd++) = (char)((ival>>6)|0xc0); + *nd = (char)((ival&0x3f)|0x80); + } else { + *(nd++) = (char)((ival>>12)|0xe0); + *(nd++) = (char)(((ival>>6)&0x3f)|0x80); + *nd = (char)((ival&0x3f)|0x80); + } + ns+=5; + } else *nd = *(ns++); + } break; + case 'U' : { // UTF-8 astral planes + char c1, c2, c3, c4, c5, c6, c7, c8; + if ((((c1 = lowercase(ns[1]))>='0' && c1<='9') || (c1>='a' && c1<='f')) && + (((c2 = lowercase(ns[2]))>='0' && c2<='9') || (c2>='a' && c2<='f')) && + (((c3 = lowercase(ns[3]))>='0' && c3<='9') || (c3>='a' && c3<='f')) && + (((c4 = lowercase(ns[4]))>='0' && c4<='9') || (c4>='a' && c4<='f')) && + (((c5 = lowercase(ns[5]))>='0' && c5<='9') || (c5>='a' && c5<='f')) && + (((c6 = lowercase(ns[6]))>='0' && c6<='9') || (c6>='a' && c6<='f')) && + (((c7 = lowercase(ns[7]))>='0' && c7<='9') || (c7>='a' && c7<='f')) && + (((c8 = lowercase(ns[8]))>='0' && c8<='9') || (c8>='a' && c8<='f'))) { + c1 = (c1<='9'?c1 - '0':c1 - 'a' + 10); + c2 = (c2<='9'?c2 - '0':c2 - 'a' + 10); + c3 = (c3<='9'?c3 - '0':c3 - 'a' + 10); + c4 = (c4<='9'?c4 - '0':c4 - 'a' + 10); + c5 = (c5<='9'?c5 - '0':c5 - 'a' + 10); + c6 = (c6<='9'?c6 - '0':c6 - 'a' + 10); + c7 = (c7<='9'?c7 - '0':c7 - 'a' + 10); + c8 = (c8<='9'?c8 - '0':c8 - 'a' + 10); + const unsigned int ival = + ((unsigned int)c1<<28) | ((unsigned int)c2<<24) | ((unsigned int)c3<<20) | ((unsigned int)c4<<16) | + ((unsigned int)c5<<12) | ((unsigned int)c6<<8) | ((unsigned int)c7<<4) | (unsigned int)c8; + if (ival<=0x007f) *nd = (char)ival; + else if (ival<=0x07ff) { + *(nd++) = (char)((ival>>6)|0xc0); + *nd = (char)((ival&0x3f)|0x80); + } else if (ival<=0xffff) { + *(nd++) = (char)((ival>>12)|0xe0); + *(nd++) = (char)(((ival>>6)&0x3f)|0x80); + *nd = (char)((ival&0x3f)|0x80); + } else { + *(nd++) = (char)((ival>>18)|0xf0); + *(nd++) = (char)(((ival>>12)&0x3f)|0x80); + *(nd++) = (char)(((ival>>6)&0x3f)|0x80); + *nd = (char)((ival&0x3f)|0x80); + } + ns+=9; + } else *nd = *(ns++); + } break; + default : if (*ns) *nd = *(ns++); + } + else *nd = *(ns++); } // Return a temporary string describing the size of a memory buffer. @@ -6155,7 +7250,6 @@ namespace cimg_library_suffixed { 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)): @@ -6177,8 +7271,9 @@ namespace cimg_library_suffixed { } } - // 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 (similar to std:: fopen(), but with wide character support on Windows). + inline std::FILE *std_fopen(const char *const path, const char *const mode); + //! Open a file. /** @@ -6198,7 +7293,7 @@ namespace cimg_library_suffixed { 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. + if (*mode && mode[1]=='b') { // Force stdin/stdout to be in binary mode #ifdef __BORLANDC__ if (setmode(_fileno(res),0x8000)==-1) res = 0; #else @@ -6206,7 +7301,7 @@ namespace cimg_library_suffixed { #endif } #endif - } else res = std_fopen(path,mode); + } else res = cimg::std_fopen(path,mode); if (!res) throw CImgIOException("cimg::fopen(): Failed to open file '%s' with mode '%s'.", path,mode); return res; @@ -6246,6 +7341,11 @@ namespace cimg_library_suffixed { #endif } + // Get the file or directory attributes with support for UTF-8 paths (Windows only). +#if cimg_OS==2 + inline DWORD win_getfileattributes(const char *const path); +#endif + //! Check if a path is a directory. /** \param path Specified path to test. @@ -6256,8 +7356,8 @@ namespace cimg_library_suffixed { 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); + const DWORD res = win_getfileattributes(path); + return res!=INVALID_FILE_ATTRIBUTES && (res&FILE_ATTRIBUTE_DIRECTORY); #else return false; #endif @@ -6269,10 +7369,15 @@ namespace cimg_library_suffixed { **/ inline bool is_file(const char *const path) { if (!path || !*path) return false; - std::FILE *const file = std_fopen(path,"rb"); +#if cimg_OS==2 + const DWORD res = cimg::win_getfileattributes(path); + return res!=INVALID_FILE_ATTRIBUTES && !(res&FILE_ATTRIBUTE_DIRECTORY); +#else + std::FILE *const file = cimg::std_fopen(path,"rb"); if (!file) return false; - std::fclose(file); + cimg::fclose(file); return !is_directory(path); +#endif } //! Get file size. @@ -6280,12 +7385,19 @@ namespace cimg_library_suffixed { \param filename Specified filename to get size from. \return File size or '-1' if file does not exist. **/ - inline cimg_int64 fsize(const char *const filename) { - std::FILE *const file = std::fopen(filename,"rb"); + inline cimg_int64 fsize(std::FILE *const file) { if (!file) return (cimg_int64)-1; + const long pos = std::ftell(file); std::fseek(file,0,SEEK_END); const cimg_int64 siz = (cimg_int64)std::ftell(file); - std::fclose(file); + std::fseek(file,pos,SEEK_SET); + return siz; + } + + inline cimg_int64 fsize(const char *const filename) { + std::FILE *const file = cimg::std_fopen(filename,"rb"); + const cimg_int64 siz = fsize(file); + cimg::fclose(file); return siz; } @@ -6294,7 +7406,7 @@ namespace cimg_library_suffixed { \param path Specified path to get attributes from. \param[in,out] attr Type of requested time attributes. Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second } - Replaced by read attributes after return (or -1 if an error occured). + Replaced by read attributes after return (or -1 if an error occurred). \param nb_attr Number of attributes to read/write. \return Latest read attribute. **/ @@ -6341,7 +7453,7 @@ namespace cimg_library_suffixed { \param path Specified path to get attributes from. \param attr Type of requested time attributes. Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second } - \return Specified attribute or -1 if an error occured. + \return Specified attribute or -1 if an error occurred. **/ inline int fdate(const char *const path, unsigned int attr) { int out = (int)attr; @@ -6351,8 +7463,9 @@ namespace cimg_library_suffixed { //! Get current local time (multiple-attributes version). /** \param[in,out] attr Type of requested time attributes. - Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second } - Replaced by read attributes after return (or -1 if an error occured). + Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second | + 7=millisecond } + Replaced by read attributes after return (or -1 if an error occurred). \param nb_attr Number of attributes to read/write. \return Latest read attribute. **/ @@ -6364,19 +7477,29 @@ namespace cimg_library_suffixed { SYSTEMTIME st; GetLocalTime(&st); for (unsigned int i = 0; itm_year + 1900:attr[i]==1?st->tm_mon + 1:attr[i]==2?st->tm_mday: - attr[i]==3?st->tm_wday:attr[i]==4?st->tm_hour:attr[i]==5?st->tm_min: - attr[i]==6?st->tm_sec:-1); + res = (int)(attr[i]==0?st->tm_year + 1900: + attr[i]==1?st->tm_mon + 1: + attr[i]==2?st->tm_mday: + attr[i]==3?st->tm_wday: + attr[i]==4?st->tm_hour: + attr[i]==5?st->tm_min: + attr[i]==6?st->tm_sec: + attr[i]==7?_st.tv_usec/1000:-1); attr[i] = (T)res; } #endif @@ -6387,77 +7510,69 @@ namespace cimg_library_suffixed { //! Get current local time (single-attribute version). /** \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 Specified attribute or -1 if an error occured. + Can be { 0=year | 1=month | 2=day | 3=day of week | 4=hour | 5=minute | 6=second | + 7=millisecond } + \return Specified attribute or -1 if an error occurred. **/ inline int date(unsigned int attr) { int out = (int)attr; return date(&out,1); } - // 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 Program Files/ directory (Windows only). -#if cimg_OS==2 - inline const char* programfiles_path(const char *const user_path=0, const bool reinit_path=false); -#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 curl binary. + inline const char *curl_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 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 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 \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 gzip binary. + inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false); + + // 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 Medcon's \c medcon binary. + inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false); + + // 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 \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); +#if cimg_OS==2 + // Get/set path to the \c powershell binary. + inline const char *powershell_path(const char *const user_path=0, const bool reinit_path=false); +#endif //! 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 (!filename) { if (body) *body = 0; return ""; } + const char * p = std::strrchr(filename,'.'); + if (!p || std::strchr(p,'/') || std::strchr(p,'\\')) { // No extension. if (body) std::strcpy(body,filename); return filename + std::strlen(filename); } - const unsigned int l = (unsigned int)(p - filename - 1); + const unsigned int l = (unsigned int)(p - filename); if (body) { if (l) std::memcpy(body,filename,l); body[l] = 0; } - return p; + return p + 1; } - //! Generate a numbered version of a filename. + // 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; - } + const unsigned int digits, char *const str); //! Read data from file. /** @@ -6530,14 +7645,25 @@ namespace cimg_library_suffixed { // Try to guess format from an image file. inline const char *ftype(std::FILE *const file, const char *const filename); + // Get or set load from network mode (can be { 0=disabled | 1=enabled }). + inline bool& network_mode(const bool value, const bool is_set) { + static bool mode = true; + if (is_set) { cimg::mutex(0); mode = value; cimg::mutex(0,0); } + return mode; + } + + inline bool& network_mode() { + return network_mode(false,false); + } + // 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); + const char *const referer=0, const char *const user_agent=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) { + const char *const _default, 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; @@ -6553,14 +7679,14 @@ namespace cimg_library_suffixed { 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 (_default) std::fprintf(cimg::output(),"%s\n",_default); } if (name) { if (argc>0) { int k = 0; while (k Operating System: %s%-13s%s %s('cimg_OS'=%d)%s\n", + 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_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknown"), cimg::t_normal,cimg::t_green, cimg_OS, cimg::t_normal); - std::fprintf(cimg::output()," > CPU endianness: %s%s Endian%s\n", + 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", + 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": @@ -6655,7 +7781,7 @@ namespace cimg_library_suffixed { cimg_verbosity, cimg::t_normal); - std::fprintf(cimg::output()," > Stricts warnings: %s%-13s%s %s('cimg_strict_warnings' %s)%s\n", + 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", @@ -6664,14 +7790,14 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); - std::fprintf(cimg::output()," > Support for C++11: %s%-13s%s %s('cimg_use_cpp11'=%d)%s\n", + 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", + 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", @@ -6680,7 +7806,7 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); - std::fprintf(cimg::output()," > Display type: %s%-13s%s %s('cimg_display'=%d)%s\n", + 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, @@ -6688,7 +7814,7 @@ namespace cimg_library_suffixed { 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", + 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", @@ -6697,7 +7823,7 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); - std::fprintf(cimg::output()," > Using XRand for X11: %s%-13s%s %s('cimg_use_xrandr' %s)%s\n", + 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", @@ -6706,15 +7832,15 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); #endif - std::fprintf(cimg::output()," > Using OpenMP: %s%-13s%s %s('cimg_use_openmp' %s)%s\n", + std::fprintf(cimg::output()," > Using OpenMP: %s%-13s%s %s('cimg_use_openmp' %s)%s\n", cimg::t_bold, -#ifdef cimg_use_openmp +#if cimg_use_openmp!=0 "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", + 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", @@ -6722,7 +7848,7 @@ namespace cimg_library_suffixed { "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", + 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", @@ -6731,7 +7857,7 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); - std::fprintf(cimg::output()," > Using TIFF library: %s%-13s%s %s('cimg_use_tiff' %s)%s\n", + 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", @@ -6740,7 +7866,7 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); - std::fprintf(cimg::output()," > Using Magick++ library: %s%-13s%s %s('cimg_use_magick' %s)%s\n", + 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", @@ -6749,7 +7875,7 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); - std::fprintf(cimg::output()," > Using FFTW3 library: %s%-13s%s %s('cimg_use_fftw3' %s)%s\n", + 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", @@ -6758,7 +7884,7 @@ namespace cimg_library_suffixed { #endif cimg::t_normal); - std::fprintf(cimg::output()," > Using LAPACK library: %s%-13s%s %s('cimg_use_lapack' %s)%s\n", + 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", @@ -6768,30 +7894,74 @@ namespace cimg_library_suffixed { 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_snprintf(tmp,1024,"\"%.1020s\"",cimg::curl_path()); + std::fprintf(cimg::output()," > Path of 'curl': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::dcraw_path()); + std::fprintf(cimg::output()," > Path of 'dcraw': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::ffmpeg_path()); + std::fprintf(cimg::output()," > Path of 'ffmpeg': %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", + std::fprintf(cimg::output()," > Path of 'graphicsmagick': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::gunzip_path()); + std::fprintf(cimg::output()," > Path of 'gunzip': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::gzip_path()); + std::fprintf(cimg::output()," > Path of 'gzip': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); + + 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::medcon_path()); - std::fprintf(cimg::output()," > Path of 'medcon': %s%-13s%s\n", + 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", + std::fprintf(cimg::output()," > Temporary path: %s%-13s%s\n", cimg::t_bold, tmp, cimg::t_normal); + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::wget_path()); + std::fprintf(cimg::output()," > Path of 'wget': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); +#if cimg_OS==2 + cimg_snprintf(tmp,1024,"\"%.1020s\"",cimg::powershell_path()); + std::fprintf(cimg::output()," > Path of 'powershell_path': %s%-13s%s\n", + cimg::t_bold, + tmp, + cimg::t_normal); +#endif + std::fprintf(cimg::output(),"\n"); delete[] tmp; } @@ -6849,19 +8019,18 @@ namespace cimg_library_suffixed { template 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){ + 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){ + 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 - } + } // namespace cimg { ... /*------------------------------------------------ # @@ -6872,7 +8041,7 @@ namespace cimg_library_suffixed { # -------------------------------------------------*/ -#define _cimg_create_ext_operators(typ) \ +#define _cimg_create_operator(typ) \ template \ inline CImg::type> operator+(const typ val, const CImg& img) { \ return img + val; \ @@ -6911,19 +8080,19 @@ namespace cimg_library_suffixed { 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) + _cimg_create_operator(bool) + _cimg_create_operator(unsigned char) + _cimg_create_operator(char) + _cimg_create_operator(signed char) + _cimg_create_operator(unsigned short) + _cimg_create_operator(short) + _cimg_create_operator(unsigned int) + _cimg_create_operator(int) + _cimg_create_operator(cimg_uint64) + _cimg_create_operator(cimg_int64) + _cimg_create_operator(float) + _cimg_create_operator(double) + _cimg_create_operator(long double) template inline CImg<_cimg_Tfloat> operator+(const char *const expression, const CImg& img) { @@ -6976,42 +8145,38 @@ namespace cimg_library_suffixed { } template - inline CImg<_cimg_Tfloat> invert(const CImg& instance) { - return instance.get_invert(); + inline CImg<_cimg_Tfloat> invert(const CImg& instance, const bool use_LU=false, const float lambda=0) { + return instance.get_invert(use_LU,lambda); } - template - inline CImg<_cimg_Tfloat> pseudoinvert(const CImg& instance) { - return instance.get_pseudoinvert(); - } - -#define _cimg_create_ext_pointwise_function(name) \ +#define _cimg_create_pointwise_function(name) \ template \ inline CImg<_cimg_Tfloat> name(const CImg& instance) { \ return instance.get_##name(); \ } - _cimg_create_ext_pointwise_function(sqr) - _cimg_create_ext_pointwise_function(sqrt) - _cimg_create_ext_pointwise_function(exp) - _cimg_create_ext_pointwise_function(log) - _cimg_create_ext_pointwise_function(log2) - _cimg_create_ext_pointwise_function(log10) - _cimg_create_ext_pointwise_function(abs) - _cimg_create_ext_pointwise_function(sign) - _cimg_create_ext_pointwise_function(cos) - _cimg_create_ext_pointwise_function(sin) - _cimg_create_ext_pointwise_function(sinc) - _cimg_create_ext_pointwise_function(tan) - _cimg_create_ext_pointwise_function(acos) - _cimg_create_ext_pointwise_function(asin) - _cimg_create_ext_pointwise_function(atan) - _cimg_create_ext_pointwise_function(cosh) - _cimg_create_ext_pointwise_function(sinh) - _cimg_create_ext_pointwise_function(tanh) - _cimg_create_ext_pointwise_function(acosh) - _cimg_create_ext_pointwise_function(asinh) - _cimg_create_ext_pointwise_function(atanh) + _cimg_create_pointwise_function(sqr) + _cimg_create_pointwise_function(sqrt) + _cimg_create_pointwise_function(erf) + _cimg_create_pointwise_function(exp) + _cimg_create_pointwise_function(log) + _cimg_create_pointwise_function(log2) + _cimg_create_pointwise_function(log10) + _cimg_create_pointwise_function(abs) + _cimg_create_pointwise_function(sign) + _cimg_create_pointwise_function(cos) + _cimg_create_pointwise_function(sin) + _cimg_create_pointwise_function(sinc) + _cimg_create_pointwise_function(tan) + _cimg_create_pointwise_function(acos) + _cimg_create_pointwise_function(asin) + _cimg_create_pointwise_function(atan) + _cimg_create_pointwise_function(cosh) + _cimg_create_pointwise_function(sinh) + _cimg_create_pointwise_function(tanh) + _cimg_create_pointwise_function(acosh) + _cimg_create_pointwise_function(asinh) + _cimg_create_pointwise_function(atanh) /*----------------------------------- # @@ -7023,7 +8188,7 @@ namespace cimg_library_suffixed { 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 + a minimal mode where warning messages will be outputted 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. @@ -7036,7 +8201,7 @@ namespace cimg_library_suffixed { 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; + cimg_uint64 _timer, _fps_frames, _fps_timer; unsigned int _width, _height, _normalization; float _fps_fps, _min, _max; bool _is_fullscreen; @@ -7115,9 +8280,9 @@ namespace cimg_library_suffixed { display of valid data is performed. \par Example \code - CImgDisplay disp; // Does actually nothing. + CImgDisplay disp; // Does actually nothing ... - disp.display(img); // Construct new window and display image in it. + disp.display(img); // Construct new window and display image in it \endcode **/ CImgDisplay(): @@ -7127,7 +8292,8 @@ namespace cimg_library_suffixed { _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), + _window_x(cimg::type::min()),_window_y(cimg::type::min()), + _mouse_x(-1),_mouse_y(-1),_wheel(0), _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { assign(); } @@ -7151,7 +8317,8 @@ namespace cimg_library_suffixed { _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), + _window_x(cimg::type::min()),_window_y(cimg::type::min()), + _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); } @@ -7175,7 +8342,8 @@ namespace cimg_library_suffixed { _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), + _window_x(cimg::type::min()),_window_y(cimg::type::min()), + _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); } @@ -7199,7 +8367,8 @@ namespace cimg_library_suffixed { _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), + _window_x(cimg::type::min()),_window_y(cimg::type::min()), + _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); } @@ -7216,7 +8385,8 @@ namespace cimg_library_suffixed { _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), + _window_x(cimg::type::min()),_window_y(cimg::type::min()), + _mouse_x(-1),_mouse_y(-1),_wheel(0), _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) { assign(disp); } @@ -7307,26 +8477,29 @@ namespace cimg_library_suffixed { return _empty; } -#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,480,-85,false), \ - CImgDisplay::_fitscreen(dx,dy,dz,480,-85,true) +#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,-25,-85,false), \ + CImgDisplay::_fitscreen(dx,dy,dz,-25,-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 (nwMw) { nh = nh*Mw/nw; nh+=(nh==0); nw = Mw; } - if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0); nh = Mh; } - if (nw1) { w+=dz; h+=dz; } + if (wMw) { h = h*Mw/w; w = Mw; } + if (h>Mh) { w = w*Mh/h; h = Mh; } + if (w=1) { _fps_fps = _fps_frames/delta; @@ -7902,6 +9083,22 @@ namespace cimg_library_suffixed { return _fps_fps; } + // Move current display window so that its content stays inside the current screen. + CImgDisplay& move_inside_screen() { + if (is_empty()) return *this; + const int + x0 = window_x(), + y0 = window_y(), + x1 = x0 + window_width() - 1, + y1 = y0 + window_height() - 1, + sw = CImgDisplay::screen_width(), + sh = CImgDisplay::screen_height(); + if (x0<0 || y0<0 || x1>=sw || y1>=sh) + move(std::max(0,std::min(x0,sw - x1 + x0)), + std::max(0,std::min(y0,sh - y1 + y0))); + return *this; + } + //@} //--------------------------------------- // @@ -7941,8 +9138,8 @@ namespace cimg_library_suffixed { unsigned int dims = 0; cimglist_for(list,l) { const CImg& 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]); + 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 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::type ulongT; + const ulongT one = (ulongT)1; + CImg off_x(wd), off_y(hd + 1); + if (wd==ws) off_x.fill(1); + else { + ulongT *poff_x = off_x._data, curr = 0; + for (unsigned int x = 0; x=32?false:(keys_return[kc1]>>kc2)&1; @@ -8654,7 +9868,7 @@ namespace cimg_library_suffixed { } } - static void* _events_thread(void *arg) { // Thread to manage events for all opened display windows. + 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); @@ -8677,7 +9891,7 @@ namespace cimg_library_suffixed { return 0; } - void _set_colormap(Colormap& _colormap, const unsigned int dim) { + void _set_colormap(Colormap& cmap, const unsigned int dim) { XColor *const colormap = new XColor[256]; switch (dim) { case 1 : { // colormap for greyscale images @@ -8708,7 +9922,7 @@ namespace cimg_library_suffixed { } } } - XStoreColors(cimg::X11_attr().display,_colormap,colormap,256); + XStoreColors(cimg::X11_attr().display,cmap,colormap,256); delete[] colormap; } @@ -8718,14 +9932,14 @@ namespace cimg_library_suffixed { XWindowAttributes attr; XEvent event; XMapRaised(dpy,_window); - do { // Wait for the window to be mapped. + 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. + 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); @@ -8736,7 +9950,7 @@ namespace cimg_library_suffixed { 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. + if (wait_expose) { // Send an expose event sticked to display window to force repaint XEvent event; event.xexpose.type = Expose; event.xexpose.serial = 0; @@ -8749,8 +9963,9 @@ namespace cimg_library_suffixed { event.xexpose.height = height(); event.xexpose.count = 0; XSendEvent(dpy,_window,0,0,&event); - } else { // Repaint directly (may be called from the expose 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); @@ -8861,38 +10076,30 @@ namespace cimg_library_suffixed { const unsigned int sx = screen_width(), sy = screen_height(); if (sx==_width && sy==_height) return; - XSetWindowAttributes winattr; - winattr.override_redirect = 1; + XSetWindowAttributes attr_set; + + attr_set.background_pixel = XBlackPixel(dpy,XDefaultScreen(dpy)); + attr_set.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); + InputOutput,CopyFromParent,CWBackPixel | CWOverrideRedirect,&attr_set); 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); + do { + XGetWindowAttributes(dpy,_background_window,&attr); + if (attr.map_state!=IsViewable) { XSync(dpy,0); cimg::sleep(10); } + } while (attr.map_state!=IsViewable); } 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)); @@ -8950,7 +10157,7 @@ namespace cimg_library_suffixed { XVisualInfo *vinfo = XGetVisualInfo(dpy,VisualIDMask,&vtemplate,&nb_visuals); if (vinfo && vinfo->red_maskblue_mask) cimg::X11_attr().is_blue_first = true; cimg::X11_attr().byte_order = ImageByteOrder(dpy); - XFree(vinfo); + XFree(vinfo); cimg_lock_display(); cimg::X11_attr().events_thread = new pthread_t; @@ -8962,7 +10169,7 @@ namespace cimg_library_suffixed { _height = std::min(dimh,(unsigned int)screen_height()); _normalization = normalization_type<4?normalization_type:3; _is_fullscreen = fullscreen_flag; - _window_x = _window_y = 0; + _window_x = _window_y = cimg::type::min(); _is_closed = closed_flag; _title = tmp_title; flush(); @@ -8971,16 +10178,16 @@ namespace cimg_library_suffixed { if (_is_fullscreen) { if (!_is_closed) _init_fullscreen(); const unsigned int sx = screen_width(), sy = screen_height(); - XSetWindowAttributes winattr; - winattr.override_redirect = 1; + XSetWindowAttributes attr_set; + attr_set.override_redirect = 1; _window = XCreateWindow(dpy,DefaultRootWindow(dpy),(sx - _width)/2,(sy - _height)/2,_width,_height,0,0, - InputOutput,CopyFromParent,CWOverrideRedirect,&winattr); + InputOutput,CopyFromParent,CWOverrideRedirect,&attr_set); } 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); + ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask | + EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask); XStoreName(dpy,_window,_title?_title:" "); if (cimg::X11_attr().nb_bits==8) { @@ -9045,7 +10252,7 @@ namespace cimg_library_suffixed { 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::min(); } + if (!_is_closed) _map_window(); else _window_x = _window_y = cimg::type::min(); cimg_unlock_display(); cimg::mutex(14,0); } @@ -9063,28 +10270,28 @@ namespace cimg_library_suffixed { // 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; + + XDestroyImage(_image); if (cimg::X11_attr().nb_bits==8) XFreeColormap(dpy,_colormap); - _colormap = 0; + XDestroyWindow(dpy,_window); XSync(dpy,0); + _window = 0; _colormap = 0; _data = 0; _image = 0; // Reset display variables. delete[] _title; _width = _height = _normalization = _window_width = _window_height = 0; - _window_x = _window_y = 0; + _window_x = _window_y = cimg::type::min(); _is_fullscreen = false; _is_closed = true; _min = _max = 0; @@ -9162,7 +10369,7 @@ namespace cimg_library_suffixed { XResizeWindow(dpy,_window,dimx,dimy); XGetWindowAttributes(dpy,_window,&attr); if (attr.width==(int)dimx && attr.height==(int)dimy) break; - cimg::wait(5); + cimg::wait(5,&_timer); } } if (_width!=dimx || _height!=dimy) switch (cimg::X11_attr().nb_bits) { @@ -9197,9 +10404,9 @@ namespace cimg_library_suffixed { CImgDisplay& show() { if (is_empty() || !_is_closed) return *this; cimg_lock_display(); + _is_closed = false; if (_is_fullscreen) _init_fullscreen(); _map_window(); - _is_closed = false; cimg_unlock_display(); return paint(); } @@ -9210,7 +10417,7 @@ namespace cimg_library_suffixed { cimg_lock_display(); if (_is_fullscreen) _desinit_fullscreen(); XUnmapWindow(dpy,_window); - _window_x = _window_y = -1; + _window_x = _window_y = cimg::type::min(); _is_closed = true; cimg_unlock_display(); return *this; @@ -9218,12 +10425,13 @@ namespace cimg_library_suffixed { CImgDisplay& move(const int posx, const int posy) { if (is_empty()) return *this; + show(); 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; + _window_x = posx; + _window_y = posy; cimg_unlock_display(); } _is_moved = false; @@ -9243,7 +10451,7 @@ namespace cimg_library_suffixed { if (is_empty()) return *this; Display *const dpy = cimg::X11_attr().display; cimg_lock_display(); - static const char pix_data[8] = { 0 }; + static const char pix_data[8] = {}; XColor col; col.red = col.green = col.blue = 0; Pixmap pix = XCreateBitmapFromData(dpy,_window,pix_data,8,8); @@ -9343,18 +10551,18 @@ namespace cimg_library_suffixed { (*ptrd++) = (unsigned char)*(data1++); break; case 2 : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { - const unsigned char + const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++); - (*ptrd++) = (R&0xf0) | (G>>4); - } break; + (*ptrd++) = (R&0xf0) | (G>>4); + } break; default : for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { - const unsigned char + const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++), B = (unsigned char)*(data3++); - (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); - } + (*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6); + } } if (ndata!=_data) { _render_resize(ndata,img._width,img._height,(unsigned char*)_data,_width,_height); @@ -9504,10 +10712,10 @@ namespace cimg_library_suffixed { } } else { if (_normalization==3) { - if (cimg::type::is_float()) _min = (float)img.min_max(_max); + if (sizeof(T)>1 && cimg::type::string()!=cimg::type::string()) _min = (float)img.min_max(_max); else { _min = (float)cimg::type::min(); _max = (float)cimg::type::max(); } } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max); - const float delta = _max - _min, mm = 255/(delta?delta:1.0f); + const float delta = _max - _min, mm = 255/(delta?delta:1.f); switch (cimg::X11_attr().nb_bits) { case 8 : { // 256 colormap, with normalization _set_colormap(_colormap,img._spectrum); @@ -9688,7 +10896,7 @@ namespace cimg_library_suffixed { _render_resize(ndata,img._width,img._height,(unsigned int*)_data,_width,_height); delete[] ndata; } - } + } } } cimg_unlock_display(); @@ -9845,7 +11053,7 @@ namespace cimg_library_suffixed { switch (msg) { case WM_CLOSE : disp->_mouse_x = disp->_mouse_y = -1; - disp->_window_x = disp->_window_y = 0; + disp->_window_x = disp->_window_y = cimg::type::min(); disp->set_button().set_key(0).set_key(0,false)._is_closed = true; ReleaseMutex(disp->_mutex); ShowWindow(disp->_window,SW_HIDE); @@ -9879,9 +11087,9 @@ namespace cimg_library_suffixed { } 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); + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE_WIN)>=0); + cimg_unlock_display(); break; case WM_ERASEBKGND : // return 0; @@ -9911,16 +11119,16 @@ namespace cimg_library_suffixed { 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; + cimg_lock_display(); + if (disp->_is_cursor_visible) while (ShowCursor(TRUE)<0); else while (ShowCursor(FALSE_WIN)>=0); + cimg_unlock_display(); + } 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); + cimg_lock_display(); + while (ShowCursor(TRUE)<0) {} + cimg_unlock_display(); } break; case WM_LBUTTONDOWN : disp->set_button(1); @@ -9976,25 +11184,35 @@ namespace cimg_library_suffixed { 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); + border2 = (int)(rect.bottom - rect.top + 1 - disp->_height - border1), + ww = disp->width() + 2*border1, + wh = disp->height() + border1 + border2, + sw = CImgDisplay::screen_width(), + sh = CImgDisplay::screen_height(); + int + wx = (int)cimg::round(cimg::rand(0,sw - ww -1)), + wy = (int)cimg::round(cimg::rand(64,sh - wh - 65)); + if (wx + ww>=sw) wx = sw - ww; + if (wy + wh>=sh) wy = sh - wh; + if (wx<0) wx = 0; + if (wy<0) wy = 0; 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)); + (DWORD)(WS_OVERLAPPEDWINDOW | (disp->_is_closed?0:WS_VISIBLE)), + wx,wy,ww,wh,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; + disp->_window_x = rect.left; + disp->_window_y = rect.top; + } else disp->_window_x = disp->_window_y = cimg::type::min(); } 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)); + (DWORD)(WS_POPUP | (disp->_is_closed?0:WS_VISIBLE)), + (int)(sx - disp->_width)/2, + (int)(sy - disp->_height)/2, + disp->width(),disp->height(),0,0,0,&(disp->_ccs)); disp->_window_x = disp->_window_y = 0; } SetForegroundWindow(disp->_window); @@ -10015,17 +11233,14 @@ namespace cimg_library_suffixed { } CImgDisplay& _update_window_pos() { - if (_is_closed) _window_x = _window_y = -1; + if (_is_closed) _window_x = _window_y = cimg::type::min(); 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; + _window_x = rect.left; + _window_y = rect.top; } return *this; } @@ -10034,7 +11249,7 @@ namespace cimg_library_suffixed { _background_window = 0; if (!_is_fullscreen || _is_closed) _curr_mode.dmSize = 0; else { - DEVMODE mode; +/* 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; @@ -10050,13 +11265,15 @@ namespace cimg_library_suffixed { EnumDisplaySettings(0,ibest,&mode); ChangeDisplaySettings(&mode,0); } else _curr_mode.dmSize = 0; - +*/ + _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); + CLIENTCREATESTRUCT background_ccs = { 0,0 }; + _background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, + 0,0,(int)sx,(int)sy,0,0,0,&background_ccs); SetForegroundWindow(_background_window); } } @@ -10088,7 +11305,7 @@ namespace cimg_library_suffixed { _height = std::min(dimh,(unsigned int)screen_height()); _normalization = normalization_type<4?normalization_type:3; _is_fullscreen = fullscreen_flag; - _window_x = _window_y = 0; + _window_x = _window_y = cimg::type::min(); _is_closed = closed_flag; _is_cursor_visible = true; _is_mouse_tracked = false; @@ -10100,8 +11317,8 @@ namespace cimg_library_suffixed { 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); + _mutex = CreateMutex(0,FALSE_WIN,0); + _is_created = CreateEvent(0,FALSE_WIN,FALSE_WIN,0); _thread = CreateThread(0,0,_events_thread,arg,0,0); WaitForSingleObject(_is_created,INFINITE); return *this; @@ -10117,7 +11334,7 @@ namespace cimg_library_suffixed { _title = 0; if (_is_fullscreen) _desinit_fullscreen(); _width = _height = _normalization = _window_width = _window_height = 0; - _window_x = _window_y = 0; + _window_x = _window_y = cimg::type::min(); _is_fullscreen = false; _is_closed = true; _min = _max = 0; @@ -10173,7 +11390,7 @@ namespace cimg_library_suffixed { 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); + if (is_empty()) return assign((unsigned int)nwidth,(unsigned int)nheight); const unsigned int tmpdimx = (nwidth>0)?nwidth:(-nwidth*_width/100), tmpdimy = (nheight>0)?nheight:(-nheight*_height/100), @@ -10236,26 +11453,18 @@ namespace cimg_library_suffixed { _is_closed = true; if (_is_fullscreen) _desinit_fullscreen(); ShowWindow(_window,SW_HIDE); - _window_x = _window_y = 0; + _window_x = _window_y = cimg::type::min(); 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); + SetWindowPos(_window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER); _window_x = posx; _window_y = posy; - show(); } + show(); _is_moved = false; return *this; } @@ -10274,9 +11483,11 @@ namespace cimg_library_suffixed { 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; } + if (!_is_closed) { + _update_window_pos(); + const int res = (int)SetCursorPos(_window_x + posx,_window_y + posy); + if (res) { _mouse_x = posx; _mouse_y = posy; } + } return *this; } @@ -10367,9 +11578,12 @@ namespace cimg_library_suffixed { } else { if (_normalization==3) { if (cimg::type::is_float()) _min = (float)img.min_max(_max); - else { _min = (float)cimg::type::min(); _max = (float)cimg::type::max(); } + else { + _min = (float)cimg::type::min(); + _max = (float)cimg::type::max(); + } } else if ((_min>_max) || _normalization==1) _min = (float)img.min_max(_max); - const float delta = _max - _min, mm = 255/(delta?delta:1.0f); + const float delta = _max - _min, mm = 255/(delta?delta:1.f); switch (img._spectrum) { case 1 : { for (cimg_ulong xy = (cimg_ulong)img._width*img._height; xy>0; --xy) { @@ -10481,7 +11695,7 @@ namespace cimg_library_suffixed { #endif //@} - }; + }; // struct CImgDisplay { ... /* #-------------------------------------- @@ -10511,7 +11725,7 @@ namespace cimg_library_suffixed { If you need a fifth dimension, you can use image lists \c CImgList rather than simple images \c CImg. Thus, the \c CImg class is able to represent volumetric images of vector-valued pixels, - as well as images with less dimensions (1d scalar signal, 2d color images, ...). + 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: @@ -10601,10 +11815,10 @@ namespace cimg_library_suffixed { being achieved (often in a faster way) using methods of \c CImg. \par Example \code - CImg img("reference.jpg"); // Load image from file. + CImg img("reference.jpg"); // Load image from file // Set all pixels to '0', with a CImg iterator. for (CImg::iterator it = img.begin(), it. \par Example \code - const CImg img("reference.jpg"); // Load image from file. + const CImg img("reference.jpg"); // Load image from file float sum = 0; // Compute sum of all pixel values, with a CImg iterator. for (CImg::iterator it = img.begin(), it::type floatT; typedef typename cimg::last::type doubleT; + // Return 'dx*dy*dz*dc' as a 'size_t' and check no overflow occurs. + static size_t safe_size(const unsigned int dx, const unsigned int dy, + const unsigned int dz, const unsigned int dc) { + if (!(dx && dy && dz && dc)) return 0; + size_t siz = (size_t)dx, osiz = siz; + if ((dy==1 || (siz*=dy)>osiz) && + ((osiz = siz), dz==1 || (siz*=dz)>osiz) && + ((osiz = siz), dc==1 || (siz*=dc)>osiz) && + ((osiz = siz), sizeof(T)==1 || (siz*sizeof(T))>osiz)) { + if (siz > cimg_max_buf_size){ + throw CImgArgumentException("CImg<%s>::safe_size(): Specified size (%u,%u,%u,%u) exceeds maximum " + "allowed buffer size of %lu ", + pixel_type(),dx,dy,dz,dc,cimg_max_buf_size); + } + return siz; + } + throw CImgArgumentException("CImg<%s>::safe_size(): Specified size (%u,%u,%u,%u) overflows 'size_t'.", + pixel_type(),dx,dy,dz,dc); + } + //@} //--------------------------- // @@ -10728,10 +11962,10 @@ namespace cimg_library_suffixed { - An empty image is never shared. \par Example \code - CImg 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. + CImg 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) {} @@ -10755,14 +11989,14 @@ namespace cimg_library_suffixed { CImg(unsigned int,unsigned int,unsigned int,unsigned int,T) instead. \par Example \code - CImg img1(256,256,1,3); // Construct a 256x256x1x3 (color) image, filled with garbage values. - CImg img2(256,256,1,3,0); // Construct a 256x256x1x3 (color) image, filled with value '0'. + CImg img1(256,256,1,3); // Construct a 256x256x1x3 (color) image, filled with garbage values + CImg 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; + const size_t siz = safe_size(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 (...) { @@ -10794,7 +12028,7 @@ namespace cimg_library_suffixed { 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; + const size_t siz = safe_size(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 (...) { @@ -10829,10 +12063,10 @@ namespace cimg_library_suffixed { Otherwise, the constructor may crash or fill your image pixels with garbage. \par Example \code - const CImg 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. + const CImg 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 @@ -10841,21 +12075,21 @@ namespace cimg_library_suffixed { 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); \ - } \ + 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); + _CImg_stdarg(*this,value0,value1,safe_size(size_x,size_y,size_z,size_c),int); } #if cimg_use_cpp11==1 @@ -10876,10 +12110,10 @@ namespace cimg_library_suffixed { the pixel buffer with a sequence of specified integer values. \par Example \code - const CImg 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. + const CImg 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 @@ -10887,7 +12121,7 @@ namespace cimg_library_suffixed { template 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 values, - const bool repeat_values=true): + 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(); \ @@ -10902,7 +12136,7 @@ namespace cimg_library_suffixed { template CImg(const unsigned int size_x, const unsigned int size_y, const unsigned int size_z, std::initializer_list values, - const bool repeat_values=true): + 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); @@ -10911,7 +12145,7 @@ namespace cimg_library_suffixed { template CImg(const unsigned int size_x, const unsigned int size_y, std::initializer_list values, - const bool repeat_values=true): + 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); @@ -10937,7 +12171,7 @@ namespace cimg_library_suffixed { but it also fills the pixel buffer with a sequence of specified integer values. \par Example \code - const CImg img = {10,20,30,20,10 }; // Construct a 5x1 image with one channel, and set its pixel values. + const CImg 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 @@ -10952,7 +12186,7 @@ namespace cimg_library_suffixed { } template - CImg & operator=(std::initializer_list values) { + CImg& operator=(std::initializer_list values) { _cimg_constructor_cpp11(siz>values.size()); return *this; } @@ -10984,7 +12218,7 @@ namespace cimg_library_suffixed { 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); + _CImg_stdarg(*this,value0,value1,safe_size(size_x,size_y,size_z,size_c),double); } //! Construct image with specified size and initialize pixel values from a value string. @@ -11011,15 +12245,15 @@ namespace cimg_library_suffixed { - A \c CImgArgumentException is thrown when an invalid value string \c values is specified. \par Example \code - const CImg 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. + const CImg 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; + const char *const values, const bool repeat_values):_is_shared(false) { + const size_t siz = safe_size(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 (...) { @@ -11058,10 +12292,10 @@ namespace cimg_library_suffixed { (e.g. already deallocated). \par Example \code - unsigned char tab[256*256] = { 0 }; - CImg 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'. + unsigned char tab[256*256] = {}; + CImg 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 @@ -11075,7 +12309,7 @@ namespace cimg_library_suffixed { cimg_instance, size_x,size_y,size_z,size_c,CImg::pixel_type()); } - const size_t siz = (size_t)size_x*size_y*size_z*size_c; + const size_t siz = safe_size(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 (...) { @@ -11094,7 +12328,7 @@ namespace cimg_library_suffixed { //! 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; + const size_t siz = safe_size(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(values); @@ -11112,6 +12346,60 @@ namespace cimg_library_suffixed { } else { _width = _height = _depth = _spectrum = 0; _is_shared = false; _data = 0; } } + //! Construct image from memory buffer with specified size and pixel ordering scheme. + template + CImg(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 char *const axes_order):_data(0),_is_shared(false) { + const size_t siz = safe_size(size_x,size_y,size_z,size_c); + if (values && siz) { + unsigned char s_code[4] = { 0,1,2,3 }, n_code[4] = {}; + for (unsigned int l = 0; axes_order[l]; ++l) { + int c = cimg::lowercase(axes_order[l]); + if (l>=4 || (c!='x' && c!='y' && c!='z' && c!='c')) { *s_code = 4; break; } + else { ++n_code[c%=4]; s_code[l] = c; } + } + if (*axes_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]); + int s0 = 0, s1 = 0, s2 = 0, s3 = 0; + const char *inv_order = 0; + switch (code) { + case 0x0123 : inv_order = "xyzc"; s0 = size_x; s1 = size_y; s2 = size_z; s3 = size_c; break; // xyzc + case 0x0132 : inv_order = "xyzc"; s0 = size_x; s1 = size_y; s2 = size_c; s3 = size_z; break; // xycz + case 0x0213 : inv_order = "xzyc"; s0 = size_x; s1 = size_z; s2 = size_y; s3 = size_c; break; // xzyc + case 0x0231 : inv_order = "xcyz"; s0 = size_x; s1 = size_z; s2 = size_c; s3 = size_y; break; // xzcy + case 0x0312 : inv_order = "xzcy"; s0 = size_x; s1 = size_c; s2 = size_y; s3 = size_z; break; // xcyz + case 0x0321 : inv_order = "xczy"; s0 = size_x; s1 = size_c; s2 = size_z; s3 = size_y; break; // xczy + case 0x1023 : inv_order = "yxzc"; s0 = size_y; s1 = size_x; s2 = size_z; s3 = size_c; break; // yxzc + case 0x1032 : inv_order = "yxcz"; s0 = size_y; s1 = size_x; s2 = size_c; s3 = size_z; break; // yxcz + case 0x1203 : inv_order = "zxyc"; s0 = size_y; s1 = size_z; s2 = size_x; s3 = size_c; break; // yzxc + case 0x1230 : inv_order = "cxyz"; s0 = size_y; s1 = size_z; s2 = size_c; s3 = size_x; break; // yzcx + case 0x1302 : inv_order = "zxcy"; s0 = size_y; s1 = size_c; s2 = size_x; s3 = size_z; break; // ycxz + case 0x1320 : inv_order = "cxzy"; s0 = size_y; s1 = size_c; s2 = size_z; s3 = size_x; break; // yczx + case 0x2013 : inv_order = "yzxc"; s0 = size_z; s1 = size_x; s2 = size_y; s3 = size_c; break; // zxyc + case 0x2031 : inv_order = "ycxz"; s0 = size_z; s1 = size_x; s2 = size_c; s3 = size_y; break; // zxcy + case 0x2103 : inv_order = "zyxc"; s0 = size_z; s1 = size_y; s2 = size_x; s3 = size_c; break; // zyxc + case 0x2130 : inv_order = "cyxz"; s0 = size_z; s1 = size_y; s2 = size_c; s3 = size_x; break; // zycx + case 0x2301 : inv_order = "zcxy"; s0 = size_z; s1 = size_c; s2 = size_x; s3 = size_y; break; // zcxy + case 0x2310 : inv_order = "czxy"; s0 = size_z; s1 = size_c; s2 = size_y; s3 = size_x; break; // zcyx + case 0x3012 : inv_order = "yzcx"; s0 = size_c; s1 = size_x; s2 = size_y; s3 = size_z; break; // cxyz + case 0x3021 : inv_order = "yczx"; s0 = size_c; s1 = size_x; s2 = size_z; s3 = size_y; break; // cxzy + case 0x3102 : inv_order = "zycx"; s0 = size_c; s1 = size_y; s2 = size_x; s3 = size_z; break; // cyxz + case 0x3120 : inv_order = "cyzx"; s0 = size_c; s1 = size_y; s2 = size_z; s3 = size_x; break; // cyzx + case 0x3201 : inv_order = "zcyx"; s0 = size_c; s1 = size_z; s2 = size_x; s3 = size_y; break; // czxy + case 0x3210 : inv_order = "czyx"; s0 = size_c; s1 = size_z; s2 = size_y; s3 = size_x; break; // czyx + } + CImg(values,s0,s1,s2,s3,true).get_permute_axes(inv_order).move_to(*this); + } else { + _width = _height = _depth = _spectrum = 0; _data = 0; + throw CImgArgumentException(_cimg_instance + "CImg(): Invalid specified axes order '%s'.", + cimg_instance, + axes_order); + } + } 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 @@ -11120,7 +12408,7 @@ namespace cimg_library_suffixed { \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 + - The recognition of the image file format by %CImg higlhy 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 from a float-valued image file). @@ -11270,10 +12558,10 @@ namespace cimg_library_suffixed { instead. \par Example \code - const CImg 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'). + const CImg 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 @@ -11305,7 +12593,7 @@ namespace cimg_library_suffixed { \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). + (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) { @@ -11318,6 +12606,7 @@ namespace cimg_library_suffixed { CImg(CImg&& img):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) { swap(img); } + CImg& operator=(CImg&& img) { if (_is_shared) return assign(img); return img.swap(*this); @@ -11340,17 +12629,17 @@ namespace cimg_library_suffixed { **/ CImg& 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; + const size_t siz = safe_size(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) + if (_is_shared) throw CImgArgumentException(_cimg_instance - "assign(): Invalid assignement request of shared instance from specified " + "assign(): Invalid assignment request of shared instance from specified " "image (%u,%u,%u,%u).", cimg_instance, size_x,size_y,size_z,size_c); - else { + else { delete[] _data; try { _data = new T[siz]; } catch (...) { _width = _height = _depth = _spectrum = 0; _data = 0; @@ -11383,7 +12672,7 @@ namespace cimg_library_suffixed { 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); + _CImg_stdarg(*this,value0,value1,safe_size(size_x,size_y,size_z,size_c),int); return *this; } @@ -11395,7 +12684,7 @@ namespace cimg_library_suffixed { 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); + _CImg_stdarg(*this,value0,value1,safe_size(size_x,size_y,size_z,size_c),double); return *this; } @@ -11416,7 +12705,7 @@ namespace cimg_library_suffixed { template CImg& 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; + const size_t siz = safe_size(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++); @@ -11426,7 +12715,7 @@ namespace cimg_library_suffixed { //! Construct image with specified size and initialize pixel values from a memory buffer \specialization. CImg& 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; + const size_t siz = safe_size(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); @@ -11466,22 +12755,30 @@ namespace cimg_library_suffixed { //! Construct image with specified size and initialize pixel values from a memory buffer \overloading. CImg& 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; + const size_t siz = safe_size(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 + 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(values); + } + _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c; _is_shared = true; + _data = const_cast(values); } return *this; } + //! Construct image from memory buffer with specified size and pixel ordering scheme. + template + CImg& 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 char *const axes_order) { + CImg(values,size_x,size_y,size_z,size_c,axes_order).move_to(*this); + } + //! Construct image from reading an image file \inplace. /** In-place version of the constructor CImg(const char*). @@ -11581,9 +12878,9 @@ namespace cimg_library_suffixed { designed to be instantaneous when \c T and \c t are the same. \par Example \code - CImg 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. + CImg 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 @@ -11608,15 +12905,15 @@ namespace cimg_library_suffixed { \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 + - When optional parameter \c pos is omitted, the image instance is transferred 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 list; // Construct an empty image list. - CImg img("reference.jpg"); // Read image from filename. - img.move_to(list); // Transfer image content as a new item in the list (no buffer copy). + CImgList list; // Construct an empty image list + CImg 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 @@ -11636,7 +12933,7 @@ namespace cimg_library_suffixed { \code CImg img1("lena.jpg"), img2("milla.jpg"); - img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena'. + img1.swap(img2); // Now, 'img1' is 'milla' and 'img2' is 'lena' \endcode **/ CImg& swap(CImg& img) { @@ -11687,23 +12984,23 @@ namespace cimg_library_suffixed { (width() - 1,height() - 1,depth() - 1,spectrum() - 1). - 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 img(x,y,c) instead of + For instance, pixels of a 2D image (depth() equal to \c 1) can be accessed by img(x,y,c) instead of img(x,y,0,c). \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 + For debugging 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 img(100,100,1,3,0); // Construct a 100x100x1x3 (color) image with pixels set to '0'. + CImg 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). + 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. + 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 @@ -11805,17 +13102,17 @@ namespace cimg_library_suffixed { } #endif - //! Implicitely cast an image into a \c T*. + //! Implicitly cast an image into a \c T*. /** - Implicitely cast a \c CImg instance into a \c T* or \c const \c T* pointer, whether the image instance + Implicitly cast a \c CImg 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 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. + CImg 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."); } } @@ -11823,15 +13120,15 @@ namespace cimg_library_suffixed { - It also allows to use brackets to access pixel values, without need for a \c CImg::operator[](), e.g. \code CImg 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). + 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. + //! Implicitly cast an image into a \c T* \const. operator const T*() const { return _data; } @@ -11845,9 +13142,9 @@ namespace cimg_library_suffixed { - The \c value may be casted to pixel type \c T if necessary. \par Example \code - CImg 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'). + CImg 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& operator=(const T& value) { @@ -11866,10 +13163,10 @@ namespace cimg_library_suffixed { replace the image instance. The image size is modified if necessary. \par Example \code - CImg 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). + CImg img1(100,100), img2(img1), img3(img1); // Declare 3 scalar images 100x100 with uninitialized 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 @@ -11878,7 +13175,7 @@ namespace cimg_library_suffixed { const unsigned int omode = cimg::exception_mode(); cimg::exception_mode(0); try { - _fill(expression,true,true,0,0,"operator=",0); + _fill(expression,true,3,0,"operator=",0,0); } catch (CImgException&) { cimg::exception_mode(omode); load(expression); @@ -11919,20 +13216,20 @@ namespace cimg_library_suffixed { For instance, adding \c 0.2 to a \c CImg is possible but does nothing indeed. - Overflow values are treated as with standard C++ numeric types. For instance, \code - CImg img(100,100,1,1,255); // Construct a 100x100 image with pixel values '255'. - img+=1; // Add '1' to each pixels -> Overflow. + CImg 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 img1("reference.jpg"); // Load a 8-bits RGB image (values in [0,255]). - CImg 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 img3 = (img1 + 100).cut(0,255); // Do the same in a more simple and elegant way. + CImg img1("reference.jpg"); // Load a 8-bits RGB image (values in [0,255]) + CImg 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 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 @@ -11940,8 +13237,7 @@ namespace cimg_library_suffixed { template CImg& 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); + cimg_openmp_for(*this,*ptr + value,524288); return *this; } @@ -11954,7 +13250,7 @@ namespace cimg_library_suffixed { instead of assigning them. **/ CImg& operator+=(const char *const expression) { - return *this+=(+*this)._fill(expression,true,true,0,0,"operator+=",this); + return *this+=(+*this)._fill(expression,true,3,0,"operator+=",this,0); } //! In-place addition operator. @@ -11972,8 +13268,8 @@ namespace cimg_library_suffixed { CImg img1("reference.jpg"); // Load a RGB color image (img1.spectrum()==3) // Construct a scalar shading (img2.spectrum()==1). const CImg 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. + 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 @@ -12000,8 +13296,7 @@ namespace cimg_library_suffixed { **/ CImg& operator++() { if (is_empty()) return *this; - cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=524288)) - cimg_rof(*this,ptrd,T) ++*ptrd; + cimg_openmp_for(*this,*ptr + 1,524288); return *this; } @@ -12060,29 +13355,28 @@ namespace cimg_library_suffixed { return CImg<_cimg_Tt>(*this,false)+=img; } - //! In-place substraction operator. + //! In-place subtraction operator. /** - Similar to operator+=(const t), except that it performs a substraction instead of an addition. + Similar to operator+=(const t), except that it performs a subtraction instead of an addition. **/ template CImg& 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); + cimg_openmp_for(*this,*ptr - value,524288); return *this; } - //! In-place substraction operator. + //! In-place subtraction operator. /** - Similar to operator+=(const char*), except that it performs a substraction instead of an addition. + Similar to operator+=(const char*), except that it performs a subtraction instead of an addition. **/ CImg& operator-=(const char *const expression) { - return *this-=(+*this)._fill(expression,true,true,0,0,"operator-=",this); + return *this-=(+*this)._fill(expression,true,3,0,"operator-=",this,0); } - //! In-place substraction operator. + //! In-place subtraction operator. /** - Similar to operator+=(const CImg&), except that it performs a substraction instead of an addition. + Similar to operator+=(const CImg&), except that it performs a subtraction instead of an addition. **/ template CImg& operator-=(const CImg& img) { @@ -12104,8 +13398,7 @@ namespace cimg_library_suffixed { **/ CImg& 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; + cimg_openmp_for(*this,*ptr - 1,524288); return *this; } @@ -12127,8 +13420,8 @@ namespace cimg_library_suffixed { \par Example \code const CImg - img1("reference.jpg"), // Load a RGB color image. - img2 = -img1; // Compute its opposite (in '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 @@ -12137,7 +13430,7 @@ namespace cimg_library_suffixed { return CImg(_width,_height,_depth,_spectrum,(T)0)-=*this; } - //! Substraction operator. + //! Subtraction 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. @@ -12147,7 +13440,7 @@ namespace cimg_library_suffixed { return CImg<_cimg_Tt>(*this,false)-=value; } - //! Substraction operator. + //! Subtraction 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. @@ -12156,7 +13449,7 @@ namespace cimg_library_suffixed { return CImg(*this,false)-=expression; } - //! Substraction operator. + //! Subtraction operator. /** Similar to operator-=(const CImg&), 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. @@ -12173,8 +13466,7 @@ namespace cimg_library_suffixed { template CImg& 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); + cimg_openmp_for(*this,*ptr * value,262144); return *this; } @@ -12183,7 +13475,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except that it performs a multiplication instead of an addition. **/ CImg& operator*=(const char *const expression) { - return mul((+*this)._fill(expression,true,true,0,0,"operator*=",this)); + return mul((+*this)._fill(expression,true,3,0,"operator*=",this,0)); } //! In-place multiplication operator. @@ -12197,9 +13489,9 @@ namespace cimg_library_suffixed { - The size of the image instance can be modified by this operator. \par Example \code - CImg A(2,2,1,1, 1,2,3,4); // Construct 2x2 matrix A = [1,2;3,4]. - const CImg X(1,2,1,1, 1,2); // Construct 1x2 vector X = [1;2]. - A*=X; // Assign matrix multiplication A*X to 'A'. + CImg A(2,2,1,1, 1,2,3,4); // Construct 2x2 matrix A = [1,2;3,4] + const CImg 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 **/ @@ -12234,22 +13526,199 @@ namespace cimg_library_suffixed { **/ template CImg<_cimg_Tt> operator*(const CImg& img) const { + typedef _cimg_Ttdouble Ttdouble; + typedef _cimg_Tt Tt; 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)", + "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; + CImg res(img._width,_height); + + // Check for common cases to optimize. + if (img._width==1) { // Matrix * Vector + if (_height==1) switch (_width) { // Vector^T * Vector + case 1 : + res[0] = (Tt)((Ttdouble)_data[0]*img[0]); + return res; + case 2 : + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[1]); + return res; + case 3 : + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[1] + + (Ttdouble)_data[2]*img[2]); + return res; + case 4 : + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[1] + + (Ttdouble)_data[2]*img[2] + (Ttdouble)_data[3]*img[3]); + return res; + default : { + Ttdouble val = 0; + cimg_pragma_openmp(parallel for reduction(+:val) cimg_openmp_if_size(size(),4096)) + cimg_forX(*this,i) val+=(Ttdouble)_data[i]*img[i]; + res[0] = val; + return res; + } + } else if (_height==_width) switch (_width) { // Square_matrix * Vector + case 2 : // 2x2_matrix * Vector + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[1]); + res[1] = (Tt)((Ttdouble)_data[2]*img[0] + (Ttdouble)_data[3]*img[1]); + return res; + case 3 : // 3x3_matrix * Vector + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[1] + + (Ttdouble)_data[2]*img[2]); + res[1] = (Tt)((Ttdouble)_data[3]*img[0] + (Ttdouble)_data[4]*img[1] + + (Ttdouble)_data[5]*img[2]); + res[2] = (Tt)((Ttdouble)_data[6]*img[0] + (Ttdouble)_data[7]*img[1] + + (Ttdouble)_data[8]*img[2]); + return res; + case 4 : // 4x4_matrix * Vector + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[1] + + (Ttdouble)_data[2]*img[2] + (Ttdouble)_data[3]*img[3]); + res[1] = (Tt)((Ttdouble)_data[4]*img[0] + (Ttdouble)_data[5]*img[1] + + (Ttdouble)_data[6]*img[2] + (Ttdouble)_data[7]*img[3]); + res[2] = (Tt)((Ttdouble)_data[8]*img[0] + (Ttdouble)_data[9]*img[1] + + (Ttdouble)_data[10]*img[2] + (Ttdouble)_data[11]*img[3]); + res[3] = (Tt)((Ttdouble)_data[12]*img[0] + (Ttdouble)_data[13]*img[1] + + (Ttdouble)_data[14]*img[2] + (Ttdouble)_data[15]*img[3]); + return res; + } + } else if (_height==_width) { + if (img._height==img._width) switch (_width) { // Square_matrix * Square_matrix + case 2 : // 2x2_matrix * 2x2_matrix + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[2]); + res[1] = (Tt)((Ttdouble)_data[0]*img[1] + (Ttdouble)_data[1]*img[3]); + res[2] = (Tt)((Ttdouble)_data[2]*img[0] + (Ttdouble)_data[3]*img[2]); + res[3] = (Tt)((Ttdouble)_data[2]*img[1] + (Ttdouble)_data[3]*img[3]); + return res; + case 3 : // 3x3_matrix * 3x3_matrix + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[3] + + (Ttdouble)_data[2]*img[6]); + res[1] = (Tt)((Ttdouble)_data[0]*img[1] + (Ttdouble)_data[1]*img[4] + + (Ttdouble)_data[2]*img[7]); + res[2] = (Tt)((Ttdouble)_data[0]*img[2] + (Ttdouble)_data[1]*img[5] + + (Ttdouble)_data[2]*img[8]); + res[3] = (Tt)((Ttdouble)_data[3]*img[0] + (Ttdouble)_data[4]*img[3] + + (Ttdouble)_data[5]*img[6]); + res[4] = (Tt)((Ttdouble)_data[3]*img[1] + (Ttdouble)_data[4]*img[4] + + (Ttdouble)_data[5]*img[7]); + res[5] = (Tt)((Ttdouble)_data[3]*img[2] + (Ttdouble)_data[4]*img[5] + + (Ttdouble)_data[5]*img[8]); + res[6] = (Tt)((Ttdouble)_data[6]*img[0] + (Ttdouble)_data[7]*img[3] + + (Ttdouble)_data[8]*img[6]); + res[7] = (Tt)((Ttdouble)_data[6]*img[1] + (Ttdouble)_data[7]*img[4] + + (Ttdouble)_data[8]*img[7]); + res[8] = (Tt)((Ttdouble)_data[6]*img[2] + (Ttdouble)_data[7]*img[5] + + (Ttdouble)_data[8]*img[8]); + return res; + case 4 : // 4x4_matrix * 4x4_matrix + res[0] = (Tt)((Ttdouble)_data[0]*img[0] + (Ttdouble)_data[1]*img[4] + + (Ttdouble)_data[2]*img[8] + (Ttdouble)_data[3]*img[12]); + res[1] = (Tt)((Ttdouble)_data[0]*img[1] + (Ttdouble)_data[1]*img[5] + + (Ttdouble)_data[2]*img[9] + (Ttdouble)_data[3]*img[13]); + res[2] = (Tt)((Ttdouble)_data[0]*img[2] + (Ttdouble)_data[1]*img[6] + + (Ttdouble)_data[2]*img[10] + (Ttdouble)_data[3]*img[14]); + res[3] = (Tt)((Ttdouble)_data[0]*img[3] + (Ttdouble)_data[1]*img[7] + + (Ttdouble)_data[2]*img[11] + (Ttdouble)_data[3]*img[15]); + res[4] = (Tt)((Ttdouble)_data[4]*img[0] + (Ttdouble)_data[5]*img[4] + + (Ttdouble)_data[6]*img[8] + (Ttdouble)_data[7]*img[12]); + res[5] = (Tt)((Ttdouble)_data[4]*img[1] + (Ttdouble)_data[5]*img[5] + + (Ttdouble)_data[6]*img[9] + (Ttdouble)_data[7]*img[13]); + res[6] = (Tt)((Ttdouble)_data[4]*img[2] + (Ttdouble)_data[5]*img[6] + + (Ttdouble)_data[6]*img[10] + (Ttdouble)_data[7]*img[14]); + res[7] = (Tt)((Ttdouble)_data[4]*img[3] + (Ttdouble)_data[5]*img[7] + + (Ttdouble)_data[6]*img[11] + (Ttdouble)_data[7]*img[15]); + res[8] = (Tt)((Ttdouble)_data[8]*img[0] + (Ttdouble)_data[9]*img[4] + + (Ttdouble)_data[10]*img[8] + (Ttdouble)_data[11]*img[12]); + res[9] = (Tt)((Ttdouble)_data[8]*img[1] + (Ttdouble)_data[9]*img[5] + + (Ttdouble)_data[10]*img[9] + (Ttdouble)_data[11]*img[13]); + res[10] = (Tt)((Ttdouble)_data[8]*img[2] + (Ttdouble)_data[9]*img[6] + + (Ttdouble)_data[10]*img[10] + (Ttdouble)_data[11]*img[14]); + res[11] = (Tt)((Ttdouble)_data[8]*img[3] + (Ttdouble)_data[9]*img[7] + + (Ttdouble)_data[10]*img[11] + (Ttdouble)_data[11]*img[15]); + res[12] = (Tt)((Ttdouble)_data[12]*img[0] + (Ttdouble)_data[13]*img[4] + + (Ttdouble)_data[14]*img[8] + (Ttdouble)_data[15]*img[12]); + res[13] = (Tt)((Ttdouble)_data[12]*img[1] + (Ttdouble)_data[13]*img[5] + + (Ttdouble)_data[14]*img[9] + (Ttdouble)_data[15]*img[13]); + res[14] = (Tt)((Ttdouble)_data[12]*img[2] + (Ttdouble)_data[13]*img[6] + + (Ttdouble)_data[14]*img[10] + (Ttdouble)_data[15]*img[14]); + res[15] = (Tt)((Ttdouble)_data[12]*img[3] + (Ttdouble)_data[13]*img[7] + + (Ttdouble)_data[14]*img[11] + (Ttdouble)_data[15]*img[15]); + return res; + } else switch (_width) { // Square_matrix * Matrix + case 2 : { // 2x2_matrix * Matrix + const t *const ps0 = img.data(), *const ps1 = img.data(0,1); + Tt *const pd0 = res.data(), *const pd1 = res.data(0,1); + const Ttdouble + a0 = (Ttdouble)_data[0], a1 = (Ttdouble)_data[1], + a2 = (Ttdouble)_data[2], a3 = (Ttdouble)_data[3]; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(img.width(),4096)) + cimg_forX(img,i) { + const Ttdouble x = (Ttdouble)ps0[i], y = (Ttdouble)ps1[i]; + pd0[i] = (Tt)(a0*x + a1*y); + pd1[i] = (Tt)(a2*x + a3*y); + } + return res; + } + case 3 : { // 3x3_matrix * Matrix + const t *const ps0 = img.data(), *const ps1 = img.data(0,1), *const ps2 = img.data(0,2); + Tt *const pd0 = res.data(), *const pd1 = res.data(0,1), *const pd2 = res.data(0,2); + const Ttdouble + a0 = (Ttdouble)_data[0], a1 = (Ttdouble)_data[1], a2 = (Ttdouble)_data[2], + a3 = (Ttdouble)_data[3], a4 = (Ttdouble)_data[4], a5 = (Ttdouble)_data[5], + a6 = (Ttdouble)_data[6], a7 = (Ttdouble)_data[7], a8 = (Ttdouble)_data[8]; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(img.width(),1024)) + cimg_forX(img,i) { + const Ttdouble x = (Ttdouble)ps0[i], y = (Ttdouble)ps1[i], z = (Ttdouble)ps2[i]; + pd0[i] = (Tt)(a0*x + a1*y + a2*z); + pd1[i] = (Tt)(a3*x + a4*y + a5*z); + pd2[i] = (Tt)(a6*x + a7*y + a8*z); + } + return res; + } + case 4 : { // 4x4_matrix * Matrix + const t + *const ps0 = img.data(), *const ps1 = img.data(0,1), + *const ps2 = img.data(0,2), *const ps3 = img.data(0,3); + Tt + *const pd0 = res.data(), *const pd1 = res.data(0,1), + *const pd2 = res.data(0,2), *const pd3 = res.data(0,3); + const Ttdouble + a0 = (Ttdouble)_data[0], a1 = (Ttdouble)_data[1], a2 = (Ttdouble)_data[2], a3 = (Ttdouble)_data[3], + a4 = (Ttdouble)_data[4], a5 = (Ttdouble)_data[5], a6 = (Ttdouble)_data[6], a7 = (Ttdouble)_data[7], + a8 = (Ttdouble)_data[8], a9 = (Ttdouble)_data[9], a10 = (Ttdouble)_data[10], a11 = (Ttdouble)_data[11], + a12 = (Ttdouble)_data[12], a13 = (Ttdouble)_data[13], a14 = (Ttdouble)_data[14], + a15 = (Ttdouble)_data[15]; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(img.width(),512)) + cimg_forX(img,i) { + const Ttdouble x = (Ttdouble)ps0[i], y = (Ttdouble)ps1[i], z = (Ttdouble)ps2[i], c = (Ttdouble)ps3[i]; + pd0[i] = (Tt)(a0*x + a1*y + a2*z + a3*c); + pd1[i] = (Tt)(a4*x + a5*y + a6*z + a7*c); + pd2[i] = (Tt)(a8*x + a9*y + a10*z + a11*c); + pd3[i] = (Tt)(a12*x + a13*y + a14*z + a15*c); + } + return res; + } + } + } + + // Fallback to generic version. +#if cimg_use_openmp!=0 + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(size()>(cimg_openmp_sizefactor)*1024 && + img.size()>(cimg_openmp_sizefactor)*1024)) + cimg_forXY(res,i,j) { + Ttdouble value = 0; + cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); + res(i,j) = (Tt)value; } #else - _cimg_Tt *ptrd = res._data; + 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; + Ttdouble value = 0; + cimg_forX(*this,k) value+=(*this)(k,j)*img(i,k); + *(ptrd++) = (Tt)value; } #endif return res; @@ -12262,8 +13731,7 @@ namespace cimg_library_suffixed { template CImg& 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); + cimg_openmp_for(*this,*ptr / value,32768); return *this; } @@ -12272,7 +13740,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except that it performs a division instead of an addition. **/ CImg& operator/=(const char *const expression) { - return div((+*this)._fill(expression,true,true,0,0,"operator/=",this)); + return div((+*this)._fill(expression,true,3,0,"operator/=",this,0)); } //! In-place division operator. @@ -12327,8 +13795,7 @@ namespace cimg_library_suffixed { template CImg& 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); + cimg_openmp_for(*this,cimg::mod(*ptr,(T)value),16384); return *this; } @@ -12337,7 +13804,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except that it performs a modulo operation instead of an addition. **/ CImg& operator%=(const char *const expression) { - return *this%=(+*this)._fill(expression,true,true,0,0,"operator%=",this); + return *this%=(+*this)._fill(expression,true,3,0,"operator%=",this,0); } //! In-place modulo operator. @@ -12394,8 +13861,7 @@ namespace cimg_library_suffixed { template CImg& 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); + cimg_openmp_for(*this,(longT)*ptr & (longT)value,32768); return *this; } @@ -12404,7 +13870,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition. **/ CImg& operator&=(const char *const expression) { - return *this&=(+*this)._fill(expression,true,true,0,0,"operator&=",this); + return *this&=(+*this)._fill(expression,true,3,0,"operator&=",this,0); } //! In-place bitwise AND operator. @@ -12419,8 +13885,8 @@ namespace cimg_library_suffixed { 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 CImg& 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); + cimg_openmp_for(*this,(longT)*ptr | (longT)value,32768); return *this; } @@ -12471,7 +13936,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition. **/ CImg& operator|=(const char *const expression) { - return *this|=(+*this)._fill(expression,true,true,0,0,"operator|=",this); + return *this|=(+*this)._fill(expression,true,3,0,"operator|=",this,0); } //! In-place bitwise OR operator. @@ -12486,8 +13951,8 @@ namespace cimg_library_suffixed { 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 CImg& 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); + cimg_openmp_for(*this,(longT)*ptr ^ (longT)value,32768); return *this; } @@ -12542,7 +14006,7 @@ namespace cimg_library_suffixed { - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead. **/ CImg& operator^=(const char *const expression) { - return *this^=(+*this)._fill(expression,true,true,0,0,"operator^=",this); + return *this^=(+*this)._fill(expression,true,3,0,"operator^=",this,0); } //! In-place bitwise XOR operator. @@ -12559,8 +14023,8 @@ namespace cimg_library_suffixed { 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 CImg& 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); + cimg_openmp_for(*this,((longT)*ptr) << (int)value,65536); return *this; } @@ -12611,7 +14074,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition. **/ CImg& operator<<=(const char *const expression) { - return *this<<=(+*this)._fill(expression,true,true,0,0,"operator<<=",this); + return *this<<=(+*this)._fill(expression,true,3,0,"operator<<=",this,0); } //! In-place bitwise left shift operator. @@ -12669,8 +14132,7 @@ namespace cimg_library_suffixed { template CImg& 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); + cimg_openmp_for(*this,((longT)*ptr) >> (int)value,65536); return *this; } @@ -12679,7 +14141,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition. **/ CImg& operator>>=(const char *const expression) { - return *this>>=(+*this)._fill(expression,true,true,0,0,"operator>>=",this); + return *this>>=(+*this)._fill(expression,true,3,0,"operator>>=",this,0); } //! In-place bitwise right shift operator. @@ -12761,13 +14223,13 @@ namespace cimg_library_suffixed { \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); + return *this==(+*this)._fill(expression,true,3,0,"operator==",this,0); } //! 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. + Return \c true if the image instance and the input image \c img have the same pixel values, + even if the dimensions of the two images do not match. It returns \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==() @@ -12776,9 +14238,9 @@ namespace cimg_library_suffixed { pixel types \c T and \c t. \par Example \code - const CImg img1(1,3,1,1, 0,1,2); // Construct a 1x3 vector [0;1;2] (with 'float' pixel values). - const CImg 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. + const CImg img1(1,3,1,1, 0,1,2); // Construct a 1x3 vector [0;1;2] (with 'float' pixel values) + const CImg 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 @@ -12851,8 +14313,8 @@ namespace cimg_library_suffixed { img1("reference.jpg"), img2 = img1.get_mirror('x'), img3 = img2.get_blur(5); - const CImgList 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'. + const CImgList 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 **/ @@ -12878,15 +14340,15 @@ namespace cimg_library_suffixed { //! Split image along specified axis. /** - Return a new list of images (\c CImgList instance) containing the splitted components + Return a new list of images (\c CImgList instance) containing the split 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 img("reference.jpg"); // Load a RGB color image. - const CImgList list = (img<'c'); // Get a list of its three R,G,B channels. + const CImg img("reference.jpg"); // Load a RGB color image + const CImgList list = (img<'c'); // Get a list of its three R,G,B channels (img,list).display(); \endcode \image html ref_operator_less.jpg @@ -12907,7 +14369,7 @@ namespace cimg_library_suffixed { 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"). + - The returned string does not contain any spaces. - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. **/ static const char* pixel_type() { @@ -12950,7 +14412,7 @@ namespace cimg_library_suffixed { 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 + - 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 @@ -12991,8 +14453,8 @@ namespace cimg_library_suffixed { size()*sizeof(T). \par Example \code - const CImg img(100,100,1,3); // Construct new 100x100 color image. - if (img.size()==30000) // Test succeeds. + const CImg 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 @@ -13070,9 +14532,9 @@ namespace cimg_library_suffixed { Thus, this method has the same properties as operator()(unsigned int,unsigned int,unsigned int,unsigned int). \par Example \code - const CImg 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. + const CImg 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 { @@ -13105,7 +14567,7 @@ namespace cimg_library_suffixed { Use it mainly as a strict upper bound for a CImg::iterator. \par Example \code - CImg img(100,100,1,3); // Define a 100x100 RGB color image. + CImg img(100,100,1,3); // Define a 100x100 RGB color image // 'img.end()' used below as an upper bound for the iterator. for (CImg::iterator it = img.begin(); it=0?0:1), nx = x + 1; + x = (int)fx - (fx>=0?0:1), nx = x + 1; const float dx = fx - x; const Tfloat @@ -13501,6 +14963,30 @@ namespace cimg_library_suffixed { return Ic + dx*(In - Ic); } + //! Return pixel value, using linear interpolation and periodic boundary conditions for the X-coordinate. + Tfloat linear_atX_p(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_p(): Empty instance.", + cimg_instance); + + return _linear_atX_p(fx,y,z,c); + } + + Tfloat _linear_atX_p(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::mod(fx,_width - 0.5f); + const unsigned int + x = (unsigned int)nfx; + const float + dx = nfx - x; + const unsigned int + nx = cimg::mod(x + 1,_width); + 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 @@ -13516,7 +15002,7 @@ namespace cimg_library_suffixed { 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 Icc + (Inc - Icc + (Icc + Inn - Icn - Inc)*dy)*dx + (Icn - Icc)*dy; } //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X and Y-coordinates. @@ -13552,7 +15038,36 @@ namespace cimg_library_suffixed { 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 Icc + (Inc - Icc + (Icc + Inn - Icn - Inc)*dy)*dx + (Icn - Icc)*dy; + } + + //! Return pixel value, using linear interpolation and periodic boundary conditions for the X and Y-coordinates. + Tfloat linear_atXY_p(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "linear_atXY_p(): Empty instance.", + cimg_instance); + + return _linear_atXY_p(fx,fy,z,c); + } + + Tfloat _linear_atXY_p(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::mod(fx,_width - 0.5f), + nfy = cimg::mod(fy,_height - 0.5f); + const unsigned int + x = (unsigned int)nfx, + y = (unsigned int)nfy; + const float + dx = nfx - x, + dy = nfy - y; + const unsigned int + nx = cimg::mod(x + 1,_width), + ny = cimg::mod(y + 1,_height); + 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 + (Inc - Icc + (Icc + Inn - Icn - Inc)*dy)*dx + (Icn - Icc)*dy; } //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for the X,Y and Z-coordinates. @@ -13575,13 +15090,13 @@ namespace cimg_library_suffixed { 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); + (Incc - Iccc + + (Iccc + Innc - Icnc - Incc + + (Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)*dz)*dy + + (Iccc + Incn - Iccn - Incc)*dz)*dx + + (Icnc - Iccc + + (Iccc + Icnn - Iccn - Icnc)*dz)*dy + + (Iccn - Iccc)*dz; } //! Return pixel value, using linear interpolation and Neumann boundary conditions for the X,Y and Z-coordinates. @@ -13624,13 +15139,55 @@ namespace cimg_library_suffixed { 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); + (Incc - Iccc + + (Iccc + Innc - Icnc - Incc + + (Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)*dz)*dy + + (Iccc + Incn - Iccn - Incc)*dz)*dx + + (Icnc - Iccc + + (Iccc + Icnn - Iccn - Icnc)*dz)*dy + + (Iccn - Iccc)*dz; + } + + //! Return pixel value, using linear interpolation and periodic boundary conditions for the X,Y and Z-coordinates. + Tfloat linear_atXYZ_p(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_p(): Empty instance.", + cimg_instance); + + return _linear_atXYZ_p(fx,fy,fz,c); + } + + Tfloat _linear_atXYZ_p(const float fx, const float fy=0, const float fz=0, const int c=0) const { + const float + nfx = cimg::mod(fx,_width - 0.5f), + nfy = cimg::mod(fy,_height - 0.5f), + nfz = cimg::mod(fz,_depth - 0.5f); + 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 = cimg::mod(x + 1,_width), + ny = cimg::mod(y + 1,_height), + nz = cimg::mod(z + 1,_depth); + 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 + + (Incc - Iccc + + (Iccc + Innc - Icnc - Incc + + (Iccn + Innn + Icnc + Incc - Icnn - Incn - Iccc - Innc)*dz)*dy + + (Iccc + Incn - Iccn - Incc)*dz)*dx + + (Icnc - Iccc + + (Iccc + Icnn - Iccn - Icnc)*dz)*dy + + (Iccn - Iccc)*dz; } //! Return pixel value, using linear interpolation and Dirichlet boundary conditions for all X,Y,Z,C-coordinates. @@ -13743,6 +15300,65 @@ namespace cimg_library_suffixed { dc*(Icccn - Icccc); } + //! Return pixel value, using linear interpolation and periodic boundary conditions for all X,Y,Z and C-coordinates. + Tfloat linear_atXYZC_p(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_p(): Empty instance.", + cimg_instance); + + return _linear_atXYZC_p(fx,fy,fz,fc); + } + + Tfloat _linear_atXYZC_p(const float fx, const float fy=0, const float fz=0, const float fc=0) const { + const float + nfx = cimg::mod(fx,_width - 0.5f), + nfy = cimg::mod(fy,_height - 0.5f), + nfz = cimg::mod(fz,_depth - 0.5f), + nfc = cimg::mod(fc,_spectrum - 0.5f); + 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 = cimg::mod(x + 1,_width), + ny = cimg::mod(y + 1,_height), + nz = cimg::mod(z + 1,_depth), + nc = cimg::mod(c + 1,_spectrum); + 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), @@ -13776,7 +15392,7 @@ namespace cimg_library_suffixed { Similar to cubic_atX(float,int,int,int,const T) const, except that the return value is clamped to stay in the min/max range of the datatype \c T. **/ - T cubic_cut_atX(const float fx, const int y, const int z, const int c, const T& out_value) const { + T cubic_atX_c(const float fx, const int y, const int z, const int c, const T& out_value) const { return cimg::type::cut(cubic_atX(fx,y,z,c,out_value)); } @@ -13807,7 +15423,7 @@ namespace cimg_library_suffixed { 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); + nfx = cimg::type::is_nan(fx)?0:cimg::cut(fx,0,width() - 1); const int x = (int)nfx; const float @@ -13825,14 +15441,46 @@ namespace cimg_library_suffixed { Similar to cubic_atX(float,int,int,int) const, except that the return value is clamped to stay in the min/max range of the datatype \c T. **/ - T cubic_cut_atX(const float fx, const int y, const int z, const int c) const { + T cubic_atX_c(const float fx, const int y, const int z, const int c) const { return cimg::type::cut(cubic_atX(fx,y,z,c)); } - T _cubic_cut_atX(const float fx, const int y, const int z, const int c) const { + T _cubic_atX_c(const float fx, const int y, const int z, const int c) const { return cimg::type::cut(_cubic_atX(fx,y,z,c)); } + //! Return pixel value, using cubic interpolation and periodic boundary conditions for the X-coordinate. + Tfloat cubic_atX_p(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_p(): Empty instance.", + cimg_instance); + return _cubic_atX_p(fx,y,z,c); + } + + Tfloat _cubic_atX_p(const float fx, const int y=0, const int z=0, const int c=0) const { + const float + nfx = cimg::type::is_nan(fx)?0:cimg::mod(fx,_width - 0.5f); + const int + x = (int)nfx; + const float + dx = nfx - x; + const int + px = cimg::mod(x - 1,width()), nx = cimg::mod(x + 1,width()), ax = cimg::mod(x + 2,width()); + 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)); + } + + T cubic_atX_pc(const float fx, const int y, const int z, const int c) const { + return cimg::type::cut(cubic_atX_p(fx,y,z,c)); + } + + T _cubic_atX_pc(const float fx, const int y, const int z, const int c) const { + return cimg::type::cut(_cubic_atX_p(fx,y,z,c)); + } + //! 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 @@ -13864,7 +15512,7 @@ namespace cimg_library_suffixed { Similar to cubic_atXY(float,float,int,int,const T) const, except that the return value is clamped to stay in the min/max range of the datatype \c T. **/ - T cubic_cut_atXY(const float fx, const float fy, const int z, const int c, const T& out_value) const { + T cubic_atXY_c(const float fx, const float fy, const int z, const int c, const T& out_value) const { return cimg::type::cut(cubic_atXY(fx,fy,z,c,out_value)); } @@ -13886,13 +15534,13 @@ namespace cimg_library_suffixed { 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); + nfx = cimg::type::is_nan(fx)?0:cimg::cut(fx,0,width() - 1), + nfy = cimg::type::is_nan(fy)?0: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; + px = x - 1<0?0:x - 1, nx = dx<=0?x:x + 1, ax = x + 2>=width()?width() - 1:x + 2, + py = y - 1<0?0:y - 1, ny = dy<=0?y:y + 1, 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), @@ -13914,14 +15562,56 @@ namespace cimg_library_suffixed { Similar to cubic_atXY(float,float,int,int) const, except that the return value is clamped to stay in the min/max range of the datatype \c T. **/ - T cubic_cut_atXY(const float fx, const float fy, const int z, const int c) const { + T cubic_atXY_c(const float fx, const float fy, const int z, const int c) const { return cimg::type::cut(cubic_atXY(fx,fy,z,c)); } - T _cubic_cut_atXY(const float fx, const float fy, const int z, const int c) const { + T _cubic_atXY_c(const float fx, const float fy, const int z, const int c) const { return cimg::type::cut(_cubic_atXY(fx,fy,z,c)); } + //! Return pixel value, using cubic interpolation and periodic boundary conditions for the X and Y-coordinates. + Tfloat cubic_atXY_p(const float fx, const float fy, const int z=0, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXY_p(): Empty instance.", + cimg_instance); + return _cubic_atXY_p(fx,fy,z,c); + } + + Tfloat _cubic_atXY_p(const float fx, const float fy, const int z=0, const int c=0) const { + const float + nfx = cimg::type::is_nan(fx)?0:cimg::mod(fx,_width - 0.5f), + nfy = cimg::type::is_nan(fy)?0:cimg::mod(fy,_height - 0.5f); + const int x = (int)nfx, y = (int)nfy; + const float dx = nfx - x, dy = nfy - y; + const int + px = cimg::mod(x - 1,width()), nx = cimg::mod(x + 1,width()), ax = cimg::mod(x + 2,width()), + py = cimg::mod(y - 1,height()), ny = cimg::mod(y + 1,height()), ay = cimg::mod(y + 2,height()); + 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)); + } + + T cubic_atXY_pc(const float fx, const float fy, const int z, const int c) const { + return cimg::type::cut(cubic_atXY_p(fx,fy,z,c)); + } + + T _cubic_atXY_pc(const float fx, const float fy, const int z, const int c) const { + return cimg::type::cut(_cubic_atXY_p(fx,fy,z,c)); + } + //! 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 @@ -14014,7 +15704,7 @@ namespace cimg_library_suffixed { Similar to cubic_atXYZ(float,float,float,int,const T) const, except that the return value is clamped to stay in the min/max range of the datatype \c T. **/ - T cubic_cut_atXYZ(const float fx, const float fy, const float fz, const int c, const T& out_value) const { + T cubic_atXYZ_c(const float fx, const float fy, const float fz, const int c, const T& out_value) const { return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c,out_value)); } @@ -14036,9 +15726,9 @@ namespace cimg_library_suffixed { 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); + nfx = cimg::type::is_nan(fx)?0:cimg::cut(fx,0,width() - 1), + nfy = cimg::type::is_nan(fy)?0:cimg::cut(fy,0,height() - 1), + nfz = cimg::type::is_nan(fz)?0: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 @@ -14126,14 +15816,125 @@ namespace cimg_library_suffixed { Similar to cubic_atXYZ(float,float,float,int) const, except that the return value is clamped to stay in the min/max range of the datatype \c T. **/ - T cubic_cut_atXYZ(const float fx, const float fy, const float fz, const int c) const { + T cubic_atXYZ_c(const float fx, const float fy, const float fz, const int c) const { return cimg::type::cut(cubic_atXYZ(fx,fy,fz,c)); } - T _cubic_cut_atXYZ(const float fx, const float fy, const float fz, const int c) const { + T _cubic_atXYZ_c(const float fx, const float fy, const float fz, const int c) const { return cimg::type::cut(_cubic_atXYZ(fx,fy,fz,c)); } + //! 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_p(const float fx, const float fy, const float fz, const int c=0) const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "cubic_atXYZ_p(): Empty instance.", + cimg_instance); + return _cubic_atXYZ_p(fx,fy,fz,c); + } + + Tfloat _cubic_atXYZ_p(const float fx, const float fy, const float fz, const int c=0) const { + const float + nfx = cimg::type::is_nan(fx)?0:cimg::mod(fx,_width - 0.5f), + nfy = cimg::type::is_nan(fy)?0:cimg::mod(fy,_height - 0.5f), + nfz = cimg::type::is_nan(fz)?0:cimg::mod(fz,_depth - 0.5f); + 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 = cimg::mod(x - 1,width()), nx = cimg::mod(x + 1,width()), ax = cimg::mod(x + 2,width()), + py = cimg::mod(y - 1,height()), ny = cimg::mod(y + 1,height()), ay = cimg::mod(y + 2,height()), + pz = cimg::mod(z - 1,depth()), nz = cimg::mod(z + 1,depth()), az = cimg::mod(z + 2,depth()); + 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)); + } + + T cubic_atXYZ_pc(const float fx, const float fy, const float fz, const int c) const { + return cimg::type::cut(cubic_atXYZ_p(fx,fy,fz,c)); + } + + T _cubic_atXYZ_pc(const float fx, const float fy, const float fz, const int c) const { + return cimg::type::cut(_cubic_atXYZ_p(fx,fy,fz,c)); + } + //! 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 @@ -14276,7 +16077,7 @@ namespace cimg_library_suffixed { 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 (or \c 0 if no limits are set). - \param format For float/double-values, tell the printf format used to generate the ascii representation + \param format For float/double-values, tell the printf format used to generate the text representation of the numbers (or \c 0 for default representation). \note - The returned image is never empty. @@ -14637,10 +16438,10 @@ namespace cimg_library_suffixed { \note - Useful to convert an offset to a buffer value into pixel value coordinates: \code - const CImg img(100,100,1,3); // Construct a 100x100 RGB color image. - const unsigned long offset = 1249; // Offset to the pixel (49,12,0,0). + const CImg 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. + 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); } @@ -14726,9 +16527,9 @@ namespace cimg_library_suffixed { \par Example \code const CImg - 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. + 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 @@ -14739,18 +16540,19 @@ namespace cimg_library_suffixed { 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. + //! 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. + 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 + (at least 256 bytes). \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. + - 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 @@ -14761,11 +16563,11 @@ namespace cimg_library_suffixed { char *const error_message=0) const { if (error_message) *error_message = 0; - // Check consistency for the particular case of an empty 3d object. + // 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, " + if (error_message) cimg_snprintf(error_message,256, + "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()); @@ -14775,21 +16577,21 @@ namespace cimg_library_suffixed { } // 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)", + if (_height!=3 || _depth>1 || _spectrum>1) { // Check vertices dimensions + if (error_message) cimg_snprintf(error_message,256, + "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", + if (error_message) cimg_snprintf(error_message,256, + "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", + if (error_message) cimg_snprintf(error_message,256, + "3D object (%u,%u) defines %lu opacities", _width,primitives._width,(unsigned long)opacities.size()); return false; } @@ -14800,70 +16602,70 @@ namespace cimg_library_suffixed { const CImg& primitive = primitives[l]; const unsigned int psiz = (unsigned int)primitive.size(); switch (psiz) { - case 1 : { // Point. + 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 " + if (error_message) cimg_snprintf(error_message,256, + "3D object (%u,%u) refers to invalid vertex index %u in " "point primitive [%u]", _width,primitives._width,i0,l); return false; } } break; - case 5 : { // Sphere. + 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 " + if (error_message) cimg_snprintf(error_message,256, + "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 : case 6 : { // Segment. + case 2 : case 6 : { // Segment 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 " + if (error_message) cimg_snprintf(error_message,256, + "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 : case 9 : { // Triangle. + case 3 : case 9 : { // Triangle 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 " + if (error_message) cimg_snprintf(error_message,256, + "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 : case 12 : { // Quadrangle. + case 4 : case 12 : { // Quadrangle 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 " + if (error_message) cimg_snprintf(error_message,256, + "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", + if (error_message) cimg_snprintf(error_message,256, + "3D object (%u,%u) defines an invalid primitive [%u] of size %u", _width,primitives._width,l,(unsigned int)psiz); return false; } @@ -14873,8 +16675,8 @@ namespace cimg_library_suffixed { cimglist_for(colors,c) { const CImg& color = colors[c]; if (!color) { - if (error_message) cimg_sprintf(error_message, - "3d object (%u,%u) defines no color for primitive [%u]", + if (error_message) cimg_snprintf(error_message,256, + "3D object (%u,%u) defines no color for primitive [%u]", _width,primitives._width,c); return false; } @@ -14884,8 +16686,8 @@ namespace cimg_library_suffixed { if (colors._width>primitives._width) { const CImg &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)", + if (error_message) cimg_snprintf(error_message,256, + "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; @@ -14895,33 +16697,40 @@ namespace cimg_library_suffixed { return true; } - //! Test if image instance represents a valid serialization of a 3d object. + //! 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. + 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. + - 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 256-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); + if (error_message) cimg_snprintf(error_message,256, + "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"); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d header not found"); return false; } + if (!cimg::type::is_finite(*ptrs) || !cimg::type::is_finite(ptrs[1])) { + if (error_message) cimg_snprintf(error_message,256, + "Specified numbers of vertices/primitives (%g/%g) are invalid", + (double)*ptrs,(double)ptrs[1]); + return false; + } + const unsigned int nb_points = cimg::float2uint((float)*(ptrs++)), nb_primitives = cimg::float2uint((float)*(ptrs++)); @@ -14930,9 +16739,10 @@ namespace cimg_library_suffixed { 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); + if (error_message) cimg_snprintf(error_message,256, + "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; } } @@ -14940,33 +16750,33 @@ namespace cimg_library_suffixed { // 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); + if (error_message) cimg_snprintf(error_message,256, + "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":""); + if (error_message) cimg_snprintf(error_message,256, + "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); + if (error_message) cimg_snprintf(error_message,256, + "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); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d (%u,%u) defines %u vertices but no primitive", + nb_points,nb_primitives,nb_points); return false; } @@ -14975,56 +16785,56 @@ namespace cimg_library_suffixed { for (unsigned int p = 0; p=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); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d (%u,%u) refers to invalid vertex index %u in point primitive [%u]", + nb_points,nb_primitives,i0,p); return false; } } break; - case 5 : { // Sphere. + 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); + if (error_message) cimg_snprintf(error_message,256, + "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. + 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); + if (error_message) cimg_snprintf(error_message,256, + "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. + 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); + if (error_message) cimg_snprintf(error_message,256, + "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. + case 4 : case 12 : { // Quadrangle const unsigned int i0 = cimg::float2uint((float)*(ptrs++)), i1 = cimg::float2uint((float)*(ptrs++)), @@ -15032,97 +16842,97 @@ namespace cimg_library_suffixed { 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); + if (error_message) cimg_snprintf(error_message,256, + "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); + if (error_message) cimg_snprintf(error_message,256, + "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)); + if (error_message) cimg_snprintf(error_message,256, + "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); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d (%u,%u) defines no color/texture data", + nb_points,nb_primitives); return false; } for (unsigned int c = 0; c=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); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d (%u,%u) refers to invalid shared sprite/texture index %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)); + if (error_message) cimg_snprintf(error_message,256, + "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); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d (%u,%u) defines no opacity data", + nb_points,nb_primitives); return false; } for (unsigned int o = 0; o=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); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d (%u,%u) refers to invalid shared opacity index %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); + if (error_message) cimg_snprintf(error_message,256, + "CImg3d (%u,%u) has incomplete opacity data for primitive [%u]", + nb_points,nb_primitives,o); return false; } } // Check end of data. if (ptrs1?"s":""); + if (error_message) cimg_snprintf(error_message,256, + "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; @@ -15142,50 +16952,53 @@ namespace cimg_library_suffixed { // Define the math formula parser/compiler and expression evaluator. struct _cimg_math_parser { CImg mem; - CImg memtype; - CImgList _code, &code, code_init, code_end; + CImg memtype, memmerge; + CImgList _code, &code, code_begin, code_end, + _code_begin_t, &code_begin_t, _code_end_t, &code_end_t; CImg opcode; const CImg *p_code_end, *p_code; const CImg *const p_break; CImg expr, pexpr; const CImg& imgin; - const CImgList& listin; CImg &imgout; - CImgList& listout; + CImgList& imglist; CImg _img_stats, &img_stats, constcache_vals; - CImgList _list_stats, &list_stats, _list_median, &list_median; + CImgList _list_stats, &list_stats, _list_median, &list_median, _list_norm, &list_norm; CImg mem_img_stats, constcache_inds; CImg level, variable_pos, reserved_label; CImgList variable_def, macro_def, macro_body; - CImgList 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; + unsigned int mempos, mem_img_median, mem_img_norm, mem_img_index, debug_indent, + result_dim, result_end_dim, break_type, constcache_size; + bool is_parallelizable, is_noncritical_run, is_end_code, is_fill, need_input_copy, return_new_comp; + double *result, *result_end; + cimg_uint64 rng; 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_const_scalar(arg) (memtype[arg]==1) // Is const scalar? #define _cimg_mp_is_vector(arg) (memtype[arg]>1) // Is vector? +#define _cimg_mp_is_comp(arg) (!memtype[arg]) // Is computation value? +#define _cimg_mp_is_reserved(arg) (memtype[arg]==-1) // Is scalar and reserved (e.g. variable)? #define _cimg_mp_size(arg) (_cimg_mp_is_scalar(arg)?0U:(unsigned int)memtype[arg] - 1) // Size (0=scalar, N>0=vectorN) -#define _cimg_mp_calling_function calling_function_s()._data +#define _cimg_mp_calling_function s_calling_function()._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_const_scalar(arg,n_arg,mode) check_const_scalar(arg,n_arg,mode,ss,se,saved_char) +#define _cimg_mp_check_const_index(arg) check_const_index(arg,ss,se,saved_char) +#define _cimg_mp_check_notnan_index(arg) check_notnan_index(arg,ss,se,saved_char) +#define _cimg_mp_check_list() check_list(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_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,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_return_nan() _cimg_mp_return(_cimg_mp_slot_nan) -#define _cimg_mp_constant(val) _cimg_mp_return(constant((double)(val))) +#define _cimg_mp_const_scalar(val) _cimg_mp_return(const_scalar((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)) @@ -15199,43 +17012,61 @@ namespace cimg_library_suffixed { #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)) +#define _cimg_mp_vector4_vvss(op,i1,i2,i3,i4) _cimg_mp_return(vector4_vvss(op,i1,i2,i3,i4)) +#define _cimg_mp_vector4_vsss(op,i1,i2,i3,i4) _cimg_mp_return(vector4_vsss(op,i1,i2,i3,i4)) +#define _cimg_mp_vector4_svss(op,i1,i2,i3,i4) _cimg_mp_return(vector4_svss(op,i1,i2,i3,i4)) +#define _cimg_mp_strerr \ + *se = saved_char; \ + for (s0 = ss; s0>expr._data && *s0!=';'; --s0) {} \ + if (*s0==';') ++s0; \ + while (cimg::is_blank(*s0)) ++s0; \ + cimg::strellipsize(s0,64) + + // Constructors / Destructors. + ~_cimg_math_parser() { + cimg::srand(rng); + } - // Constructors. _cimg_math_parser(const char *const expression, const char *const funcname=0, const CImg& img_input=CImg::const_empty(), CImg *const img_output=0, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0, - const bool _is_fill=false): - code(_code),p_break((CImg*)0 - 2), - imgin(img_input),listin(list_inputs?*list_inputs:CImgList::const_empty()), - imgout(img_output?*img_output:CImg::empty()),listout(list_outputs?*list_outputs:CImgList::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), + CImgList *const list_images=0, const bool _is_fill=false): + code(_code),code_begin_t(_code_begin_t),code_end_t(_code_end_t), + p_break((CImg*)(cimg_ulong)-2),imgin(img_input), + imgout(img_output?*img_output:CImg::empty()),imglist(list_images?*list_images:CImgList::empty()), + img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),list_norm(_list_norm),user_macro(0), + mem_img_median(~0U),mem_img_norm(~0U),mem_img_index(~0U),debug_indent(0),result_dim(0),result_end_dim(0), + break_type(0),constcache_size(0),is_parallelizable(true),is_noncritical_run(false),is_fill(_is_fill), + need_input_copy(false),result_end(0),rng((cimg::_rand(),cimg::rng())), calling_function(funcname?funcname:"cimg_math_parser") { + +#if cimg_use_openmp!=0 + rng+=omp_get_thread_num(); +#endif if (!expression || !*expression) throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: Empty expression.", pixel_type(),_cimg_mp_calling_function); const char *_expression = expression; - while (*_expression && ((signed char)*_expression<=' ' || *_expression==';')) ++_expression; + while (*_expression && (cimg::is_blank(*_expression) || *_expression==';')) ++_expression; CImg::string(_expression).move_to(expr); char *ps = &expr.back() - 1; - while (ps>expr._data && ((signed char)*ps<=' ' || *ps==';')) --ps; + while (ps>expr._data && (cimg::is_blank(*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 = ' '; + if (!cimg::is_blank(*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_interpolation (reserved_label[31]!=~0U?reserved_label[31]:0) +#define _cimg_mp_boundary (reserved_label[32]!=~0U?reserved_label[32]:0) +#define _cimg_mp_slot_t 17 #define _cimg_mp_slot_nan 29 #define _cimg_mp_slot_x 30 #define _cimg_mp_slot_y 31 @@ -15246,7 +17077,7 @@ namespace cimg_library_suffixed { 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[_cimg_mp_slot_t] = 0; // thread_id mem[18] = (double)imgin._width; // w mem[19] = (double)imgin._height; // h mem[20] = (double)imgin._depth; // d @@ -15255,41 +17086,41 @@ namespace cimg_library_suffixed { 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[26] = (double)imglist._width; // l + mem[27] = std::exp(1.); // e mem[28] = cimg::PI; // pi mem[_cimg_mp_slot_nan] = cimg::type::nan(); // nan // Set value property : - // { -2 = other | -1 = variable | 0 = computation value | + // { -1 = reserved (e.g. 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; + memtype[_cimg_mp_slot_t] = memtype[_cimg_mp_slot_x] = memtype[_cimg_mp_slot_y] = + memtype[_cimg_mp_slot_z] = memtype[_cimg_mp_slot_c] = -1; 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 + // reserved_label[0-32] are used to store the memory index of these variables: + // [0] = wh, [1] = whd, [2] = whds, [3] = pi, [4] = im, [5] = iM, [6] = ia, [7] = iv, [8] = id, + // [9] = is, [10] = ip, [11] = ic, [12] = in, [13] = xm, [14] = ym, [15] = zm, [16] = cm, [17] = xM, + // [18] = yM, [19] = zM, [20] = cM, [21] = i0...[30] = i9, [31] = interpolation, [32] = boundary - // Compile expression into a serie of opcodes. + // Compile expression into a sequence of opcodes. s_op = ""; ss_op = expr._data; - const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0,false); - if (!_cimg_mp_is_constant(ind_result)) { + const unsigned int ind_result = compile(expr._data,expr._data + expr._width - 1,0,0,0); + if (!_cimg_mp_is_const_scalar(ind_result)) { if (_cimg_mp_is_vector(ind_result)) CImg(&mem[ind_result] + 1,_cimg_mp_size(ind_result),1,1,1,true). fill(cimg::type::nan()); - else mem[ind_result] = cimg::type::nan(); + else if (ind_result!=_cimg_mp_slot_t) mem[ind_result] = cimg::type::nan(); } + if (mem._width>=256 && mem._width - mempos>=mem._width/2) mem.resize(mempos,1,1,1,-1); + result_dim = _cimg_mp_size(ind_result); + result = mem._data + ind_result; // Free resources used for compiling expression and prepare evaluation. - result_dim = _cimg_mp_size(ind_result); - if (mem._width>=256 && mem._width - mempos>=mem._width/2) mem.resize(mempos,1,1,1,-1); - result = mem._data + ind_result; memtype.assign(); constcache_vals.assign(); constcache_inds.assign(); @@ -15301,11 +17132,11 @@ namespace cimg_library_suffixed { opcode.assign(); opcode._is_shared = true; - // Execute init() bloc if any specified. - if (code_init) { + // Execute begin() block if any specified. + if (code_begin) { 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_data; const ulongT target = opcode[1]; mem[target] = _cimg_mp_defunc(*this); @@ -15315,127 +17146,80 @@ namespace cimg_library_suffixed { } _cimg_math_parser(): - code(_code),p_code_end(0),p_break((CImg*)0 - 2), - imgin(CImg::const_empty()),listin(CImgList::const_empty()), - imgout(CImg::empty()),listout(CImgList::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) { + code(_code),code_begin_t(_code_begin_t),code_end_t(_code_end_t), + p_code_end(0),p_break((CImg*)(cimg_ulong)-2), + imgin(CImg::const_empty()),imgout(CImg::empty()),imglist(CImgList::empty()), + img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),list_norm(_list_norm),debug_indent(0), + result_dim(0),result_end_dim(0),break_type(0),constcache_size(0),is_parallelizable(true), + is_noncritical_run(false),is_fill(false),need_input_copy(false), + result_end(0),rng(0),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(); + mem(mp.mem),code(mp.code),code_begin_t(mp.code_begin_t),code_end_t(mp.code_end_t), + p_code_end(mp.p_code_end),p_break(mp.p_break), + imgin(mp.imgin),imgout(mp.imgout),imglist(mp.imglist), + img_stats(mp.img_stats),list_stats(mp.list_stats),list_median(mp.list_median),list_norm(mp.list_norm), + debug_indent(0),result_dim(mp.result_dim),result_end_dim(mp.result_end_dim),break_type(0),constcache_size(0), + is_parallelizable(mp.is_parallelizable),is_noncritical_run(mp.is_noncritical_run),is_fill(mp.is_fill), + need_input_copy(mp.need_input_copy), + result(mem._data + (mp.result - mp.mem._data)), + result_end(mp.result_end?mem._data + (mp.result_end - mp.mem._data):0), + rng((cimg::_rand(),cimg::rng())),calling_function(0) { + +#if cimg_use_openmp!=0 + mem[_cimg_mp_slot_t] = (double)omp_get_thread_num(); + rng+=omp_get_thread_num(); #endif opcode.assign(); opcode._is_shared = true; } - // Count parentheses/brackets level of each character of the expression. - CImg get_level(CImg& 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 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_appname "_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_appname "_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 is_inside_string(CImg& 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 res = CImg::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, - const bool is_single) { + const unsigned char block_flags) { if (depth>256) { cimg::strellipsize(expr,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: Call stack overflow (infinite recursion?), " - "in expression '%s%s%s'.", + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function, - (ss - 4)>expr._data?"...":"", - (ss - 4)>expr._data?ss - 4:expr._data, - se<&expr.back()?"...":""); + (ss - 4)>expr._data?ss - 4:expr._data); } - char c1, c2, c3, c4; + char c1, c2; // Simplify expression when possible. do { c2 = 0; if (ssss && ((signed char)(c1 = *(se - 1))<=' ' || c1==';')) --se; + while (*ss && (cimg::is_blank(*ss) || *ss==';')) ++ss; // Remove leading blanks and ';' + while (se>ss && (cimg::is_blank(c1 = *(se - 1)) || c1==';')) --se; // Remove trailing blanks and ';' } - while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) { + while (*ss=='(' && *(se - 1)==')' && std::strchr(ss,')')==se - 1) { // Remove useless start/end parentheses ++ss; --se; c2 = 1; } + if (*ss=='_' && ss + 1=se) return _cimg_mp_slot_nan; + c2 = 1; + } } while (c2 && ss::%s: %s%s Missing %s, in expression '%s%s%s'.", + "CImg<%s>::%s: %s%s Missing %s, in expression '%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()?"...":""); + ss_op); } + static const size_t siz_ref = 7*sizeof(unsigned int); 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; @@ -15446,11 +17230,21 @@ namespace cimg_library_suffixed { *s, *ps, *ns, *s0, *s1, *s2, *s3, sep = 0, end = 0; double val = 0, val1, val2; mp_func op; + return_new_comp = false; + + // Bits of 'block_flags' tell about in which code block we currently are: + // 0: critical(), 1: begin(), 2: begin_t(), 3: end(), 4: end_t(). + const bool + is_inside_critical = (bool)(block_flags&1), + is_inside_begin = (bool)(block_flags&2), + is_inside_begin_t = (bool)(block_flags&4), + is_inside_end = (bool)(block_flags&8), + is_inside_end_t = (bool)(block_flags&16); // '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) | + // 3 = image value (coordinates) | 4 = image value as a vector (offset) | // 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. @@ -15472,59 +17266,82 @@ namespace cimg_library_suffixed { int nb = 0; s = ss + (*ss=='+' || *ss=='-'?1:0); if (*s=='i' || *s=='I' || *s=='n' || *s=='N') { // Particular cases : +/-NaN and +/-Inf - is_sth = !(*ss=='-'); + is_sth = *ss=='-'; if (!cimg::strcasecmp(s,"inf")) { val = cimg::type::inf(); nb = 1; } else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); nb = 1; } - if (nb==1 && !is_sth) val = -val; + if (nb==1 && is_sth) val = -val; + } else if (*s=='0' && (s[1]=='x' || s[1]=='X')) { // Hexadecimal number + is_sth = *ss=='-'; + if (cimg_sscanf(s + 2,"%x%c",&arg1,&sep)==1) { + nb = 1; + val = (double)arg1; + if (is_sth) val = -val; + } } if (!nb) nb = cimg_sscanf(ss,"%lf%c%c",&val,&(sep=0),&(end=0)); - if (nb==1) _cimg_mp_constant(val); - if (nb==2 && sep=='%') _cimg_mp_constant(val/100); + if (nb==1) _cimg_mp_const_scalar(val); + if (nb==2 && sep=='%') _cimg_mp_const_scalar(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 'c' : _cimg_mp_return(reserved_label[(int)'c']!=~0U?reserved_label[(int)'c']:_cimg_mp_slot_c); + case 'd' : _cimg_mp_return(reserved_label[(int)'d']!=~0U?reserved_label[(int)'d']:20); + case 'e' : _cimg_mp_return(reserved_label[(int)'e']!=~0U?reserved_label[(int)'e']:27); + case 'h' : _cimg_mp_return(reserved_label[(int)'h']!=~0U?reserved_label[(int)'h']:19); + case 'k' : + if (reserved_label[(int)'k']!=~0U) _cimg_mp_return(reserved_label[(int)'k']); + pos = get_mem_img_index(); + if (pos!=~0U) _cimg_mp_return(pos); + _cimg_mp_return_nan(); + case 'l' : _cimg_mp_return(reserved_label[(int)'l']!=~0U?reserved_label[(int)'l']:26); + case 'r' : _cimg_mp_return(reserved_label[(int)'r']!=~0U?reserved_label[(int)'r']:22); + case 's' : _cimg_mp_return(reserved_label[(int)'s']!=~0U?reserved_label[(int)'s']:21); + case 't' : _cimg_mp_return(reserved_label[(int)'t']!=~0U?reserved_label[(int)'t']:_cimg_mp_slot_t); + case 'n' : + if (reserved_label[(int)'n']!=~0U) _cimg_mp_return(reserved_label[(int)'n']); +#if cimg_use_openmp!=0 + _cimg_mp_const_scalar((double)omp_get_max_threads()); +#else + _cimg_mp_return(1); +#endif + case 'w' : _cimg_mp_return(reserved_label[(int)'w']!=~0U?reserved_label[(int)'w']:18); + case 'x' : _cimg_mp_return(reserved_label[(int)'x']!=~0U?reserved_label[(int)'x']:_cimg_mp_slot_x); + case 'y' : _cimg_mp_return(reserved_label[(int)'y']!=~0U?reserved_label[(int)'y']:_cimg_mp_slot_y); + case 'z' : _cimg_mp_return(reserved_label[(int)'z']!=~0U?reserved_label[(int)'z']:_cimg_mp_slot_z); case 'u' : - if (reserved_label['u']!=~0U) _cimg_mp_return(reserved_label['u']); + if (reserved_label[(int)'u']!=~0U) _cimg_mp_return(reserved_label[(int)'u']); _cimg_mp_scalar2(mp_u,0,1); + case 'v' : + if (reserved_label[(int)'v']!=~0U) _cimg_mp_return(reserved_label[(int)'v']); + _cimg_mp_scalar2(mp_u,11,1); case 'g' : - if (reserved_label['g']!=~0U) _cimg_mp_return(reserved_label['g']); + if (reserved_label[(int)'g']!=~0U) _cimg_mp_return(reserved_label[(int)'g']); _cimg_mp_scalar0(mp_g); case 'i' : - if (reserved_label['i']!=~0U) _cimg_mp_return(reserved_label['i']); + if (reserved_label[(int)'i']!=~0U) _cimg_mp_return(reserved_label[(int)'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); + if (reserved_label[(int)'I']!=~0U) _cimg_mp_return(reserved_label[(int)'I']); + if (!imgin._spectrum) _cimg_mp_return(0); need_input_copy = true; pos = vector(imgin._spectrum); CImg::vector((ulongT)mp_Joff,pos,0,0,imgin._spectrum).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); case 'R' : - if (reserved_label['R']!=~0U) _cimg_mp_return(reserved_label['R']); + if (reserved_label[(int)'R']!=~0U) _cimg_mp_return(reserved_label[(int)'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']); + if (reserved_label[(int)'G']!=~0U) _cimg_mp_return(reserved_label[(int)'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']); + if (reserved_label[(int)'B']!=~0U) _cimg_mp_return(reserved_label[(int)'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']); + if (reserved_label[(int)'A']!=~0U) _cimg_mp_return(reserved_label[(int)'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); } @@ -15536,36 +17353,42 @@ namespace cimg_library_suffixed { _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'; + pos = 21 + *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); + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 21,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; // ip 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; + if (reserved_label[11]!=~0U) _cimg_mp_return(reserved_label[11]); + if (mem_img_median==~0U) mem_img_median = imgin?const_scalar(imgin.median()):0; _cimg_mp_return(mem_img_median); break; + case 'd' : arg1 = 8; arg2 = 3; break; // id + case 'm' : arg1 = 4; arg2 = 0; break; // im + case 'M' : arg1 = 5; arg2 = 1; break; // iM + case 'n' : // in + if (reserved_label[12]!=~0U) _cimg_mp_return(reserved_label[12]); + if (mem_img_norm==~0U) mem_img_norm = imgin?const_scalar(imgin.magnitude(2)):0; + _cimg_mp_return(mem_img_norm); + break; + case 'p' : arg1 = 10; arg2 = 13; break; // ip + case 's' : arg1 = 9; arg2 = 12; break; // is + case 'v' : arg1 = 7; arg2 = 3; break; // iv } } 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 + case 'x' : arg1 = 13; arg2 = 4; break; // xm + case 'y' : arg1 = 14; arg2 = 5; break; // ym + case 'z' : arg1 = 15; arg2 = 6; break; // zm + case 'c' : arg1 = 16; 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 + case 'x' : arg1 = 17; arg2 = 8; break; // xM + case 'y' : arg1 = 18; arg2 = 9; break; // yM + case 'z' : arg1 = 19; arg2 = 10; break; // zM + case 'c' : arg1 = 20; arg2 = 11; break; // cM } if (arg1!=~0U) { if (reserved_label[arg1]!=~0U) _cimg_mp_return(reserved_label[arg1]); @@ -15573,7 +17396,8 @@ namespace cimg_library_suffixed { 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]); + if (mem_img_stats[arg2]==~0U) mem_img_stats[arg2] = const_scalar(img_stats[arg2]); + if (arg1==8) _cimg_mp_const_scalar(std::sqrt(img_stats[arg2])); // id: std variation _cimg_mp_return(mem_img_stats[arg2]); } } else if (ss3==se) { // Three-chars reserved variable @@ -15586,20 +17410,21 @@ namespace cimg_library_suffixed { pos = ~0U; is_sth = false; + for (s0 = ss, s = ss1; s2 && (*ss=='i' || *ss=='j' || *ss=='I' || *ss=='J') && (*ss1=='(' || *ss1=='[') && - (reserved_label[*ss]==~0U || *ss1=='(' || !_cimg_mp_is_vector(reserved_label[*ss]))) { + (reserved_label[(int)*ss]==~0U || *ss1=='(' || !_cimg_mp_is_vector(reserved_label[(int)*ss]))) { is_relative = *ss=='j' || *ss=='J'; if (*ss1=='[' && *ve1==']') { // i/j/I/J[_#ind,offset] = value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; if (*ss2=='#') { // Index specified s0 = ss3; while (s0='i'?1:3,0); 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); + if (p1!=~0U) { + _cimg_mp_check_const_index(p1); + p3 = (unsigned int)cimg::mod((int)mem[p1],imglist.width()); + p2 = imglist[p3]._spectrum; + } else p2 = imgin._spectrum; + if (!p2) _cimg_mp_return(0); + _cimg_mp_check_type(arg2,2,2,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 = *ss=='I' || *ss=='J'?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; + set_reserved_vector(arg2); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -1; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -1; } - if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg2); + if (!imglist) _cimg_mp_return(arg2); if (*ss>='i') CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), arg2,p1,arg1).move_to(code); @@ -15680,20 +17504,22 @@ namespace cimg_library_suffixed { } if (*ss1=='(' && *ve1==')') { // i/j/I/J(_#ind,_x,_y,_z,_c) = value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; if (*ss2=='#') { // Index specified s0 = ss3; while (s0='i'?1:3,0); if (s0='i'?1:3,p2); + if (p_ref) { - *p_ref = _cimg_mp_is_vector(arg5)?5:3; + *p_ref = *ss=='I' || *ss=='J'?5:3; p_ref[1] = p1; p_ref[2] = (unsigned int)is_relative; p_ref[3] = arg1; @@ -15735,16 +17561,16 @@ namespace cimg_library_suffixed { 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; + set_reserved_vector(arg5); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg5)) memtype[arg5] = -1; + if (p1!=~0U && _cimg_mp_is_comp(p1)) memtype[p1] = -1; + if (_cimg_mp_is_comp(arg1)) memtype[arg1] = -1; + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -1; + if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -1; + if (_cimg_mp_is_comp(arg4)) memtype[arg4] = -1; } if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg5); + if (!imglist) _cimg_mp_return(arg5); if (*ss>='i') CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), arg5,p1,arg1,arg2,arg3,arg4).move_to(code); @@ -15773,111 +17599,103 @@ namespace cimg_library_suffixed { // 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; nsss) { + if (s0>ss && cimg::is_varname(ss,s0 - ss)) { variable_name[s0 - ss] = 0; // Remove brackets in variable name - arg1 = ~0U; // Vector slot - arg2 = compile(++s0,ve1,depth1,0,is_single); // Index - arg3 = compile(s + 1,se,depth1,0,is_single); // Value to assign + get_variable_pos(variable_name,arg1,arg2); + arg1 = arg2!=~0U?reserved_label[arg2]:arg1!=~0U?variable_pos[arg1]:~0U; // Vector slot + if (arg1==~0U || _cimg_mp_is_scalar(arg1)) + compile(ss,s0,depth1,0,block_flags); // Variable does not exist or is not a vector -> error + + arg2 = compile(++s0,ve1,depth1,0,block_flags); // Index + arg3 = compile(s + 1,se,depth1,0,block_flags); // 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; + if (_cimg_mp_is_const_scalar(arg2)) { // Constant index -> return corresponding variable slot directly + nb = (int)mem[arg2]; + if (nb>=0 && nb<(int)_cimg_mp_size(arg1)) { + arg1+=nb + 1; + CImg::vector((ulongT)mp_copy,arg1,arg3).move_to(code); + _cimg_mp_return(arg1); } - } else arg1 = reserved_label[*variable_name]; // Single-char variable - if (arg1==~0U) compile(ss,s0 - 1,depth1,0,is_single); // Variable does not exist -> error - else { // Variable already exists - if (_cimg_mp_is_scalar(arg1)) compile(ss,s,depth1,0,is_single); // 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_size(arg1)) { - arg1+=nb + 1; - CImg::vector((ulongT)mp_copy,arg1,arg3).move_to(code); - _cimg_mp_return(arg1); - } - compile(ss,s,depth1,0,is_single); // 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::vector((ulongT)mp_vector_set_off,arg3,arg1,(ulongT)_cimg_mp_size(arg1), - arg2,arg3). - move_to(code); - _cimg_mp_return(arg3); + compile(ss,s,depth1,0,block_flags); // 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] = -1; // Prevent from being used in further optimization + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -1; + } + CImg::vector((ulongT)mp_vector_set_off,arg3,arg1,(ulongT)_cimg_mp_size(arg1),arg2). + 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; nsss) { // Looks like a valid function declaration + if (cimg::is_varname(ss,s0 - ss) && std::strncmp(variable_name,"debug(",6) && + std::strncmp(variable_name,"print(",6)) { // Valid macro name s0 = variable_name._data + (s0 - ss); *s0 = 0; s1 = variable_name._data + l_variable_name - 1; // Pointer to closing parenthesis CImg(variable_name._data,(unsigned int)(s0 - variable_name._data + 1)).move_to(macro_def,0); - ++s; while (*s && (signed char)*s<=' ') ++s; + ++s; while (*s && cimg::is_blank(*s)) ++s; CImg(s,(unsigned int)(se - s + 1)).move_to(macro_body,0); - p1 = 1; // Indice of current parsed argument + bool is_variadic = false; + p1 = 1; // Index of current parsed argument for (s = s0 + 1; s<=s1; ++p1, s = ns + 1) { // Parse function arguments - if (p1>24) { - *se = saved_char; + if (is_variadic && p1>1) { + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " - "CImg<%s>::%s: %s: Too much specified arguments (>24) in macro " - "definition '%s()', in expression '%s%s%s'.", + "CImg<%s>::%s: %s: Multiple arguments not allowed when first one is " + "variadic, in macro definition '%s()', in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, - variable_name._data, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + variable_name._data,s0); } - while (*s && (signed char)*s<=' ') ++s; + if (p1>24) { + _cimg_mp_strerr; + cimg::strellipsize(variable_name,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Too much specified arguments (>24), in macro " + "definition '%s()', in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data,s0); + } + while (*s && cimg::is_blank(*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' '; ++ns) - if (!is_varchar(*ns)) { is_sth = false; break; } + if (*s2>='0' && *s2<='9') is_sth = false; + else for (ns = s2; nss2 && *ns=='.' && ns[1]=='.' && ns[2]=='.') { is_variadic = true; ns+=3; } + else if (*ns=='.') is_sth = false; + while (*ns && cimg::is_blank(*ns)) ++ns; + if (!is_sth || s2==s3 || (*ns!=',' && ns!=s1)) { - *se = saved_char; + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: %s: %s name specified for argument %u when defining " - "macro '%s()', in expression '%s%s%s'.", + "macro '%s()', in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, is_sth?"Empty":"Invalid",p1, - variable_name._data, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + variable_name._data,s0); } - if (ns==s1 || *ns==',') { // New argument found + + if (ns==s1 || *ns==',' || (is_variadic && *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 + p2macro_body[0]._data && cimg::is_varchar(*(ps - 1))) || + (ps + p2macro_body[0]._data && *(ps - 1)=='#') { // Remove pre-number sign *(ps - 1) = (char)p1; if (ps + p26 && !std::strncmp(variable_name,"const ",6); - s0 = variable_name._data; if (is_const) { - s0+=6; while ((signed char)*s0<=' ') ++s0; + s0+=6; while (cimg::is_blank(*s0)) ++s0; variable_name.resize(variable_name.end() - s0,1,1,1,0,0,1); } + if (cimg::is_varname(variable_name)) { // 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; } - - // 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,is_single); - 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; } + // Assign variable (direct). + get_variable_pos(variable_name,arg1,arg2); + arg3 = compile(s + 1,se,depth1,0,block_flags); + is_sth = return_new_comp; // is arg3 a new blank object? + if (is_const) _cimg_mp_check_const_scalar(arg3,2,0); + arg1 = arg2!=~0U?reserved_label[arg2]:arg1!=~0U?variable_pos[arg1]:~0U; 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); + if (_cimg_mp_is_vector(arg3)) { // Vector variable + arg1 = is_sth || is_comp_vector(arg3)?arg3:vector_copy(arg3); + set_reserved_vector(arg1); // Prevent from being used in further optimization } else { // Scalar variable - if (is_const) arg1 = arg2; + if (is_const) arg1 = arg3; else { - arg1 = _cimg_mp_is_comp(arg2)?arg2:scalar1(mp_copy,arg2); + arg1 = is_sth || _cimg_mp_is_comp(arg3)?arg3:scalar1(mp_copy,arg3); 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); - } + if (arg2!=~0U) reserved_label[arg2] = 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; + if (is_const || _cimg_mp_is_const_scalar(arg1)) { + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: %s: Invalid assignment of %sconst variable '%s'%s, " - "in expression '%s%s%s'.", + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, - _cimg_mp_is_constant(arg1)?"already-defined ":"non-", + _cimg_mp_is_const_scalar(arg1)?"":"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_is_const_scalar(arg1) && is_const?" as a const variable":"", + s0); } - _cimg_mp_check_type(arg2,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_size(arg1)); + _cimg_mp_check_type(arg3,2,_cimg_mp_is_vector(arg1)?3:1,_cimg_mp_size(arg1)); if (_cimg_mp_is_vector(arg1)) { // Vector - if (_cimg_mp_is_vector(arg2)) // From vector - CImg::vector((ulongT)mp_vector_copy,arg1,arg2,(ulongT)_cimg_mp_size(arg1)). + if (_cimg_mp_is_vector(arg3)) // From vector + CImg::vector((ulongT)mp_vector_copy,arg1,arg3,(ulongT)_cimg_mp_size(arg1)). move_to(code); else // From scalar - CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg2). + CImg::vector((ulongT)mp_vector_init,arg1,1,(ulongT)_cimg_mp_size(arg1),arg3). move_to(code); } else // Scalar - CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); + CImg::vector((ulongT)mp_copy,arg1,arg3).move_to(code); } + return_new_comp = false; _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 (is_sth) break; // Do nothing and make ternary operator priority over assignment if (l_variable_name>2 && (std::strchr(variable_name,'(') || std::strchr(variable_name,'['))) { ref.assign(7); - arg1 = compile(ss,s,depth1,ref,is_single); // Lvalue slot - arg2 = compile(s + 1,se,depth1,0,is_single); // Value to assign + arg1 = compile(ss,s,depth1,ref,block_flags); // Lvalue slot + arg2 = compile(s + 1,se,depth1,0,block_flags); // 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::vector((ulongT)mp_vector_set_off,arg2,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg2). + if (p_ref) std::memcpy(p_ref,ref,siz_ref); + CImg::vector((ulongT)mp_vector_set_off,arg2,arg3,(ulongT)_cimg_mp_size(arg3),arg4). move_to(code); _cimg_mp_return(arg2); } if (*ref==2) { // Image value (scalar): i/j[_#ind,off] = scalar - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) 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 (p_ref) std::memcpy(p_ref,ref,siz_ref); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg2); + if (!imglist) _cimg_mp_return(arg2); CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), arg2,p1,arg3).move_to(code); } else { @@ -16071,7 +17838,7 @@ namespace cimg_library_suffixed { } if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) = scalar - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; _cimg_mp_check_type(arg2,2,1,0); p1 = ref[1]; // Index is_relative = (bool)ref[2]; @@ -16079,9 +17846,9 @@ namespace cimg_library_suffixed { 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 (p_ref) std::memcpy(p_ref,ref,siz_ref); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg2); + if (!imglist) _cimg_mp_return(arg2); CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), arg2,p1,arg3,arg4,arg5,arg6).move_to(code); } else { @@ -16093,20 +17860,23 @@ namespace cimg_library_suffixed { } if (*ref==4) { // Image value (vector): I/J[_#ind,off] = value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (p_ref) std::memcpy(p_ref,ref,siz_ref); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg2); + if (!imglist) _cimg_mp_return(arg2); if (_cimg_mp_is_scalar(arg2)) CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), - arg2,p1,arg3).move_to(code); - else + arg2,p1,arg3).move_to(code); + else { + _cimg_mp_check_const_index(p1); CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), - arg2,p1,arg3,_cimg_mp_size(arg2)).move_to(code); + arg2,p1,arg3,_cimg_mp_size(arg2)).move_to(code); + } + } else { if (!imgout) _cimg_mp_return(arg2); if (_cimg_mp_is_scalar(arg2)) @@ -16120,22 +17890,25 @@ namespace cimg_library_suffixed { } if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) = value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (p_ref) std::memcpy(p_ref,ref,siz_ref); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg2); + if (!imglist) _cimg_mp_return(arg2); if (_cimg_mp_is_scalar(arg2)) CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), arg2,p1,arg3,arg4,arg5).move_to(code); - else + else { + _cimg_mp_check_const_index(p1); CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), - arg2,p1,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); + arg2,p1,arg3,arg4,arg5,_cimg_mp_size(arg2)).move_to(code); + } + } else { if (!imgout) _cimg_mp_return(arg2); if (_cimg_mp_is_scalar(arg2)) @@ -16159,7 +17932,7 @@ namespace cimg_library_suffixed { _cimg_mp_return(arg1); } - if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s = scalar + if (_cimg_mp_is_reserved(arg1)) { // Scalar variable: s = scalar _cimg_mp_check_type(arg2,2,1,0); CImg::vector((ulongT)mp_copy,arg1,arg2).move_to(code); _cimg_mp_return(arg1); @@ -16167,17 +17940,14 @@ namespace cimg_library_suffixed { } // No assignment expressions match -> error - *se = saved_char; + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: %s: Invalid %slvalue '%s', " - "in expression '%s%s%s'.", + "in expression '%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()?"...":""); + arg1!=~0U && _cimg_mp_is_const_scalar(arg1)?"const ":"", + variable_name._data,s0); } // Apply unary/binary/ternary operators. The operator precedences should be the same as in C++. @@ -16187,8 +17957,8 @@ namespace cimg_library_suffixed { _cimg_mp_op(*ps=='*'?"Operator '**='":*ps=='/'?"Operator '//='":"Operator '^^='"); ref.assign(7); - arg1 = compile(ss,ns,depth1,ref,is_single); // Vector slot - arg2 = compile(s + 1,se,depth1,0,is_single); // Right operand + arg1 = compile(ss,ns,depth1,ref,block_flags); // Vector slot + arg2 = compile(s + 1,se,depth1,0,block_flags); // 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 @@ -16211,33 +17981,34 @@ namespace cimg_library_suffixed { } } - // Write computed value back in image if necessary. if (*ref==4) { // Image value (vector): I/J[_#ind,off] **= value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) 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 (p_ref) std::memcpy(p_ref,ref,siz_ref); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg1); + if (!imglist) _cimg_mp_return(arg1); + _cimg_mp_check_const_index(p1); CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), arg1,p1,arg3,_cimg_mp_size(arg1)).move_to(code); } else { if (!imgout) _cimg_mp_return(arg1); CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), - arg1,arg3,_cimg_mp_size(arg1)).move_to(code); + arg1,arg3,_cimg_mp_size(arg1)).move_to(code); } } else if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) **= value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) 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 (p_ref) std::memcpy(p_ref,ref,siz_ref); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg1); + if (!imglist) _cimg_mp_return(arg1); + _cimg_mp_check_const_index(p1); CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), arg1,p1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); } else { @@ -16251,7 +18022,7 @@ namespace cimg_library_suffixed { } for (s = se2, ps = se3, ns = ps - 1; s>ss1; --s, --ps, --ns) // Here, ns = ps - 1 - if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || *ps=='%' || + if (*s=='=' && (*ps=='+' || *ps=='-' || *ps=='*' || *ps=='/' || (*ps=='%' && s[1]!='=') || *ps=='&' || *ps=='^' || *ps=='|' || (*ps=='>' && *ns=='>') || (*ps=='<' && *ns=='<')) && level[s - expr._data]==clevel) { // Self-operators (+=,-=,*=,/=,%=,>>=,<<=,&=,^=,|=) @@ -16270,15 +18041,15 @@ namespace cimg_library_suffixed { s1 = *ps=='>' || *ps=='<'?ns:ps; ref.assign(7); - arg1 = compile(ss,s1,depth1,ref,is_single); // Variable slot - arg2 = compile(s + 1,se,depth1,0,is_single); // Value to apply + arg1 = compile(ss,s1,depth1,ref,block_flags); // Variable slot + arg2 = compile(s + 1,se,depth1,0,block_flags); // 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 (*ref && (_cimg_mp_is_const_scalar(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_reserved(arg1))) { if (_cimg_mp_is_vector(arg1)) arg1 = vector_copy(arg1); else arg1 = scalar1(mp_copy,arg1); } @@ -16287,23 +18058,23 @@ namespace cimg_library_suffixed { _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)); + if (p_ref) std::memcpy(p_ref,ref,siz_ref); CImg::vector((ulongT)op,arg1,arg2).move_to(code); - CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4). move_to(code); _cimg_mp_return(arg1); } if (*ref==2) { // Image value (scalar): i/j[_#ind,off] += scalar - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) 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 (p_ref) std::memcpy(p_ref,ref,siz_ref); CImg::vector((ulongT)op,arg1,arg2).move_to(code); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg1); + if (!imglist) _cimg_mp_return(arg1); CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), arg1,p1,arg3).move_to(code); } else { @@ -16315,7 +18086,7 @@ namespace cimg_library_suffixed { } if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c) += scalar - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; _cimg_mp_check_type(arg2,2,1,0); p1 = ref[1]; // Index is_relative = (bool)ref[2]; @@ -16323,10 +18094,10 @@ namespace cimg_library_suffixed { 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 (p_ref) std::memcpy(p_ref,ref,siz_ref); CImg::vector((ulongT)op,arg1,arg2).move_to(code); if (p1!=~0U) { - if (!listout) _cimg_mp_return(arg1); + if (!imglist) _cimg_mp_return(arg1); CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), arg1,p1,arg3,arg4,arg5,arg6).move_to(code); } else { @@ -16338,15 +18109,15 @@ namespace cimg_library_suffixed { } if (*ref==4) { // Image value (vector): I/J[_#ind,off] += value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (p_ref) std::memcpy(p_ref,ref,siz_ref); 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); + if (!imglist) _cimg_mp_return(arg1); CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), arg1,p1,arg3,_cimg_mp_size(arg1)).move_to(code); } else { @@ -16358,17 +18129,17 @@ namespace cimg_library_suffixed { } if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c) += value - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (p_ref) std::memcpy(p_ref,ref,siz_ref); 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); + if (!imglist) _cimg_mp_return(arg1); CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), arg1,p1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); } else { @@ -16386,7 +18157,7 @@ namespace cimg_library_suffixed { _cimg_mp_return(arg1); } - if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s += scalar + if (_cimg_mp_is_reserved(arg1)) { // Scalar variable: s += scalar _cimg_mp_check_type(arg2,2,1,0); CImg::vector((ulongT)op,arg1,arg2).move_to(code); _cimg_mp_return(arg1); @@ -16394,82 +18165,83 @@ namespace cimg_library_suffixed { 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); + _cimg_mp_strerr; + cimg::strellipsize(variable_name,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: %s: Invalid %slvalue '%s', " - "in expression '%s%s%s'.", + "in expression '%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()?"...":""); + _cimg_mp_is_const_scalar(arg1)?"const ":"", + variable_name._data,s0); } for (s = ss1; s::vector((ulongT)mp_if,pos,arg1,arg2,arg3, p3 - p2,code._width - p3,arg4).move_to(code,p2); + return_new_comp = true; _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,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); _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,is_single); + arg2 = compile(s + 2,se,depth1,0,block_flags); _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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1] || mem[arg2]); if (!arg1) _cimg_mp_return(arg2); pos = scalar(); CImg::vector((ulongT)mp_logical_or,pos,arg1,arg2,code._width - p2). move_to(code,p2); + return_new_comp = true; _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,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); _cimg_mp_check_type(arg1,1,1,0); if (!arg1) _cimg_mp_return(0); p2 = code._width; - arg2 = compile(s + 2,se,depth1,0,is_single); + arg2 = compile(s + 2,se,depth1,0,block_flags); _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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1] && mem[arg2]); if (arg1>0 && arg1<=16) _cimg_mp_return(arg2); pos = scalar(); CImg::vector((ulongT)mp_logical_and,pos,arg1,arg2,code._width - p2). move_to(code,p2); + return_new_comp = true; _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,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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)) { @@ -16480,8 +18252,8 @@ namespace cimg_library_suffixed { 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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar((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); @@ -16490,14 +18262,14 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar((longT)mem[arg1] & (longT)mem[arg2]); if (!arg1 || !arg2) _cimg_mp_return(0); _cimg_mp_scalar2(mp_bitwise_and,arg1,arg2); } @@ -16505,45 +18277,54 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); if (arg1==arg2) _cimg_mp_return(0); p1 = _cimg_mp_size(arg1); p2 = _cimg_mp_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); + pos = scalar(); + CImg::vector((ulongT)mp_vector_neq,pos,arg1,p1,arg2,p2,11,1).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); } - if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]!=mem[arg2]); + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); if (arg1==arg2) _cimg_mp_return(1); p1 = _cimg_mp_size(arg1); p2 = _cimg_mp_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); + pos = scalar(); + CImg::vector((ulongT)mp_vector_eq,pos,arg1,p1,arg2,p2,11,1).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); } - if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]==mem[arg2]); + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1]<=mem[arg2]); if (arg1==arg2) _cimg_mp_return(1); _cimg_mp_scalar2(mp_lte,arg1,arg2); } @@ -16551,13 +18332,14 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1]>=mem[arg2]); if (arg1==arg2) _cimg_mp_return(1); _cimg_mp_scalar2(mp_gte,arg1,arg2); } @@ -16565,27 +18347,29 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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]ss; --s, --ns, --ps) - if (*s=='>' && *ns!='>' && *ps!='>' && level[s - expr._data]==clevel) { // Greather than ('>') + if (*s=='>' && *ns!='>' && *ps!='>' && level[s - expr._data]==clevel) { // Greater than ('>') _cimg_mp_op("Operator '>'"); - arg1 = compile(ss,s,depth1,0,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1]>mem[arg2]); if (arg1==arg2) _cimg_mp_return(0); _cimg_mp_scalar2(mp_gt,arg1,arg2); } @@ -16593,8 +18377,8 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_bitwise_left_shift,arg1,arg2); @@ -16604,8 +18388,8 @@ namespace cimg_library_suffixed { } 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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar((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); @@ -16614,8 +18398,8 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) _cimg_mp_vector2_vv(mp_bitwise_right_shift,arg1,arg2); @@ -16625,8 +18409,8 @@ namespace cimg_library_suffixed { } 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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar((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); @@ -16639,16 +18423,17 @@ namespace cimg_library_suffixed { *(ps - 1)<='9')))) && level[s - expr._data]==clevel) { // Addition ('+') _cimg_mp_op("Operator '+'"); - arg1 = compile(ss,s,depth1,0,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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'. + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1] + mem[arg2]); + if (code) { // Try to spot linear case 'a*b + c' CImg &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]; @@ -16671,8 +18456,8 @@ namespace cimg_library_suffixed { *(ps - 1)<='9')))) && level[s - expr._data]==clevel) { // Subtraction ('-') _cimg_mp_op("Operator '-'"); - arg1 = compile(ss,s,depth1,0,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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); @@ -16681,9 +18466,10 @@ namespace cimg_library_suffixed { 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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(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'. + if (code) { // Try to spot linear cases 'a*b - c' and 'c - a*b' CImg &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]; @@ -16702,8 +18488,8 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); _cimg_mp_check_type(arg1,1,3,2); _cimg_mp_check_type(arg2,2,3,2); if (arg2==1) _cimg_mp_return(arg1); @@ -16711,11 +18497,13 @@ namespace cimg_library_suffixed { if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) { pos = vector(2); CImg::vector((ulongT)mp_complex_mul,pos,arg1,arg2).move_to(code); + return_new_comp = true; _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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1]*mem[arg2]); if (!arg1 || !arg2) _cimg_mp_return(0); _cimg_mp_scalar2(mp_mul,arg1,arg2); } @@ -16723,35 +18511,39 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); _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::vector((ulongT)mp_complex_div_vv,pos,arg1,arg2).move_to(code); + return_new_comp = true; _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::vector((ulongT)mp_complex_div_sv,pos,arg1,arg2).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } - if (_cimg_mp_is_constant(arg1) && _cimg_mp_is_constant(arg2)) _cimg_mp_constant(mem[arg1]/mem[arg2]); + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(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,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); p2 = _cimg_mp_size(arg2); - if (p2>0 && _cimg_mp_size(arg1)==p2*p2) { // Particular case of matrix multiplication + if (p2>0 && (ulongT)_cimg_mp_size(arg1)==(ulongT)p2*p2) { // Particular case of matrix multiplication pos = vector(p2); CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,p2,p2,1).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } _cimg_mp_check_type(arg2,2,3,_cimg_mp_size(arg1)); @@ -16760,9 +18552,10 @@ namespace cimg_library_suffixed { 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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1]*mem[arg2]); - if (code) { // Try to spot double multiplication 'a*b*c'. + if (code) { // Try to spot double multiplication 'a*b*c' CImg &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]; @@ -16779,14 +18572,15 @@ namespace cimg_library_suffixed { for (s = se2; s>ss; --s) if (*s=='/' && level[s - expr._data]==clevel) { // Division ('/') _cimg_mp_op("Operator '/'"); - arg1 = compile(ss,s,depth1,0,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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 (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(mem[arg1]/mem[arg2]); if (!arg1) _cimg_mp_return(0); _cimg_mp_scalar2(mp_div,arg1,arg2); } @@ -16794,50 +18588,50 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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])); + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(cimg::mod(mem[arg1],mem[arg2])); _cimg_mp_scalar2(mp_modulo,arg1,arg2); } if (se1>ss) { if (*ss=='+' && (*ss1!='+' || (ss2='0' && *ss2<='9'))) { // Unary plus ('+') _cimg_mp_op("Operator '+'"); - _cimg_mp_return(compile(ss1,se,depth1,0,is_single)); + _cimg_mp_return(compile(ss1,se,depth1,0,block_flags)); } if (*ss=='-' && (*ss1!='-' || (ss2='0' && *ss2<='9'))) { // Unary minus ('-') _cimg_mp_op("Operator '-'"); - arg1 = compile(ss1,se,depth1,0,is_single); + arg1 = compile(ss1,se,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_minus,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(-mem[arg1]); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(-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,is_single); + arg1 = compile(ss2,se,depth1,0,block_flags); 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]); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar((bool)mem[arg1]); _cimg_mp_scalar1(mp_bool,arg1); } - arg1 = compile(ss1,se,depth1,0,is_single); + arg1 = compile(ss1,se,depth1,0,block_flags); 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]); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(!mem[arg1]); _cimg_mp_scalar1(mp_logical_not,arg1); } if (*ss=='~') { // Bitwise not ('~') _cimg_mp_op("Operator '~'"); - arg1 = compile(ss1,se,depth1,0,is_single); + arg1 = compile(ss1,se,depth1,0,block_flags); 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]); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(~(unsigned int)mem[arg1]); _cimg_mp_scalar1(mp_bitwise_not,arg1); } } @@ -16845,47 +18639,43 @@ namespace cimg_library_suffixed { 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,is_single); - arg2 = compile(s + 2,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 2,se,depth1,0,block_flags); _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)) { + if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_vector(arg2)) CImg::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)) { + else if (_cimg_mp_is_vector(arg1) && _cimg_mp_is_scalar(arg2)) CImg::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)) { + else if (_cimg_mp_is_scalar(arg1) && _cimg_mp_is_vector(arg2)) CImg::vector((ulongT)mp_complex_pow_sv,pos,arg1,arg2).move_to(code); - _cimg_mp_return(pos); - } - CImg::vector((ulongT)mp_complex_pow_ss,pos,arg1,arg2).move_to(code); + else + CImg::vector((ulongT)mp_complex_pow_ss,pos,arg1,arg2).move_to(code); + return_new_comp = true; _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,is_single); - arg2 = compile(s + 1,se,depth1,0,is_single); + arg1 = compile(ss,s,depth1,0,block_flags); + arg2 = compile(s + 1,se,depth1,0,block_flags); _cimg_mp_check_type(arg2,2,3,_cimg_mp_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])); + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) + _cimg_mp_const_scalar(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 (_cimg_mp_is_const_scalar(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); } } @@ -16895,15 +18685,24 @@ namespace cimg_library_suffixed { // Percentage computation. if (*se1=='%') { - arg1 = compile(ss,se1,depth1,0,is_single); - arg2 = _cimg_mp_is_constant(arg1)?0:constant(100); + arg1 = compile(ss,se1,depth1,0,block_flags); + arg2 = _cimg_mp_is_const_scalar(arg1)?0:const_scalar(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); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(mem[arg1]/100); _cimg_mp_scalar2(mp_div,arg1,arg2); } + // Degree to radian postfix operator ('°' in UTF-8). + if (se2>ss && (unsigned char)*se2==0xC2 && (unsigned char)*se1==0xB0) { + arg1 = compile(ss,se2,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_deg2rad,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(mem[arg1]*cimg::PI/180); + _cimg_mp_scalar1(mp_deg2rad,arg1); + } + + // Pre/post-decrement and increment. is_sth = ss1ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { // Pre/post-decrement and increment + if (is_sth || (se2>ss && (*se1=='+' || *se1=='-') && *se2==*se1)) { if ((is_sth && *ss=='+') || (!is_sth && *se1=='+')) { _cimg_mp_op("Operator '++'"); op = mp_self_increment; @@ -16912,16 +18711,16 @@ namespace cimg_library_suffixed { op = mp_self_decrement; } ref.assign(7); - arg1 = is_sth?compile(ss2,se,depth1,ref,is_single): - compile(ss,se2,depth1,ref,is_single); // Variable slot + arg1 = is_sth?compile(ss2,se,depth1,ref,block_flags): + compile(ss,se2,depth1,ref,block_flags); // 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 (*ref && (_cimg_mp_is_const_scalar(arg1) || _cimg_mp_is_vector(arg1) || _cimg_mp_is_reserved(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 + if (is_sth) pos = arg1; // Determine return index, depending on pre/post action else { if (_cimg_mp_is_vector(arg1)) pos = vector_copy(arg1); else pos = scalar1(mp_copy,arg1); @@ -16932,20 +18731,20 @@ namespace cimg_library_suffixed { arg4 = ref[2]; // Index if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); CImg::vector((ulongT)op,arg1,1).move_to(code); - CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4,arg1). + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4). move_to(code); _cimg_mp_return(pos); } if (*ref==2) { // Image value (scalar): i/j[_#ind,off]++ - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) 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::vector((ulongT)op,arg1).move_to(code); if (p1!=~0U) { - if (!listout) _cimg_mp_return(pos); + if (!imglist) _cimg_mp_return(pos); CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), arg1,p1,arg3).move_to(code); } else { @@ -16957,7 +18756,7 @@ namespace cimg_library_suffixed { } if (*ref==3) { // Image value (scalar): i/j(_#ind,_x,_y,_z,_c)++ - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; p1 = ref[1]; // Index is_relative = (bool)ref[2]; arg3 = ref[3]; // X @@ -16967,7 +18766,7 @@ namespace cimg_library_suffixed { if (is_sth && p_ref) std::memcpy(p_ref,ref,ref._width*sizeof(unsigned int)); CImg::vector((ulongT)op,arg1).move_to(code); if (p1!=~0U) { - if (!listout) _cimg_mp_return(pos); + if (!imglist) _cimg_mp_return(pos); CImg::vector((ulongT)(is_relative?mp_list_set_jxyzc:mp_list_set_ixyzc), arg1,p1,arg3,arg4,arg5,arg6).move_to(code); } else { @@ -16979,14 +18778,14 @@ namespace cimg_library_suffixed { } if (*ref==4) { // Image value (vector): I/J[_#ind,off]++ - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) 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); + if (!imglist) _cimg_mp_return(pos); CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), arg1,p1,arg3,_cimg_mp_size(arg1)).move_to(code); } else { @@ -16998,7 +18797,7 @@ namespace cimg_library_suffixed { } if (*ref==5) { // Image value (vector): I/J(_#ind,_x,_y,_z,_c)++ - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; p1 = ref[1]; // Index is_relative = (bool)ref[2]; arg3 = ref[3]; // X @@ -17007,7 +18806,7 @@ namespace cimg_library_suffixed { 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); + if (!imglist) _cimg_mp_return(pos); CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), arg1,p1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); } else { @@ -17023,7 +18822,7 @@ namespace cimg_library_suffixed { _cimg_mp_return(pos); } - if (_cimg_mp_is_variable(arg1)) { // Scalar variable: s++ + if (_cimg_mp_is_reserved(arg1)) { // Scalar variable: s++ CImg::vector((ulongT)op,arg1).move_to(code); _cimg_mp_return(pos); } @@ -17032,39 +18831,43 @@ namespace cimg_library_suffixed { else variable_name.assign(ss,(unsigned int)(se1 - ss)); variable_name.back() = 0; cimg::strpare(variable_name,false,true); - *se = saved_char; + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: %s: Invalid %slvalue '%s', " - "in expression '%s%s%s'.", + "in expression '%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()?"...":""); + _cimg_mp_is_const_scalar(arg1)?"const ":"", + variable_name._data,s0); } - // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'. - if (*se1==']' && *ss!='[') { + // Array-like access to vectors and image values 'i/j/I/J[_#ind,offset,_boundary]' and 'vector[offset]'. + if (*se1==']') { _cimg_mp_op("Value accessor '[]'"); - is_relative = *ss=='j' || *ss=='J'; - s0 = s1 = std::strchr(ss,'['); if (s0) { 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 + // Find opening bracket for the offset. + s0 = se1; while (s0>ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + if (s0>ss) { s1 = s0; do { --s1; } while (cimg::is_blank(*s1)); cimg::swap(*s0,*++s1); } + is_sth=s0>ss && *(s0-1)==']'; // Particular case s.a. '..[..][..]' ? + is_relative = *ss=='j' || *ss=='J'; + + if (!is_sth && (*ss=='I' || *ss=='J') && *ss1=='[' && + (reserved_label[(int)*ss]==~0U || + !_cimg_mp_is_vector(reserved_label[(int)*ss]))) { // Image value as a vector if (*ss2=='#') { // Index specified s0 = ss3; while (s0::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff), @@ -17091,112 +18893,113 @@ namespace cimg_library_suffixed { CImg::vector((ulongT)(is_relative?mp_Joff:mp_Ioff), pos,arg1,arg2==~0U?_cimg_mp_boundary:arg2,p2).move_to(code); } + return_new_comp = true; _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 (!is_sth && (*ss=='i' || *ss=='j') && *ss1=='[' && + (reserved_label[(int)*ss]==~0U || + !_cimg_mp_is_vector(reserved_label[(int)*ss]))) { // Image value as a scalar if (*ss2=='#') { // Index specified s0 = ss3; while (s0ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; - if (s0>ss) { // Vector value - arg1 = compile(ss,s0,depth1,0,is_single); + if (s0>ss) { // Vector element + arg1 = compile(ss,s0,depth1,0,block_flags); if (_cimg_mp_is_scalar(arg1)) { variable_name.assign(ss,(unsigned int)(s0 - ss + 1)).back() = 0; - *se = saved_char; + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: %s: Array brackets used on non-vector variable '%s', " - "in expression '%s%s%s'.", + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, - variable_name._data, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); - + variable_name._data,s0); } s1 = s0 + 1; while (s1 sub-vector extraction + if (s1 sub-vector extraction p1 = _cimg_mp_size(arg1); - arg2 = compile(++s0,s1,depth1,0,is_single); // Starting indice - arg3 = compile(++s1,se1,depth1,0,is_single); // Length - _cimg_mp_check_constant(arg3,2,3); + arg2 = compile(++s0,s1,depth1,0,block_flags); // Starting index + s0 = ++s1; while (s0::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3).move_to(code); + CImg::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3,arg4).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } // One argument -> vector value reference - arg2 = compile(++s0,se1,depth1,0,is_single); - if (_cimg_mp_is_constant(arg2)) { // Constant index + arg2 = compile(++s0,se1,depth1,0,block_flags); + if (_cimg_mp_is_const_scalar(arg2)) { // Constant index nb = (int)mem[arg2]; if (nb>=0 && nb<(int)_cimg_mp_size(arg1)) _cimg_mp_return(arg1 + 1 + nb); variable_name.assign(ss,(unsigned int)(s0 - ss)).back() = 0; - *se = saved_char; + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: Out-of-bounds reference '%s[%d]' " "(vector '%s' has dimension %u), " - "in expression '%s%s%s'.", + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function, variable_name._data,nb, - variable_name._data,_cimg_mp_size(arg1), - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + variable_name._data,_cimg_mp_size(arg1),s0); } 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 + if (_cimg_mp_is_comp(arg2)) memtype[arg2] = -1; // Prevent from being used in further optimization } pos = scalar3(mp_vector_off,arg1,_cimg_mp_size(arg1),arg2); - memtype[pos] = -2; // Prevent from being used in further optimization + memtype[pos] = -1; // 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,is_single)); // Simple parentheses + if (*ss=='(') _cimg_mp_return(compile(ss1,se1,depth1,p_ref,block_flags)); // Simple parentheses _cimg_mp_op("Value accessor '()'"); is_relative = *ss=='j' || *ss=='J'; - s0 = s1 = std::strchr(ss,'('); if (s0) { do { --s1; } while ((signed char)*s1<=' '); cimg::swap(*s0,*++s1); } + s0 = s1 = std::strchr(ss,'('); if (s0) { do { --s1; } while (cimg::is_blank(*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::vector((ulongT)(is_relative?mp_list_Jxyz:mp_list_Ixyz), @@ -17263,6 +19066,7 @@ namespace cimg_library_suffixed { arg4==~0U?_cimg_mp_interpolation:arg4, arg5==~0U?_cimg_mp_boundary:arg5,p2).move_to(code); } + return_new_comp = true; _cimg_mp_return(pos); } @@ -17270,7 +19074,8 @@ namespace cimg_library_suffixed { if ((*ss=='i' || *ss=='j') && *ss1=='(') { // Image value as scalar if (*ss2=='#') { // Index specified s0 = ss3; while (s0::vector((ulongT)mp_abort,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } +#endif + if (!std::strncmp(ss,"abs(",4)) { // Absolute value _cimg_mp_op("Function 'abs()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::abs(mem[arg1])); _cimg_mp_scalar1(mp_abs,arg1); } + if (!std::strncmp(ss,"addr(",5)) { // Pointer address + _cimg_mp_op("Function 'addr()'"); + arg1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_const_scalar((double)arg1); + } + if (!std::strncmp(ss,"acos(",5)) { // Arccos _cimg_mp_op("Function 'acos()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::acos(mem[arg1])); _cimg_mp_scalar1(mp_acos,arg1); } if (!std::strncmp(ss,"acosh(",6)) { // Hyperbolic arccosine _cimg_mp_op("Function 'acosh()'"); - arg1 = compile(ss6,se1,depth1,0,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_acosh,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::acosh(mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::acosh(mem[arg1])); _cimg_mp_scalar1(mp_acosh,arg1); } if (!std::strncmp(ss,"asinh(",6)) { // Hyperbolic arcsine _cimg_mp_op("Function 'asinh()'"); - arg1 = compile(ss6,se1,depth1,0,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_asinh,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::asinh(mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::asinh(mem[arg1])); _cimg_mp_scalar1(mp_asinh,arg1); } if (!std::strncmp(ss,"atanh(",6)) { // Hyperbolic arctangent _cimg_mp_op("Function 'atanh()'"); - arg1 = compile(ss6,se1,depth1,0,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_atanh,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::atanh(mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::atanh(mem[arg1])); _cimg_mp_scalar1(mp_atanh,arg1); } - if (!std::strncmp(ss,"arg(",4)) { // Nth argument - _cimg_mp_op("Function 'arg()'"); - s1 = ss4; while (s1::vector((ulongT)mp_arg,0,0,p2,arg1,arg2).move_to(l_opcode); + CImg::vector((ulongT)(*ss3=='0'?mp_arg0:mp_arg),0,0,p2,arg1,arg2).move_to(l_opcode); for (s = ++s2; s::vector(arg3).move_to(l_opcode); ++p3; @@ -17416,62 +19236,57 @@ namespace cimg_library_suffixed { } (l_opcode>'y').move_to(opcode); opcode[2] = opcode._height; - if (_cimg_mp_is_constant(arg1)) { + if (_cimg_mp_is_const_scalar(arg1)) { p3-=1; // Number of args - arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]); + if (*ss3=='0') arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1] + 1); + else arg1 = (unsigned int)(mem[arg1]<0?mem[arg1] + p3:mem[arg1]); if (arg1::vector((ulongT)mp_break,_cimg_mp_slot_nan).move_to(code); _cimg_mp_return_nan(); @@ -17485,90 +19300,221 @@ namespace cimg_library_suffixed { _cimg_mp_return_nan(); } } + + if (!std::strncmp(ss,"bool(",5)) { // Boolean cast + _cimg_mp_op("Function 'bool()'"); + arg1 = compile(ss5,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_bool,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar((bool)mem[arg1]); + _cimg_mp_scalar1(mp_bool,arg1); + } + + if (!std::strncmp(ss,"begin(",6)) { // Begin + _cimg_mp_op("Function 'begin()'"); + s1 = ss6; while (s1 Error too much arguments + if (p2>4) { + *s1 = 0; s1 = s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Argument '%s' is a vector of size %u (should be <=4), " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s1,p2,s0); + } + arg1 = pos + 1; + arg2 = p2>1?pos + 2:0; + arg3 = p2>2?pos + 3:0; + arg4 = p2>3?pos + 4:0; + } else { // Coordinates specified as scalars + arg1 = pos; arg2 = arg3 = arg4 = 0; + if (s1::vector((ulongT)mp_cats,0,0,0).move_to(l_opcode); - arg1 = 0; - for (s = ss5; s::vector(arg1,_cimg_mp_size(arg1)).move_to(l_opcode); - s = ns; - } - _cimg_mp_check_constant(arg1,1,3); // Last argument = output vector size - l_opcode.remove(); - (l_opcode>'y').move_to(opcode); - p1 = (unsigned int)mem[arg1]; - pos = vector(p1); - opcode[1] = pos; - opcode[2] = p1; - opcode[3] = opcode._height; - opcode.move_to(code); - _cimg_mp_return(pos); - } - if (!std::strncmp(ss,"cbrt(",5)) { // Cubic root _cimg_mp_op("Function 'cbrt()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); - _cimg_mp_check_type(arg1,0,2,2); + arg1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); pos = vector(2); - CImg::vector((ulongT)mp_complex_conj,pos,arg1).move_to(code); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_conj,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_conj,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"ceil(",5)) { // Ceil _cimg_mp_op("Function 'ceil()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_ceil,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::ceil(mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::ceil(mem[arg1])); _cimg_mp_scalar1(mp_ceil,arg1); } if (!std::strncmp(ss,"cexp(",5)) { // Complex exponential _cimg_mp_op("Function 'cexp()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); - _cimg_mp_check_type(arg1,0,2,2); + arg1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); pos = vector(2); - CImg::vector((ulongT)mp_complex_exp,pos,arg1).move_to(code); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_exp,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_exp,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"clog(",5)) { // Complex logarithm _cimg_mp_op("Function 'clog()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); - _cimg_mp_check_type(arg1,0,2,2); + arg1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); pos = vector(2); - CImg::vector((ulongT)mp_complex_log,pos,arg1).move_to(code); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_log,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_log,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } - if (!std::strncmp(ss,"continue(",9)) { // Complex absolute value + if (!std::strncmp(ss,"ccos(",5)) { // Complex cosine + _cimg_mp_op("Function 'ccos()'"); + arg1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_cos,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_cos,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"csin(",5)) { // Complex sine + _cimg_mp_op("Function 'csin()'"); + arg1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_sin,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_sin,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"csqrt(",6)) { // Complex square root + _cimg_mp_op("Function 'csqrt()'"); + arg1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_sqrt,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_sqrt,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ctan(",5)) { // Complex tangent + _cimg_mp_op("Function 'ctan()'"); + arg1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_tan,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_tan,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ccosh(",6)) { // Complex hyperbolic cosine + _cimg_mp_op("Function 'ccosh()'"); + arg1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_cosh,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_cosh,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"csinh(",6)) { // Complex hyperbolic sine + _cimg_mp_op("Function 'csinh()'"); + arg1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_sinh,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_sinh,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ctanh(",6)) { // Complex hyperbolic tangent + _cimg_mp_op("Function 'ctanh()'"); + arg1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg::vector((ulongT)mp_complex_tanh,pos,arg1,0).move_to(code); + else CImg::vector((ulongT)mp_complex_tanh,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"continue(",9)) { // Continue loop if (pexpr[se2 - expr._data]=='(') { // no arguments? CImg::vector((ulongT)mp_continue,_cimg_mp_slot_nan).move_to(code); _cimg_mp_return_nan(); @@ -17579,29 +19525,35 @@ namespace cimg_library_suffixed { _cimg_mp_op("Function 'copy()'"); ref.assign(14); s1 = ss5; while (s1=4 && arg4==~0U) arg4 = scalar1(mp_image_whd,ref[1]); + } if (_cimg_mp_is_vector(arg2)) { - if (arg3==~0U) arg3 = _cimg_mp_size(arg2); + if (arg3==~0U) arg3 = const_scalar(_cimg_mp_size(arg2)); if (!ref[7]) ++arg2; + if (ref[7]>=4 && arg5==~0U) arg5 = scalar1(mp_image_whd,ref[8]); } if (arg3==~0U) arg3 = 1; + if (arg4==~0U) arg4 = 1; + if (arg5==~0U) arg5 = 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); @@ -17609,233 +19561,567 @@ namespace cimg_library_suffixed { CImg(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); + _cimg_mp_return_nan(); } if (!std::strncmp(ss,"cos(",4)) { // Cosine _cimg_mp_op("Function 'cos()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::cosh(mem[arg1])); _cimg_mp_scalar1(mp_cosh,arg1); } + if (!std::strncmp(ss,"cov(",4)) { // Covariance + _cimg_mp_op("Function 'cov()'"); + s1 = ss4; while (s1::vector((ulongT)mp_critical,arg1,code._width - p1).move_to(code,p1); _cimg_mp_return(arg1); } - if (!std::strncmp(ss,"crop(",5)) { // Image crop + if (!std::strncmp(ss,"crop(",5)) { // Image or vector crop _cimg_mp_op("Function 'crop()'"); + is_sth = false; // is image crop ? + arg1 = 0; if (*ss5=='#') { // Index specified s0 = ss6; while (s0::sequence(_cimg_mp_size(arg1),arg1 + 1, - arg1 + (ulongT)_cimg_mp_size(arg1)); - opcode.resize(1,std::min(opcode._height,4U),1,1,0).move_to(l_opcode); - is_sth = true; - } else { - _cimg_mp_check_type(arg1,pos + 1,1,0); - CImg::vector(arg1).move_to(l_opcode); - } + arg1 = compile(s,ns,depth1,0,block_flags); + if (pos==1 && _cimg_mp_is_scalar(arg1)) is_sth = true; + else if (pos>1) _cimg_mp_check_type(arg1,pos,1,0); + CImg::vector(arg1).move_to(l_opcode); s = ns; } (l_opcode>'y').move_to(opcode); - arg1 = 0; arg2 = (p1!=~0U); - switch (opcode._height) { - case 0 : case 1 : - CImg::vector(0,0,0,0,~0U,~0U,~0U,~0U,0).move_to(opcode); - break; - case 2 : - CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,_cimg_mp_boundary).move_to(opcode); - arg1 = arg2?3:2; - break; - case 3 : - CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode); - arg1 = arg2?3:2; - break; - case 4 : - CImg::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::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::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::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::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_appname "_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 &img = p1!=~0U?listin[p1]:imgin; - if (!img) { - *se = saved_char; - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); + if (!opcode || is_sth) { // Image crop + arg1 = 0; arg2 = (p1!=~0U); + switch (opcode._height) { + case 0 : case 1 : + CImg::vector(0,0,0,0,~0U,~0U,~0U,~0U,0).move_to(opcode); + break; + case 2 : + CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,_cimg_mp_boundary).move_to(opcode); + arg1 = 2 + arg2; break; + case 3 : + CImg::vector(*opcode,0,0,0,opcode[1],~0U,~0U,~0U,opcode[2]).move_to(opcode); + arg1 = 2 + arg2; break; + case 4 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,_cimg_mp_boundary). + move_to(opcode); + arg1 = 3 + arg2; break; + case 5 : + CImg::vector(*opcode,opcode[1],0,0,opcode[2],opcode[3],~0U,~0U,opcode[4]). + move_to(opcode); + arg1 = 3 + arg2; break; + case 6 : + CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, + _cimg_mp_boundary).move_to(opcode); + arg1 = 4 + arg2; break; + case 7 : + CImg::vector(*opcode,opcode[1],opcode[2],0,opcode[3],opcode[4],opcode[5],~0U, + opcode[6]).move_to(opcode); + arg1 = 4 + arg2; break; + case 8 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4],opcode[5],opcode[6], + opcode[7],_cimg_mp_boundary).move_to(opcode); + arg1 = 5 + arg2; break; + case 9 : + arg1 = 5 + arg2; break; + default : // Error -> too much arguments + _cimg_mp_strerr; throw CImgArgumentException("[" cimg_appname "_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()?"...":""); + "CImg<%s>::%s: %s: Too much arguments specified, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s0); } - 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::vector((ulongT)mp_crop, - pos,p1, - *opcode,opcode[1],opcode[2],opcode[3], - opcode[4],opcode[5],opcode[6],opcode[7], - opcode[8]).move_to(code); + if (opcode[4]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((unsigned int)opcode[4],arg1,3); + opcode[4] = (ulongT)mem[opcode[4]]; + } + if (opcode[5]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((unsigned int)opcode[5],arg1 + 1,3); + opcode[5] = (ulongT)mem[opcode[5]]; + } + if (opcode[6]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((unsigned int)opcode[6],arg1 + 2,3); + opcode[6] = (ulongT)mem[opcode[6]]; + } + if (opcode[7]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((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) { + p2 = 0; + if (p1!=~0U) { + _cimg_mp_check_const_scalar(p1,1,1); + p2 = (unsigned int)cimg::mod((int)mem[p1],imglist.width()); + } + const CImg &img = p1!=~0U?imglist[p2]:imgin; + if (!img) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Cannot crop empty image when " + "some xyzc-coordinates are unspecified, in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s0); + } + 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::vector((ulongT)mp_image_crop, + pos,p1, // 1-2: res,#ind + *opcode,opcode[1],opcode[2],opcode[3], // 3-6: x,y,z,c + opcode[4],opcode[5],opcode[6],opcode[7], // 7-10: dx,dy,dz,dc + opcode[8]).move_to(code); // 11: boundary conditions + + } else { // Vector crop + switch (opcode._height) { + case 5 : case 6 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + 0,0,0,0,~0U,~0U,~0U,~0U,0).move_to(opcode); + break; + case 7 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + opcode[5],0,0,0,opcode[6],~0U,~0U,~0U,_cimg_mp_boundary).move_to(opcode); + arg1 = 7; break; + case 8 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + opcode[5],0,0,0,opcode[6],~0U,~0U,~0U,opcode[7]).move_to(opcode); + arg1 = 7; break; + case 9 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + opcode[5],opcode[6],0,0,opcode[7],opcode[8],~0U,~0U,_cimg_mp_boundary). + move_to(opcode); + arg1 = 8; break; + case 10 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + opcode[5],opcode[6],0,0,opcode[7],opcode[8],~0U,~0U,opcode[9]). + move_to(opcode); + arg1 = 8; break; + case 11 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + opcode[5],opcode[6],opcode[7],0,opcode[8],opcode[9],opcode[10],~0U, + _cimg_mp_boundary).move_to(opcode); + arg1 = 9; break; + case 12 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + opcode[5],opcode[6],opcode[7],0,opcode[8],opcode[9],opcode[10],~0U, + opcode[11]).move_to(opcode); + arg1 = 9; break; + case 13 : + CImg::vector(*opcode,opcode[1],opcode[2],opcode[3],opcode[4], + opcode[5],opcode[6],opcode[7],opcode[8],opcode[9],opcode[10],opcode[11], + opcode[12],_cimg_mp_boundary).move_to(opcode); + arg1 = 10; break; + case 14 : + arg1 = 10; break; + default : // Error -> too few or too much arguments + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Too %s arguments specified, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + opcode._height<5?"few":"much",s0); + } + + _cimg_mp_check_const_scalar((unsigned int)opcode[1],2,3); // w + opcode[1] = (ulongT)mem[opcode[1]]; + _cimg_mp_check_const_scalar((unsigned int)opcode[2],3,3); // h + opcode[2] = (ulongT)mem[opcode[2]]; + _cimg_mp_check_const_scalar((unsigned int)opcode[3],4,3); // d + opcode[3] = (ulongT)mem[opcode[3]]; + _cimg_mp_check_const_scalar((unsigned int)opcode[4],5,3); // s + opcode[4] = (ulongT)mem[opcode[4]]; + p1 = _cimg_mp_size((unsigned int)opcode[0]); + arg2 = (unsigned int)opcode[1]; + arg3 = (unsigned int)opcode[2]; + arg4 = (unsigned int)opcode[3]; + arg5 = (unsigned int)opcode[4]; + if (arg2*arg3*arg4*arg5!=p1) + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Input vector size (%lu values) and its specified " + "geometry (%u,%u,%u,%u) (%lu values) do not match.", + pixel_type(),_cimg_mp_calling_function,s_op, + p1,arg2,arg3,arg4,arg5,(ulongT)arg2*arg3*arg4*arg5); + + if (opcode[9]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((unsigned int)opcode[9],arg1,3); + opcode[9] = (ulongT)mem[opcode[9]]; + } else opcode[9] = opcode[1]; + if (opcode[10]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((unsigned int)opcode[10],arg1 + 1,3); + opcode[10] = (ulongT)mem[opcode[10]]; + } else opcode[10] = opcode[2]; + if (opcode[11]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((unsigned int)opcode[11],arg1 + 2,3); + opcode[11] = (ulongT)mem[opcode[11]]; + } else opcode[11] = opcode[3]; + if (opcode[12]!=(ulongT)~0U) { + _cimg_mp_check_const_scalar((unsigned int)opcode[12],arg1 + 3,3); + opcode[12] = (ulongT)mem[opcode[12]]; + } else opcode[12] = opcode[4]; + _cimg_mp_check_type((unsigned int)opcode[13],arg1 + 4,1,0); + + pos = vector((unsigned int)(opcode[9]*opcode[10]*opcode[11]*opcode[12])); + CImg::vector((ulongT)mp_vector_crop_ext, + pos,*opcode, // 1-2: res,S + opcode[1],opcode[2],opcode[3],opcode[4], // 3-6: w,h,d,s + opcode[5],opcode[6],opcode[7],opcode[8], // 7-10: x,y,z,c + opcode[9],opcode[10],opcode[11],opcode[12], // 11-14: dx,dy,dz,dc + opcode[13]).move_to(code); // 15: boundary conditions + } + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"cross(",6)) { // Cross product _cimg_mp_op("Function 'cross()'"); s1 = ss6; while (s1::vector((ulongT)mp_cross,pos,arg1,arg2).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"cut(",4)) { // Cut _cimg_mp_op("Function 'cut()'"); s1 = ss4; while (s1val2?val2:val); + _cimg_mp_const_scalar(valval2?val2:val); } _cimg_mp_scalar3(mp_cut,arg1,arg2,arg3); } + + if (!std::strncmp(ss,"convolve(",9) || !std::strncmp(ss,"correlate(",10)) { // Convolve & Correlate + is_sth = *ss2=='n'; // is_convolve? + _cimg_mp_op(is_sth?"Function 'convolve()'":"Function 'correlate()'"); + op = is_sth?mp_convolve:mp_correlate; + const ulongT default_params[] = { (ulongT)op,0, // [0]=function, [1]=result vector + 0,0,0,0,0, // [2]=A, [3]=wA, [4]=hA, [5]=dA, [6]=sA + 0,0,0,0,0, // [7]=M, [8]=wM, [9]=hM, [10]=dM, [11]=sM + 1,0,1, // [12]=boundary_conditions, [13]=is_normalized, [14]=chan._mode + ~0U,~0U,~0U, // [15]=xcenter, [16]=ycenter, [17]=zcenter + 0,0,0, // [18]=xstart, [19]=ystart, [20]=zstart + ~0U,~0U,~0U, // [21]=xend, [22]=yend, [23]=zend + 1,1,1, // [24]=xstride, [25]=ystride, [26]=zstride + 1,1,1, // [27]=xdilation, [28]=ydilation, [29]=zdilation, + 0 }; // [30]=interpolation_type + + l_opcode.assign(); // Don't use 'opcode': it could be modified by further calls to 'compile()'! + CImg(default_params,1,sizeof(default_params)/sizeof(ulongT)).move_to(l_opcode); + + arg1 = 2; + for (s = std::strchr(ss,'(') + 1; sopcode._height) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: %s arguments provided, in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + arg1<12?"Not enough":"Too much",s0); + } + _cimg_mp_check_type(opcode[2],1,2,0); // A + _cimg_mp_check_const_scalar(opcode[3],2,3); // wA + _cimg_mp_check_const_scalar(opcode[4],3,3); // hA + _cimg_mp_check_const_scalar(opcode[5],4,3); // dA + _cimg_mp_check_const_scalar(opcode[6],5,3); // sA + _cimg_mp_check_type(opcode[7],6,2,0); // M + _cimg_mp_check_const_scalar(opcode[8],7,3); // wM + _cimg_mp_check_const_scalar(opcode[9],8,3); // hM + _cimg_mp_check_const_scalar(opcode[10],9,3); // dM + _cimg_mp_check_const_scalar(opcode[11],10,3); // sM + _cimg_mp_check_type(opcode[12],11,1,0); // boundary_conditions + _cimg_mp_check_type(opcode[13],12,1,0); // is_normalized + _cimg_mp_check_const_scalar(opcode[14],13,1); // channel_mode + if (opcode[15]!=~0U) _cimg_mp_check_type(opcode[15],14,1,0); // xcenter + if (opcode[16]!=~0U) _cimg_mp_check_type(opcode[16],15,1,0); // ycenter + if (opcode[17]!=~0U) _cimg_mp_check_type(opcode[17],16,1,0); // zcenter + _cimg_mp_check_const_scalar(opcode[18],17,1); // xstart + _cimg_mp_check_const_scalar(opcode[19],18,1); // ystart + _cimg_mp_check_const_scalar(opcode[20],19,1); // zstart + if (opcode[21]!=~0U) _cimg_mp_check_const_scalar(opcode[21],20,1); // xend + if (opcode[22]!=~0U) _cimg_mp_check_const_scalar(opcode[22],21,1); // yend + if (opcode[23]!=~0U) _cimg_mp_check_const_scalar(opcode[23],22,1); // zend + _cimg_mp_check_const_scalar(opcode[24],23,0); // xstride + _cimg_mp_check_const_scalar(opcode[25],24,0); // ystride + _cimg_mp_check_const_scalar(opcode[26],25,0); // zstride + _cimg_mp_check_type(opcode[27],26,1,0); // xdilation + _cimg_mp_check_type(opcode[28],27,1,0); // ydilation + _cimg_mp_check_type(opcode[29],28,1,0); // zdilation + _cimg_mp_check_type(opcode[30],29,1,0); // interpolation_type + + const unsigned int + wA = (unsigned int)mem[opcode[3]], + hA = (unsigned int)mem[opcode[4]], + dA = (unsigned int)mem[opcode[5]], + sA = (unsigned int)mem[opcode[6]], + wM = (unsigned int)mem[opcode[8]], + hM = (unsigned int)mem[opcode[9]], + dM = (unsigned int)mem[opcode[10]], + sM = (unsigned int)mem[opcode[11]], + channel_mode = (unsigned int)mem[opcode[14]]; + const int + xstart = (int)mem[opcode[18]], + ystart = (int)mem[opcode[19]], + zstart = (int)mem[opcode[20]], + xend = opcode[21]!=~0U?(int)mem[opcode[21]]:wA - 1, + yend = opcode[22]!=~0U?(int)mem[opcode[22]]:hA - 1, + zend = opcode[23]!=~0U?(int)mem[opcode[23]]:dA - 1; + + if (xstart>xend || ystart>yend || zstart>zend) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid xyz-start/end arguments " + "(start = (%d,%d,%d), end = (%d,%d,%d)), in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + xstart,ystart,zstart,xend,yend,zend,s0); + } + + const float + xstride = (float)mem[opcode[24]], + ystride = (float)mem[opcode[25]], + zstride = (float)mem[opcode[26]]; + + if (xstride<=0 || ystride<=0 || zstride<=0) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid stride arguments (%g,%g,%g), " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + xstride,ystride,zstride,s0); + } + + arg2 = xend - xstart + 1; + arg3 = yend - ystart + 1; + arg4 = zend - zstart + 1; + arg5 = !channel_mode?sA*sM:channel_mode==1?std::max(sA,sM): + channel_mode==2?std::max(sA,sM)/std::min(sA,sM):1U; + + opcode[1] = pos = vector(arg2*arg3*arg4*arg5); + opcode[3] = (ulongT)wA; + opcode[4] = (ulongT)hA; + opcode[5] = (ulongT)dA; + opcode[6] = (ulongT)sA; + opcode[8] = (ulongT)wM; + opcode[9] = (ulongT)hM; + opcode[10] = (ulongT)dM; + opcode[11] = (ulongT)sM; + opcode[14] = (ulongT)channel_mode; + opcode[18] = (ulongT)xstart; + opcode[19] = (ulongT)ystart; + opcode[20] = (ulongT)zstart; + opcode[21] = (ulongT)xend; + opcode[22] = (ulongT)yend; + opcode[23] = (ulongT)zend; + opcode.move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } break; case 'd' : if (*ss1=='(') { // Image depth _cimg_mp_op("Function 'd()'"); if (*ss2=='#') { // Index specified - p1 = compile(ss3,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } - pos = scalar(); - CImg::vector((ulongT)mp_image_d,pos,p1).move_to(code); + _cimg_mp_scalar1(mp_image_d,p1); + } + + if (!std::strncmp(ss,"da_back(",8) || + !std::strncmp(ss,"da_pop(",7)) { // Get latest element in a dynamic array + if (!is_inside_critical) is_parallelizable = false; + const bool is_pop = *ss3=='p'; + _cimg_mp_op(is_pop?"Function 'da_pop()'":"Function 'da_back()'"); + s0 = ss + (is_pop?7:8); + if (*s0=='#') { // Index specified + s1 = ++s0; while (s11) pos = vector(p2); else pos = scalar(); // Return vector or scalar result + CImg::vector((ulongT)mp_da_back_or_pop,pos,p2,p1,is_pop).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } + if (!std::strncmp(ss,"da_insert(",10) || + !std::strncmp(ss,"da_push(",8)) { // Insert element(s) in a dynamic array + if (!is_inside_critical) is_parallelizable = false; + const bool is_push = *ss3=='p'; + _cimg_mp_op(is_push?"Function 'da_push()'":"Function 'da_insert()'"); + s0 = ss + (is_push?8:10); + if (*s0=='#') { // Index specified + s1 = ++s0; while (s1::vector((ulongT)mp_da_insert_or_push,_cimg_mp_slot_nan,p1,arg1,0,0).move_to(l_opcode); + p3 = p1==~0U?2:3; + p1 = ~0U; + for (s = s1; s::vector(arg2).move_to(l_opcode); + s = ns; + ++p3; + } + if (p1==~0U) compile(++s1,se1,depth1,0,block_flags); // Missing element -> error + (l_opcode>'y').move_to(opcode); + opcode[4] = p1; + opcode[5] = opcode._height; + opcode.move_to(code); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"da_freeze(",10)) { // Freeze dynamic array + if (!is_inside_critical) is_parallelizable = false; + _cimg_mp_op("Function 'da_freeze()'"); + s0 = ss + 10; + if (*s0=='#') { // Index specified + s1 = ++s0; while (s1::vector((ulongT)mp_da_freeze,_cimg_mp_slot_nan,p1).move_to(code); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"da_remove(",10)) { // Remove element(s) in a dynamic array + if (!is_inside_critical) is_parallelizable = false; + _cimg_mp_op("Function 'da_remove()'"); + if (ss[10]=='#') { // Index specified + s0 = ss + 11; while (s0::vector((ulongT)mp_da_remove,_cimg_mp_slot_nan,p1,arg1,arg2).move_to(code); + _cimg_mp_return_nan(); + } + + if (!std::strncmp(ss,"da_size(",8)) { // Size of a dynamic array + if (!is_inside_critical) is_parallelizable = false; + _cimg_mp_op("Function 'da_size()'"); + if (ss[8]=='#') { // Index specified + s0 = ss + 9; while (s0::string(s1,true,true).unroll('y'),true); - cimg::strpare(variable_name,false,true); - ((CImg::vector((ulongT)mp_date,pos,0,arg1,_cimg_mp_size(pos)),variable_name)>'y'). - move_to(opcode); - *se1 = ')'; - } else - CImg::vector((ulongT)mp_date,pos,0,arg1,_cimg_mp_size(pos)).move_to(opcode); - opcode[2] = opcode._height; - opcode.move_to(code); + CImg::vector((ulongT)mp_date,pos,_cimg_mp_size(pos), + arg1,arg1==~0U?~0U:_cimg_mp_size(arg1), + arg2,arg2==~0U?~0U:_cimg_mp_size(arg2)).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"debug(",6)) { // Print debug info _cimg_mp_op("Function 'debug()'"); p1 = code._width; - arg1 = compile(ss6,se1,depth1,p_ref,is_single); + arg1 = compile(ss6,se1,depth1,p_ref,block_flags); *se1 = 0; variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); cimg::strpare(variable_name,false,true); @@ -17847,6 +20133,14 @@ namespace cimg_library_suffixed { _cimg_mp_return(arg1); } + if (!std::strncmp(ss,"deg2rad(",8)) { // Degrees to radians + _cimg_mp_op("Function 'deg2rad()'"); + arg1 = compile(ss8,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_deg2rad,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(mem[arg1]*cimg::PI/180); + _cimg_mp_scalar1(mp_deg2rad,arg1); + } + if (!std::strncmp(ss,"display(",8)) { // Display memory, vector or image _cimg_mp_op("Function 'display()'"); if (pexpr[se2 - expr._data]=='(') { // no arguments? @@ -17855,18 +20149,18 @@ namespace cimg_library_suffixed { } if (*ss8!='#') { // Vector s1 = ss8; while (s1::vector((ulongT)mp_image_display,_cimg_mp_slot_nan,p1).move_to(code); _cimg_mp_return_nan(); } @@ -17905,7 +20199,7 @@ namespace cimg_library_suffixed { if (!std::strncmp(ss,"det(",4)) { // Matrix determinant _cimg_mp_op("Function 'det()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); _cimg_mp_check_matrix_square(arg1,1); p1 = (unsigned int)cimg::round(std::sqrt((float)_cimg_mp_size(arg1))); _cimg_mp_scalar2(mp_det,arg1,p1); @@ -17913,156 +20207,295 @@ namespace cimg_library_suffixed { if (!std::strncmp(ss,"diag(",5)) { // Diagonal matrix _cimg_mp_op("Function 'diag()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); - if (_cimg_mp_is_scalar(arg1)) _cimg_mp_return(arg1); - p1 = _cimg_mp_size(arg1); - pos = vector(p1*p1); - CImg::vector((ulongT)mp_diag,pos,arg1,p1).move_to(code); + CImg::vector((ulongT)mp_diag,0,0).move_to(l_opcode); + for (s = ss5; s::sequence(_cimg_mp_size(arg2),arg2 + 1, + arg2 + (ulongT)_cimg_mp_size(arg2)). + move_to(l_opcode); + else CImg::vector(arg2).move_to(l_opcode); + s = ns; + } + (l_opcode>'y').move_to(opcode); + arg1 = opcode._height - 3; + pos = vector(arg1*arg1); + opcode[1] = pos; + opcode[2] = opcode._height; + opcode.move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"dot(",4)) { // Dot product _cimg_mp_op("Function 'dot()'"); s1 = ss4; while (s1::vector((ulongT)mp_dowhile,p1,p2,arg2 - arg1,code._width - arg2,_cimg_mp_size(p1), - p1>=arg6 && !_cimg_mp_is_constant(p1), - p2>=arg6 && !_cimg_mp_is_constant(p2)).move_to(code,arg1); + CImg::vector((ulongT)mp_do,p1,p2,arg2 - arg1,code._width - arg2,_cimg_mp_size(p1), + p1>=arg6 && !_cimg_mp_is_const_scalar(p1), + p2>=arg6 && !_cimg_mp_is_const_scalar(p2)).move_to(code,arg1); _cimg_mp_return(p1); } if (!std::strncmp(ss,"draw(",5)) { // Draw image - if (!is_single) is_parallelizable = false; _cimg_mp_op("Function 'draw()'"); if (*ss5=='#') { // Index specified s0 = ss6; while (s01) { - arg3 = arg2 + 1; - if (p2>2) { - arg4 = arg3 + 1; - if (p2>3) arg5 = arg4 + 1; - } - } - ++s0; - is_sth = true; + + for (s = s0; s::vector(arg1).move_to(l_opcode); + s = ns; + } + (l_opcode>'y').move_to(opcode); + + is_sth = p1==~0U && opcode._height>5 && _cimg_mp_is_vector((unsigned int)opcode[5]); // Is vector drawing? + if ((is_sth && (opcode._height<6 || opcode._height>17)) || + (!is_sth && (opcode._height<1 || opcode._height>12))) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Too %s arguments specified, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + opcode._height>12?"much":"few",s0); + } + + if (is_sth) { // Drawing in a vector + _cimg_mp_check_type((unsigned int)*opcode,1,2,0); // D + _cimg_mp_check_type((unsigned int)opcode[1],2,1,0); // w + _cimg_mp_check_type((unsigned int)opcode[2],3,1,0); // h + _cimg_mp_check_type((unsigned int)opcode[3],4,1,0); // d + _cimg_mp_check_type((unsigned int)opcode[4],5,1,0); // s + + if (opcode._height<8 || (opcode._height<10 && _cimg_mp_is_vector((unsigned int)opcode[7]))) { + // D,w,h,d,s,S[,opac,M,maxM] + if (opcode._height>6) _cimg_mp_check_type((unsigned int)opcode[6],7,1,0); // opac + if (opcode._height>8) _cimg_mp_check_type((unsigned int)opcode[8],9,1,0); // maxM + CImg::vector((ulongT)mp_vector_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode), // 1-2: D,sizD + opcode[1],opcode[2],opcode[3],opcode[4], // 3-6: w,h,d,s + opcode[5],_cimg_mp_size((unsigned int)opcode[5]), // 7-8: S,sizS + 0,0,0,0, // 9-12: x,y,z,c + ~0U,~0U,~0U,~0U, // 13-16: dx,dy,dz,dc + opcode._height<7?1:opcode[6], // 17: opac + opcode._height<8?~0U:opcode[7], // 18: M + opcode._height<8?0:_cimg_mp_size((unsigned int)opcode[7]), // 19: sizM + opcode._height<9?1:opcode[8]).move_to(code); // 20: maxM + } else if (opcode._height<10 || (opcode._height<12 && _cimg_mp_is_vector((unsigned int)opcode[9]))) { + // D,w,h,d,s,S,x,dx[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[6],7,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[7],8,1,0); // dx + if (opcode._height>8) _cimg_mp_check_type((unsigned int)opcode[8],9,1,0); // opac + if (opcode._height>10) _cimg_mp_check_type((unsigned int)opcode[10],11,1,0); // maxM + CImg::vector((ulongT)mp_vector_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode), // 1-2: D,sizD + opcode[1],opcode[2],opcode[3],opcode[4], // 3-6: w,h,d,s + opcode[5],_cimg_mp_size((unsigned int)opcode[5]), // 7-8: S,sizS + opcode[6],0,0,0, // 9-12: x,y,z,c + opcode[7],~0U,~0U,~0U, // 13-16: dx,dy,dz,dc + opcode._height<9?1:opcode[8], // 17: opac + opcode._height<10?~0U:opcode[9], // 18: M + opcode._height<10?0:_cimg_mp_size((unsigned int)opcode[9]), // 19: sizM + opcode._height<11?1:opcode[10]).move_to(code); // 20: maxM + } else if (opcode._height<12 || (opcode._height<14 && _cimg_mp_is_vector((unsigned int)opcode[11]))) { + // D,w,h,d,s,S,x,y,dx,dy[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[6],7,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[7],8,1,0); // y + _cimg_mp_check_type((unsigned int)opcode[8],9,1,0); // dx + _cimg_mp_check_type((unsigned int)opcode[9],10,1,0); // dy + if (opcode._height>10) _cimg_mp_check_type((unsigned int)opcode[10],11,1,0); // opac + if (opcode._height>12) _cimg_mp_check_type((unsigned int)opcode[12],13,1,0); // maxM + CImg::vector((ulongT)mp_vector_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode), // 1-2: D,sizD + opcode[1],opcode[2],opcode[3],opcode[4], // 3-6: w,h,d,s + opcode[5],_cimg_mp_size((unsigned int)opcode[5]), // 7-8: S,sizS + opcode[6],opcode[7],0,0, // 9-12: x,y,z,c + opcode[8],opcode[9],~0U,~0U, // 13-16: dx,dy,dz,dc + opcode._height<11?1:opcode[10], // 17: opac + opcode._height<12?~0U:opcode[11], // 18: M + opcode._height<12?0:_cimg_mp_size((unsigned int)opcode[11]), // 19: sizM + opcode._height<13?1:opcode[12]).move_to(code); // 20: maxM + } else if (opcode._height<14 || (opcode._height<16 && _cimg_mp_is_vector((unsigned int)opcode[13]))) { + // D,w,h,d,s,S,x,y,z,dx,dy,dz[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[6],7,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[7],8,1,0); // y + _cimg_mp_check_type((unsigned int)opcode[8],9,1,0); // z + _cimg_mp_check_type((unsigned int)opcode[9],10,1,0); // dx + _cimg_mp_check_type((unsigned int)opcode[10],11,1,0); // dy + _cimg_mp_check_type((unsigned int)opcode[11],12,1,0); // dz + if (opcode._height>12) _cimg_mp_check_type((unsigned int)opcode[12],13,1,0); // opac + if (opcode._height>14) _cimg_mp_check_type((unsigned int)opcode[14],15,1,0); // maxM + CImg::vector((ulongT)mp_vector_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode), // 1-2: D,sizD + opcode[1],opcode[2],opcode[3],opcode[4], // 3-6: w,h,d,s + opcode[5],_cimg_mp_size((unsigned int)opcode[5]), // 7-8: S,sizS + opcode[6],opcode[7],opcode[8],0, // 9-12: x,y,z,c + opcode[9],opcode[10],opcode[11],~0U, // 13-16: dx,dy,dz,dc + opcode._height<13?1:opcode[12], // 17: opac + opcode._height<14?~0U:opcode[13], // 18: M + opcode._height<14?0:_cimg_mp_size((unsigned int)opcode[13]), // 19: sizM + opcode._height<15?1:opcode[14]).move_to(code); // 20: maxM + } else if (opcode._height<16 || (opcode._height<18 && _cimg_mp_is_vector((unsigned int)opcode[15]))) { + // D,w,h,d,s,S,x,y,z,c,dx,dy,dz,dc[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[6],7,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[7],8,1,0); // y + _cimg_mp_check_type((unsigned int)opcode[8],9,1,0); // z + _cimg_mp_check_type((unsigned int)opcode[9],10,1,0); // c + _cimg_mp_check_type((unsigned int)opcode[10],11,1,0); // dx + _cimg_mp_check_type((unsigned int)opcode[11],12,1,0); // dy + _cimg_mp_check_type((unsigned int)opcode[12],13,1,0); // dz + _cimg_mp_check_type((unsigned int)opcode[13],14,1,0); // dc + if (opcode._height>14) _cimg_mp_check_type((unsigned int)opcode[14],15,1,0); // opac + if (opcode._height>16) _cimg_mp_check_type((unsigned int)opcode[16],17,1,0); // maxM + CImg::vector((ulongT)mp_vector_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode), // 1-2: D,sizD + opcode[1],opcode[2],opcode[3],opcode[4], // 3-6: w,h,d,s + opcode[5],_cimg_mp_size((unsigned int)opcode[5]), // 7-8: S,sizS + opcode[6],opcode[7],opcode[8],opcode[9], // 9-12: x,y,z,c + opcode[10],opcode[11],opcode[12],opcode[13], // 13-16: dx,dy,dz,dc + opcode._height<15?1:opcode[14], // 17: opac + opcode._height<16?~0U:opcode[15], // 18: M + opcode._height<16?0:_cimg_mp_size((unsigned int)opcode[15]), // 19: sizM + opcode._height<17?1:opcode[16]).move_to(code); // 20: maxM } else { - if (s0::%s: %s: Invalid types in specified arguments, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s0); + } + + } else { // Drawing in an image + if (!is_inside_critical) is_parallelizable = false; + arg1 = p1!=~0U; + _cimg_mp_check_type((unsigned int)*opcode,1 + arg1,2,0); // S + if (opcode._height<3 || (opcode._height<5 && _cimg_mp_is_vector((unsigned int)opcode[2]))) { + // S[,opac,M,maxM] + if (opcode._height>1) _cimg_mp_check_type((unsigned int)opcode[1],2 + arg1,1,0); // opac + if (opcode._height>3) _cimg_mp_check_type((unsigned int)opcode[3],4 + arg1,1,0); // maxM + CImg::vector((ulongT)mp_image_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode),p1, // 1-3: S,sizS,#ind + 0,0,0,0, // 4-7: x,y,z,c + ~0U,~0U,~0U,~0U, // 8-11: dx,dy,dz,dc + opcode._height<2?1:opcode[1], // 12: opac + opcode._height<3?~0U:opcode[2], // 13: M + opcode._height<3?0:_cimg_mp_size((unsigned int)opcode[2]), // 14: sizM + opcode._height<4?1:opcode[3]).move_to(code); // 15: maxM + } else if (opcode._height<5 || (opcode._height<7 && _cimg_mp_is_vector((unsigned int)opcode[4]))) { + // x,dx,S[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[1],2 + arg1,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[2],3 + arg1,1,0); // dx + if (opcode._height>3) _cimg_mp_check_type((unsigned int)opcode[3],4 + arg1,1,0); // opac + if (opcode._height>5) _cimg_mp_check_type((unsigned int)opcode[5],6 + arg1,1,0); // maxM + CImg::vector((ulongT)mp_image_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode),p1, // 1-3: S,sizS,#ind + opcode[1],0,0,0, // 4-7: x,y,z,c + opcode[2],~0U,~0U,~0U, // 8-11: dx,dy,dz,dc + opcode._height<4?1:opcode[3], // 12: opac + opcode._height<5?~0U:opcode[4], // 13: M + opcode._height<5?0:_cimg_mp_size((unsigned int)opcode[4]), // 14: sizM + opcode._height<6?1:opcode[5]).move_to(code); // 15: maxM + } else if (opcode._height<7 || (opcode._height<9 && _cimg_mp_is_vector((unsigned int)opcode[6]))) { + // x,y,dx,dy,S[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[1],2 + arg1,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[2],3 + arg1,1,0); // y + _cimg_mp_check_type((unsigned int)opcode[3],4 + arg1,1,0); // dx + _cimg_mp_check_type((unsigned int)opcode[4],5 + arg1,1,0); // dy + if (opcode._height>5) _cimg_mp_check_type((unsigned int)opcode[5],6 + arg1,1,0); // opac + if (opcode._height>7) _cimg_mp_check_type((unsigned int)opcode[7],8 + arg1,1,0); // maxM + CImg::vector((ulongT)mp_image_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode),p1, // 1-3: S,sizS,#ind + opcode[1],opcode[2],0,0, // 4-7: x,y,z,c + opcode[3],opcode[4],~0U,~0U, // 8-11: dx,dy,dz,dc + opcode._height<6?1:opcode[5], // 12: opac + opcode._height<7?~0U:opcode[6], // 13: M + opcode._height<7?0:_cimg_mp_size((unsigned int)opcode[6]), // 14: sizM + opcode._height<8?1:opcode[7]).move_to(code); // 15: maxM + } else if (opcode._height<9 || (opcode._height<11 && _cimg_mp_is_vector((unsigned int)opcode[8]))) { + // x,y,z,dx,dy,dz,S[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[1],2 + arg1,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[2],3 + arg1,1,0); // y + _cimg_mp_check_type((unsigned int)opcode[3],4 + arg1,1,0); // z + _cimg_mp_check_type((unsigned int)opcode[4],5 + arg1,1,0); // dx + _cimg_mp_check_type((unsigned int)opcode[5],6 + arg1,1,0); // dy + _cimg_mp_check_type((unsigned int)opcode[6],7 + arg1,1,0); // dz + if (opcode._height>7) _cimg_mp_check_type((unsigned int)opcode[7],8 + arg1,1,0); // opac + if (opcode._height>9) _cimg_mp_check_type((unsigned int)opcode[9],10 + arg1,1,0); // maxM + CImg::vector((ulongT)mp_image_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode),p1, // 1-3: S,sizS,#ind + opcode[1],opcode[2],opcode[3],0, // 4-7: x,y,z,c + opcode[4],opcode[5],opcode[6],~0U, // 8-11: dx,dy,dz,dc + opcode._height<8?1:opcode[7], // 12: opac + opcode._height<9?~0U:opcode[8], // 13: M + opcode._height<9?0:_cimg_mp_size((unsigned int)opcode[8]), // 14: sizM + opcode._height<10?1:opcode[9]).move_to(code); // 15: maxM + } else if (opcode._height<11 || (opcode._height<13 && _cimg_mp_is_vector((unsigned int)opcode[10]))) { + // x,y,z,c,dx,dy,dz,dc,S[,opac,M,maxM] + _cimg_mp_check_type((unsigned int)opcode[1],2 + arg1,1,0); // x + _cimg_mp_check_type((unsigned int)opcode[2],3 + arg1,1,0); // y + _cimg_mp_check_type((unsigned int)opcode[3],4 + arg1,1,0); // z + _cimg_mp_check_type((unsigned int)opcode[4],5 + arg1,1,0); // c + _cimg_mp_check_type((unsigned int)opcode[5],6 + arg1,1,0); // dx + _cimg_mp_check_type((unsigned int)opcode[6],7 + arg1,1,0); // dy + _cimg_mp_check_type((unsigned int)opcode[7],8 + arg1,1,0); // dz + _cimg_mp_check_type((unsigned int)opcode[8],9 + arg1,1,0); // dc + if (opcode._height>9) _cimg_mp_check_type((unsigned int)opcode[9],10 + arg1,1,0); // opac + if (opcode._height>11) _cimg_mp_check_type((unsigned int)opcode[11],12 + arg1,1,0); // maxM + CImg::vector((ulongT)mp_image_draw, + *opcode,_cimg_mp_size((unsigned int)*opcode),p1, // 1-3: S,sizS,#ind + opcode[1],opcode[2],opcode[3],opcode[4], // 4-7: x,y,z,c + opcode[5],opcode[6],opcode[7],opcode[8], // 8-11: dx,dy,dz,dc + opcode._height<10?1:opcode[9], // 12: opac + opcode._height<11?~0U:opcode[10], // 13: M + opcode._height<11?0:_cimg_mp_size((unsigned int)opcode[10]), // 14: sizM + opcode._height<12?1:opcode[11]).move_to(code); // 15: maxM + } else { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid types in specified arguments, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s0); } } - - l_opcode.assign(); // Don't use 'opcode': it can be modified by further calls to 'compile()'! - CImg::vector((ulongT)mp_draw,arg1,(ulongT)_cimg_mp_size(arg1),p1,arg2,arg3,arg4,arg5, - 0,0,0,0,1,(ulongT)~0U,0,1).move_to(l_opcode); - - arg2 = arg3 = arg4 = arg5 = ~0U; - p2 = p1!=~0U?0:1; - if (s0::vector((ulongT)mp_echo,_cimg_mp_slot_nan,0).move_to(l_opcode); - for (s = ss5; s::vector(arg1,_cimg_mp_size(arg1)).move_to(l_opcode); s = ns; } @@ -18074,143 +20507,264 @@ namespace cimg_library_suffixed { if (!std::strncmp(ss,"eig(",4)) { // Matrix eigenvalues/eigenvector _cimg_mp_op("Function 'eig()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); _cimg_mp_check_matrix_square(arg1,1); p1 = (unsigned int)cimg::round(std::sqrt((float)_cimg_mp_size(arg1))); pos = vector((p1 + 1)*p1); CImg::vector((ulongT)mp_matrix_eig,pos,arg1,p1).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"ellipse(",8)) { // Ellipse/circle drawing + if (!is_inside_critical) is_parallelizable = false; + _cimg_mp_op("Function 'ellipse()'"); + if (*ss8=='#') { // Index specified + s0 = ss + 9; while (s0::vector((ulongT)mp_ellipse,_cimg_mp_slot_nan,0,p1).move_to(l_opcode); + for (s = s0; s::sequence(_cimg_mp_size(arg2),arg2 + 1, + arg2 + (ulongT)_cimg_mp_size(arg2)). + move_to(l_opcode); + else + CImg::vector(arg2).move_to(l_opcode); + s = ns; + } + (l_opcode>'y').move_to(opcode); + opcode[2] = opcode._height; + opcode.move_to(code); + _cimg_mp_return_nan(); + } + +#if cimg_use_cpp11==1 + if (!std::strncmp(ss,"erf(",4)) { // Error function + _cimg_mp_op("Function 'erf()'"); + arg1 = compile(ss4,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_erf,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::erf(mem[arg1])); + _cimg_mp_scalar1(mp_erf,arg1); + } +#endif + + if (!std::strncmp(ss,"erfinv(",7)) { // Inverse of error function + _cimg_mp_op("Function 'erfinv()'"); + arg1 = compile(ss7,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_erfinv,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::erfinv(mem[arg1])); + _cimg_mp_scalar1(mp_erfinv,arg1); + } + + if (!std::strncmp(ss,"exp(",4)) { // Exponential + _cimg_mp_op("Function 'exp()'"); + arg1 = compile(ss4,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_exp,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::exp(mem[arg1])); + _cimg_mp_scalar1(mp_exp,arg1); + } + + if (!std::strncmp(ss,"expr(",5)) { // Vector from expression + _cimg_mp_op("Function 'expr()'"); + s1 = ss5; while (s1::vector((ulongT)mp_expr,pos,arg1,p1,arg2,arg3,arg4,arg5).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"eye(",4)) { // Identity matrix + _cimg_mp_op("Function 'eye()'"); + arg1 = compile(ss4,se1,depth1,0,block_flags); + _cimg_mp_check_const_scalar(arg1,1,3); + p1 = (unsigned int)mem[arg1]; + pos = vector(p1*p1); + CImg::vector((ulongT)mp_eye,pos,p1).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"end(",4)) { // End _cimg_mp_op("Function 'end()'"); - code.swap(code_end); - compile(ss4,se1,depth1,p_ref,true); - code.swap(code_end); + s1 = ss4; while (s1::vector((ulongT)mp_ellipse,_cimg_mp_slot_nan,0,p1).move_to(l_opcode); - for (s = s0; s::sequence(_cimg_mp_size(arg2),arg2 + 1, - arg2 + (ulongT)_cimg_mp_size(arg2)). - move_to(l_opcode); - else CImg::vector(arg2).move_to(l_opcode); - s = ns; + if (!std::strncmp(ss,"end_t(",6)) { // End thread + _cimg_mp_op("Function 'end_t()'"); + s1 = ss6; while (s1'y').move_to(opcode); - opcode[2] = opcode._height; - opcode.move_to(code); _cimg_mp_return_nan(); } - - if (!std::strncmp(ss,"ext(",4)) { // Extern - _cimg_mp_op("Function 'ext()'"); - if (!is_single) is_parallelizable = false; - CImg::vector((ulongT)mp_ext,0,0).move_to(l_opcode); - pos = 1; - for (s = ss4; s::vector(arg1,_cimg_mp_size(arg1)).move_to(l_opcode); - s = ns; - } - (l_opcode>'y').move_to(opcode); - pos = scalar(); - opcode[1] = pos; - opcode[2] = opcode._height; - opcode.move_to(code); - _cimg_mp_return(pos); - } - - if (!std::strncmp(ss,"exp(",4)) { // Exponential - _cimg_mp_op("Function 'exp()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); - 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,is_single); - _cimg_mp_check_constant(arg1,1,3); - p1 = (unsigned int)mem[arg1]; - pos = vector(p1*p1); - CImg::vector((ulongT)mp_eye,pos,p1).move_to(code); - _cimg_mp_return(pos); - } break; case 'f' : + if (!std::strncmp(ss,"f2ui(",5)) { // Special float->uint conversion + _cimg_mp_op("Function 'f2ui()'"); + arg1 = compile(ss5,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_f2ui,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar((double)cimg::float2uint((float)mem[arg1])); + _cimg_mp_scalar1(mp_f2ui,arg1); + } + if (!std::strncmp(ss,"fact(",5)) { // Factorial _cimg_mp_op("Function 'fact()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_factorial,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::factorial((int)mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::factorial((int)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,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_fibonacci,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(cimg::fibonacci((int)mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::fibonacci((int)mem[arg1])); _cimg_mp_scalar1(mp_fibonacci,arg1); } + if (!std::strncmp(ss,"fill(",5)) { // Fill + _cimg_mp_op("Function 'fill()'"); + s0 = ss5; while (s0::%s: %s: Target scalar is constant, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,ss); + s1 = ++s0; while (s1::%s: %s: Invalid loop variable name '%s', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data,s0); + } + get_variable_pos(variable_name,arg2,arg3); + arg2 = arg3!=~0U?reserved_label[arg3]:arg2!=~0U?variable_pos[arg2]:~0U; // Variable slot + if (arg2!=~0U && (!_cimg_mp_is_scalar(arg2) || + _cimg_mp_is_const_scalar(arg2))) { // Variable is not a vector or is a constant->error + cimg::strellipsize(variable_name,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid type '%s' for variable '%s' " + "(expected 'scalar'), in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg2)._data,variable_name._data,s0); + } else if (arg2==~0U) { // Variable does not exist -> create it + arg2 = scalar(); + if (arg3!=~0U) reserved_label[arg3] = arg2; + else { + if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); + variable_pos[variable_def._width] = arg2; + variable_name.move_to(variable_def); + } + memtype[arg2] = -1; + } + arg3 = compile(++s1,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg3,3,1,0); + } else { // Version with 2 arguments + arg2 = ~0U; + arg3 = compile(s0,se1,depth1,0,block_flags); + } + // arg2 = variable slot, arg3 = fill expression. + _cimg_mp_check_type(arg3,3,1,0); + CImg::vector((ulongT)mp_fill,arg1,_cimg_mp_size(arg1),arg2,arg3,code._width - p1). + move_to(code,p1); + _cimg_mp_return_nan(); + } + if (!std::strncmp(ss,"find(",5)) { // Find _cimg_mp_op("Function 'find()'"); // First argument: data to look at. s0 = ss5; while (s01) _cimg_mp_scalar5(mp_list_find_seq,p1,arg2,_cimg_mp_size(arg2),arg3,arg4); - _cimg_mp_scalar4(mp_list_find,p1,arg2,arg3,arg4); + _cimg_mp_scalar4(mp_list_find,p1,arg2 + (_cimg_mp_size(arg2)?1:0),arg3,arg4); } - if (_cimg_mp_is_vector(arg2)) + if (_cimg_mp_size(arg2)>1) _cimg_mp_scalar6(mp_find_seq,arg1,_cimg_mp_size(arg1),arg2,_cimg_mp_size(arg2),arg3,arg4); - _cimg_mp_scalar5(mp_find,arg1,_cimg_mp_size(arg1),arg2,arg3,arg4); + _cimg_mp_scalar5(mp_find,arg1,_cimg_mp_size(arg1),arg2 + (_cimg_mp_size(arg2)?1:0),arg3,arg4); } if (*ss1=='o' && *ss2=='r' && *ss3=='(') { // For loop @@ -18219,68 +20773,75 @@ namespace cimg_library_suffixed { s2 = s1 + 1; while (s2::vector((ulongT)mp_for,p3,(ulongT)_cimg_mp_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); + p3>=arg6 && !_cimg_mp_is_const_scalar(p3), + p2>=arg6 && !_cimg_mp_is_const_scalar(p2)).move_to(code,arg1); _cimg_mp_return(p3); } if (!std::strncmp(ss,"floor(",6)) { // Floor _cimg_mp_op("Function 'floor()'"); - arg1 = compile(ss6,se1,depth1,0,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_floor,arg1); - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(std::floor(mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::floor(mem[arg1])); _cimg_mp_scalar1(mp_floor,arg1); } if (!std::strncmp(ss,"fsize(",6)) { // File size _cimg_mp_op("Function 'fsize()'"); - *se1 = 0; - variable_name.assign(CImg::string(ss6,true,true).unroll('y'),true); - cimg::strpare(variable_name,false,true); + arg1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,1,2,0); pos = scalar(); - ((CImg::vector((ulongT)mp_fsize,pos,0),variable_name)>'y').move_to(opcode); - *se1 = ')'; - opcode[2] = opcode._height; - opcode.move_to(code); + CImg::vector((ulongT)mp_fsize,pos,arg1,(ulongT)_cimg_mp_size(arg1)).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } break; case 'g' : +#if cimg_use_cpp11==1 + if (!std::strncmp(ss,"gamma(",6)) { // Gamma + _cimg_mp_op("Function 'gamma()'"); + arg1 = compile(ss6,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_gamma,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::tgamma(mem[arg1])); + _cimg_mp_scalar1(mp_gamma,arg1); + } +#endif + if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function _cimg_mp_op("Function 'gauss()'"); s1 = ss6; while (s1::max(); + if (mem[arg2]>=siz_max) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Specified variable size %g is larger than %d.", + pixel_type(),_cimg_mp_calling_function,s_op, + mem[arg2],siz_max); + } + arg2 = (unsigned int)mem[arg2]; + if (arg2) pos = vector(arg2); else pos = scalar(); + CImg::vector((ulongT)mp_get,pos,arg1,p1,arg2,arg3).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } +#endif break; case 'h' : if (*ss1=='(') { // Image height _cimg_mp_op("Function 'h()'"); if (*ss2=='#') { // Index specified - p1 = compile(ss3,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } - pos = scalar(); - CImg::vector((ulongT)mp_image_h,pos,p1).move_to(code); - _cimg_mp_return(pos); + _cimg_mp_scalar1(mp_image_h,p1); } break; @@ -18315,11 +20907,26 @@ namespace cimg_library_suffixed { if (*ss1=='c' && *ss2=='(') { // Image median _cimg_mp_op("Function 'ic()'"); if (*ss3=='#') { // Index specified - p1 = compile(ss4,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss4,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss3!=se1) break; p1 = ~0U; } pos = scalar(); CImg::vector((ulongT)mp_image_median,pos,p1).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (*ss1=='n' && *ss2=='(') { // Image norm + _cimg_mp_op("Function 'in()'"); + if (*ss3=='#') { // Index specified + p1 = compile(ss4,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); + } else { if (ss3!=se1) break; p1 = ~0U; } + pos = scalar(); + CImg::vector((ulongT)mp_image_norm,pos,p1).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } @@ -18327,52 +20934,125 @@ namespace cimg_library_suffixed { _cimg_mp_op("Function 'if()'"); s1 = ss3; while (s1::vector((ulongT)mp_if,pos,arg1,arg2,arg3, p3 - p2,code._width - p3,arg4).move_to(code,p2); + return_new_comp = true; _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,true); - code.swap(code_init); - _cimg_mp_return(arg1); + if (!std::strncmp(ss,"inrange(",8)) { // Check value range + _cimg_mp_op("Function 'inrange()'"); + s1 = ss8; while (s1=val1) + is_sth = (mem[arg4]?(val>=val1):(val>val1)) && (mem[arg5]?(val<=val2):(val=val2):(val>val2)) && (mem[arg4]?(val<=val1):(val::vector((ulongT)mp_inrange,pos,arg6,arg1,p1,arg2,p2,arg3,p3,arg4,arg5).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); } if (!std::strncmp(ss,"int(",4)) { // Integer cast _cimg_mp_op("Function 'int()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); 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]); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar((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,is_single); + if (!std::strncmp(ss,"invert(",7)) { // Matrix/scalar inverse (or pseudoinverse) + _cimg_mp_op("Function 'invert()'"); + s1 = ss7; while (s1::vector((ulongT)mp_matrix_inv,pos,arg1,p1).move_to(code); + p1 = _cimg_mp_size(arg1); + if (arg2==~0U) { // nb_colsA not specified: assuming square matrix + _cimg_mp_check_matrix_square(arg1,1); + p2 = p3 = (unsigned int)cimg::round(std::sqrt((float)p1)); + } else { + p2 = arg2; + p3 = p1/p2; + if (p3*p2!=p1) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Type of first argument ('%s') " + "does not match with second argument 'nb_colsA=%u', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,p2,s0); + } + } + pos = vector(p1); + CImg::vector((ulongT)mp_matrix_invert,pos,arg1,p2,p3,arg3,arg4).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } - if (_cimg_mp_is_constant(arg1)) _cimg_mp_constant(1/mem[arg1]); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(1/mem[arg1]); _cimg_mp_scalar2(mp_div,1,arg1); } @@ -18381,26 +21061,29 @@ namespace cimg_library_suffixed { 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,is_single); + try { arg1 = compile(ss7,se1,depth1,0,block_flags); } + catch(CImgException&) { _cimg_mp_return(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); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_return(mem[arg1]==0. || mem[arg1]==1.); _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); + arg1 = compile(ss6,se1,depth1,0,block_flags); + pos = scalar(); + CImg::vector((ulongT)mp_isdir,pos,arg1,(ulongT)_cimg_mp_size(arg1)).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); } 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); + arg1 = compile(ss7,se1,depth1,0,block_flags); + pos = scalar(); + CImg::vector((ulongT)mp_isfile,pos,arg1,(ulongT)_cimg_mp_size(arg1)).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); } if (!std::strncmp(ss,"isin(",5)) { // Is in sequence/vector? @@ -18411,54 +21094,71 @@ namespace cimg_library_suffixed { for (s = ss5; s::sequence(_cimg_mp_size(arg1),arg1 + 1, - arg1 + (ulongT)_cimg_mp_size(arg1)). - move_to(l_opcode); - else CImg::vector(arg1).move_to(l_opcode); + arg1 = compile(s,ns,depth1,0,block_flags); + CImg::vector(arg1,_cimg_mp_size(arg1)).move_to(l_opcode); s = ns; } (l_opcode>'y').move_to(opcode); opcode[2] = opcode._height; opcode.move_to(code); + return_new_comp = true; _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,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); 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::is_inf(mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) + _cimg_mp_return((unsigned int)cimg::type::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,is_single); + try { arg1 = compile(ss6,se1,depth1,0,block_flags); } + catch(CImgException&) { _cimg_mp_return(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)); + if (_cimg_mp_is_const_scalar(arg1)) + _cimg_mp_return((unsigned int)((double)(longT)mem[arg1]==mem[arg1])); _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,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); 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::is_nan(mem[arg1])); + if (_cimg_mp_is_const_scalar(arg1)) + _cimg_mp_return((unsigned int)cimg::type::is_nan(mem[arg1])); _cimg_mp_scalar1(mp_isnan,arg1); } - if (!std::strncmp(ss,"isval(",6)) { // Is value? - _cimg_mp_op("Function 'isval()'"); + if (!std::strncmp(ss,"isnum(",6)) { // Is number? + _cimg_mp_op("Function 'isnum()'"); val = 0; if (cimg_sscanf(ss6,"%lf%c%c",&val,&sep,&end)==2 && sep==')') _cimg_mp_return(1); _cimg_mp_return(0); } + if (!std::strncmp(ss,"isexpr(",7)) { // Is valid expression? + _cimg_mp_op("Function 'isexpr()'"); + if (ss7==se1) _cimg_mp_return(0); + try { arg1 = compile(ss7,se1,depth1,0,block_flags); } + catch (CImgException&) { _cimg_mp_return(0); } + _cimg_mp_return(1); + } + + if (!std::strncmp(ss,"isvarname(",10)) { // Is variable name? + _cimg_mp_op("Function 'isvarname()'"); + arg1 = compile(ss + 10,se1,depth1,0,block_flags); + pos = scalar(); + CImg::vector((ulongT)mp_isvarname,pos,arg1,(ulongT)_cimg_mp_size(arg1)).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } } break; @@ -18469,74 +21169,307 @@ namespace cimg_library_suffixed { _cimg_mp_scalar0(mp_list_l); } + if (!std::strncmp(ss,"lerp(",5)) { // Linear interpolation + _cimg_mp_op("Function 'lerp()'"); + s1 = ss5; while (s1::vector((ulongT)mp_vector_lerp,pos,p1,arg1,arg2,arg3).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + if (!std::strncmp(ss,"log(",4)) { // Natural logarithm _cimg_mp_op("Function 'log()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss + 10,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::lowercase(mem[arg1])); _cimg_mp_scalar1(mp_lowercase,arg1); } break; case 'm' : + if (!std::strncmp(ss,"map(",4)) { // Map vector + _cimg_mp_op("Function 'map()'"); + s1 = ss4; while (s1::%s: %s: Type of first arguments ('%s') " + "does not match with third argument 'nb_channelsX=%u', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,arg3,s0); + } + if (p2%arg4) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Type of second arguments ('%s') " + "does not match with fourth argument 'nb_channelsP=%u', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg2)._data,arg4,s0); + } + pos = vector(p1*arg4); + CImg::vector((ulongT)mp_map,pos,arg1,arg2,p1,p2,arg3,arg4,arg5).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + if (!std::strncmp(ss,"mul(",4)) { // Matrix multiplication _cimg_mp_op("Function 'mul()'"); s1 = ss4; while (s1expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); + _cimg_mp_strerr; throw CImgArgumentException("[" cimg_appname "_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'.", + "in expression '%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()?"...":""); + s_type(arg1)._data,s_type(arg2)._data,p3,s0); } pos = vector(arg4*p3); CImg::vector((ulongT)mp_matrix_mul,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } + + if (!std::strncmp(ss,"mproj(",6)) { // Project matrix onto dictionary + _cimg_mp_op("Function 'mproj()'"); + s1 = ss6; while (s1::%s: %s: Type of first argument ('%s') " + "do not match with second argument 'nb_colsS=%u', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,wS,s0); + } + if (wD*hD!=p2) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Type of third argument ('%s') " + "do not match with fourth argument 'nb_colsD=%u', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg3)._data,wD,s0); + } + if (hS!=hD) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Type of first argument ('%s') " + "do not match with third argument ('%s'), " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg1)._data,s_type(arg3)._data,s0); + } + pos = vector(wS*wD); + CImg::vector((ulongT)mp_mproj,pos,arg1,wS,hS,arg3,wD,arg5,arg6,p3).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"mse(",4)) { // Mean-squared error + _cimg_mp_op("Function 'mse()'"); + s1 = ss4; while (s1::%s: %s: First argument cannot be a linked reference, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s0); + } + + arg1 = ~0U; // Merge operator + // (0='=',1='+',2='-',3='*',4='/',5='&',6='|',7='xor',8='&&',9=='||',10='min',11='max') + if (s1::%s: %s: Merge has already been requested before " + "for specified variable " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s0); + } + if (arg1==~0U) { + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid specified operator " + "(should be one of '=,+,-,*,/,&,|,xor,&&,||,min,max'), " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s0); + } + memmerge.resize(3,memmerge._height + 1,1,1,0,0); + memmerge(0,memmerge._height - 1) = (int)pos; + memmerge(1,memmerge._height - 1) = (int)_cimg_mp_size(pos); + memmerge(2,memmerge._height - 1) = (int)arg1; + _cimg_mp_return_nan(); + } break; case 'n' : +#ifdef cimg_mp_func_name + if (!std::strncmp(ss,"name(",5)) { // Get image name as a string vector + _cimg_mp_op("Function 'name()'"); + if (*ss5=='#') { // Index specified + s0 = ss6; while (s0::vector((ulongT)mp_name,pos,p1,arg1).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } +#endif + if (!std::strncmp(ss,"narg(",5)) { // Number of arguments _cimg_mp_op("Function 'narg()'"); if (ss5>=se1) _cimg_mp_return(0); @@ -18546,51 +21479,68 @@ namespace cimg_library_suffixed { (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns; ++arg1; s = ns; } - _cimg_mp_constant(arg1); + _cimg_mp_const_scalar((double)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::vector((ulongT)mp_norm0,pos,0).move_to(l_opcode); break; - case 1 : - CImg::vector((ulongT)mp_norm1,pos,0).move_to(l_opcode); break; - case 2 : - CImg::vector((ulongT)mp_norm2,pos,0).move_to(l_opcode); break; - case ~0U : - CImg::vector((ulongT)mp_norminf,pos,0).move_to(l_opcode); break; - default : - CImg::vector((ulongT)mp_normp,pos,0,(ulongT)(arg1==~0U?-1:(int)arg1)). - move_to(l_opcode); - } - for ( ; s::vector(0,0,0,arg1).move_to(l_opcode); + for (++s; s::sequence(_cimg_mp_size(arg2),arg2 + 1, - arg2 + (ulongT)_cimg_mp_size(arg2)). + CImg::sequence(_cimg_mp_size(arg2),arg2 + 1,arg2 + (ulongT)_cimg_mp_size(arg2)). move_to(l_opcode); else CImg::vector(arg2).move_to(l_opcode); + is_sth&=_cimg_mp_is_const_scalar(arg2); s = ns; } - (l_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]); + op = val==2?_mp_vector_norm2:val==1?_mp_vector_norm1:!val?_mp_vector_norm0: + cimg::type::is_inf(val)?_mp_vector_norminf:_mp_vector_normp; + opcode[0] = (ulongT)op; opcode[2] = opcode._height; + if (is_sth) _cimg_mp_const_scalar(op(*this)); + if (opcode._height==5) { // Single argument + if (arg1) { _cimg_mp_scalar1(mp_abs,opcode[4]); } + else { _cimg_mp_scalar2(mp_neq,opcode[4],0); } + } + opcode[1] = pos = scalar(); opcode.move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + break; + + case 'o' : + if (!std::strncmp(ss,"o2c(",4)) { // Offset to coordinates + _cimg_mp_op("Function 'o2c()'"); + if (*ss4=='#') { // Index specified + s0 = ss5; while (s0::vector((ulongT)mp_o2c,pos,p1,arg1).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } break; @@ -18599,37 +21549,42 @@ namespace cimg_library_suffixed { if (!std::strncmp(ss,"permut(",7)) { // Number of permutations _cimg_mp_op("Function 'permut()'"); s1 = ss7; while (s1::vector((ulongT)mp_polygon,_cimg_mp_slot_nan,0,p1).move_to(l_opcode); - for (s = s0; s1 && _cimg_mp_is_vector(arg2)) // Vector argument allowed to specify coordinates and color CImg::sequence(_cimg_mp_size(arg2),arg2 + 1, arg2 + (ulongT)_cimg_mp_size(arg2)). move_to(l_opcode); - else CImg::vector(arg2).move_to(l_opcode); + else { + _cimg_mp_check_type(arg2,pos,1,0); + CImg::vector(arg2).move_to(l_opcode); + } s = ns; } (l_opcode>'y').move_to(opcode); @@ -18638,94 +21593,220 @@ namespace cimg_library_suffixed { _cimg_mp_return_nan(); } - if (!std::strncmp(ss,"print(",6) || !std::strncmp(ss,"prints(",7)) { // Print expressions - is_sth = ss[5]=='s'; // is prints() + if (!std::strncmp(ss,"print(",6) || + !std::strncmp(ss,"prints(",7)) { // Print expressions + s0 = ss6 + (*ss5=='('?0:1); + is_sth = *ss5=='s'; // corresponding string must be printed? _cimg_mp_op(is_sth?"Function 'prints()'":"Function 'print()'"); - s0 = is_sth?ss7:ss6; - if (*s0!='#' || is_sth) { // Regular expression - for (s = s0; s::string(s,true,true).unroll('y'),true); - cimg::strpare(variable_name,false,true); - if (_cimg_mp_is_vector(pos)) // Vector - ((CImg::vector((ulongT)mp_vector_print,pos,0,(ulongT)_cimg_mp_size(pos),is_sth?1:0), - variable_name)>'y').move_to(opcode); - else // Scalar - ((CImg::vector((ulongT)mp_print,pos,0,is_sth?1:0), - variable_name)>'y').move_to(opcode); - opcode[2] = opcode._height; - opcode.move_to(code); - *ns = c1; s = ns; - } - _cimg_mp_return(pos); - } else { // Image - p1 = compile(ss7,se1,depth1,0,is_single); - _cimg_mp_check_list(true); + if (!is_sth && *s0=='#') { // Image + p1 = compile(ss7,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); CImg::vector((ulongT)mp_image_print,_cimg_mp_slot_nan,p1).move_to(code); _cimg_mp_return_nan(); } - } - if (!std::strncmp(ss,"pseudoinv(",10)) { // Matrix/scalar pseudo-inversion - _cimg_mp_op("Function 'pseudoinv()'"); - s1 = ss + 10; while (s1expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); - throw CImgArgumentException("[" cimg_appname "_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()?"...":""); + // Regular expression + for (s = s0; s::string(s,true,true).unroll('y'),true); + cimg::strpare(variable_name,false,true); + if (_cimg_mp_is_const_scalar(pos)) // Const scalar + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %.17g " + "(mem[%u]: %s%s)", + variable_name._data,mem[pos],pos,s_type(pos)._data,s_ref(ref)._data); + else // Vector or non-const scalar + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = (uninitialized) " + "(mem[%u]: %s%s)", + variable_name._data,pos,s_type(pos)._data,s_ref(ref)._data); + + if (p_ref) std::memcpy(p_ref,ref,siz_ref); + + if (_cimg_mp_is_vector(pos)) // Vector + ((CImg::vector((ulongT)mp_vector_print,pos,0,(ulongT)_cimg_mp_size(pos),is_sth?1:0), + variable_name)>'y').move_to(opcode); + else // Scalar + ((CImg::vector((ulongT)mp_print,pos,0,is_sth?1:0), + variable_name)>'y').move_to(opcode); + + opcode[2] = opcode._height; + opcode.move_to(code); + *ns = c1; s = ns; } - pos = vector(p1); - CImg::vector((ulongT)mp_matrix_pseudoinv,pos,arg1,p2,p3).move_to(code); _cimg_mp_return(pos); } break; case 'r' : + if (!std::strncmp(ss,"rad2deg(",8)) { // Degrees to radians + _cimg_mp_op("Function 'rad2deg()'"); + arg1 = compile(ss8,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_rad2deg,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(mem[arg1]*180/cimg::PI); + _cimg_mp_scalar1(mp_rad2deg,arg1); + } + + if (!std::strncmp(ss,"ref(",4)) { // Variable declaration + _cimg_mp_op("Function 'ref()'"); + s1 = ss4; while (s1=se1 || !*s1) compile(s1,s1,depth1,0,block_flags); // Will throw missing argument error + arg3 = compile(ss4,s1++,depth1,p_ref,block_flags); + *se1 = 0; + + if (!cimg::is_varname(s1)) { // Invalid variable name + variable_name.assign(s1,(unsigned int)(se1 + 1 - s1)).back() = 0; + cimg::strellipsize(variable_name,64); + *se1 = ')'; + _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid specified variable name '%s', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data,s0); + } + get_variable_pos(s1,arg1,arg2); + if (arg2!=~0U) reserved_label[arg2] = arg3; + else if (arg1!=~0U) variable_pos[arg1] = arg3; + else { // New variable + if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); + variable_pos[variable_def._width] = arg3; + CImg::string(s1).move_to(variable_def); + } + if (_cimg_mp_is_vector(arg3)) + set_reserved_vector(arg3); // Prevent from being used in further optimization + else if (_cimg_mp_is_comp(arg3)) memtype[arg3] = -1; + *se1 = ')'; + _cimg_mp_return(arg3); + } + + if (!std::strncmp(ss,"repeat(",7)) { // Repeat + _cimg_mp_op("Function 'repeat()'"); + s0 = ss7; while (s0::%s: %s: Invalid loop variable name '%s', " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + variable_name._data,s0); + } + get_variable_pos(variable_name,arg2,arg3); + arg2 = arg3!=~0U?reserved_label[arg3]:arg2!=~0U?variable_pos[arg2]:~0U; // Variable slot + if (arg2!=~0U && (!_cimg_mp_is_scalar(arg2) || + _cimg_mp_is_const_scalar(arg2))) { // Variable is not a vector or is a constant->error + cimg::strellipsize(variable_name,64); + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Invalid type '%s' for variable '%s' " + "(expected 'scalar'), in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + s_type(arg2)._data,variable_name._data,s0); + } else if (arg2==~0U) { // Variable does not exist -> create it + arg2 = scalar(); + if (arg3!=~0U) reserved_label[arg3] = arg2; + else { + if (variable_def._width>=variable_pos._width) variable_pos.resize(-200,1,1,1,0); + variable_pos[variable_def._width] = arg2; + variable_name.move_to(variable_def); + } + memtype[arg2] = -1; + } + arg3 = compile(++s1,se1,depth1,0,block_flags); + } else { // Version with 2 arguments + arg2 = ~0U; + arg3 = compile(s0,se1,depth1,0,block_flags); + } + // arg2 = variable slot, arg3 = fill expression. + CImg::vector((ulongT)mp_repeat,arg3,arg1,arg2,code._width - p1).move_to(code,p1); + _cimg_mp_return_nan(); + } + if (!std::strncmp(ss,"resize(",7)) { // Vector or image resize _cimg_mp_op("Function 'resize()'"); if (*ss7!='#') { // Vector - s1 = ss7; while (s1::vector(arg2).move_to(l_opcode); + s = ns; } - _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::vector((ulongT)mp_vector_resize,pos,arg2,arg1,(ulongT)_cimg_mp_size(arg1), - arg3,arg4).move_to(code); + (l_opcode>'y').move_to(opcode); + if (opcode.height()<2) compile(s,se1,depth1,0,block_flags); // Not enough arguments -> throw exception + arg1 = (unsigned int)opcode[0]; // Vector to resize + p1 = _cimg_mp_size(arg1); + + if (opcode.height()<=4) { // Simple vector resize + arg2 = (unsigned int)opcode[1]; + _cimg_mp_check_const_scalar(arg2,2,3); + arg2 = (unsigned int)mem[arg2]; + arg3 = opcode.height()<3?1U:(unsigned int)opcode[2]; + _cimg_mp_check_type(arg3,3,1,0); + arg4 = opcode.height()<4?0U:(unsigned int)opcode[3]; + _cimg_mp_check_type(arg4,4,1,0); + pos = vector(arg2); + CImg::vector((ulongT)mp_vector_resize,pos,arg2,arg1,p1,arg3,arg4).move_to(code); + } else { // Advanced vector resize (vector viewed as an image) + // opcode = [ A, ow,oh,od,os, nw,nh,nd,ns, interp, boundary_cond, ax,ay,az,ac ] + // [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ] + + if (opcode.height()<6) compile(s,se1,depth1,0,block_flags); // Not enough arguments -> throw exception + p2 = opcode.height(); + opcode.resize(1,15,1,1,0); + if (p2<7) opcode[6] = opcode[2]; + if (p2<8) opcode[7] = opcode[3]; + if (p2<9) opcode[8] = opcode[4]; + if (p2<10) opcode[9] = 1; + _cimg_mp_check_const_scalar(opcode[1],2,3); + _cimg_mp_check_const_scalar(opcode[2],3,3); + _cimg_mp_check_const_scalar(opcode[3],4,3); + _cimg_mp_check_const_scalar(opcode[4],5,3); + _cimg_mp_check_const_scalar(opcode[5],6,3); + _cimg_mp_check_const_scalar(opcode[6],7,3); + _cimg_mp_check_const_scalar(opcode[7],8,3); + _cimg_mp_check_const_scalar(opcode[8],9,3); + arg2 = (unsigned int)mem[opcode[1]]; opcode[1] = arg2; + arg3 = (unsigned int)mem[opcode[2]]; opcode[2] = arg3; + arg4 = (unsigned int)mem[opcode[3]]; opcode[3] = arg4; + arg5 = (unsigned int)mem[opcode[4]]; opcode[4] = arg5; + if (arg2*arg3*arg4*arg5!=std::max(1U,p1)) + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Input vector size (%lu values) and its specified " + "geometry (%u,%u,%u,%u) (%lu values) do not match.", + pixel_type(),_cimg_mp_calling_function,s_op, + std::max(p1,1U),arg2,arg3,arg4,arg5,(ulongT)arg2*arg3*arg4*arg5); + arg2 = (unsigned int)mem[opcode[5]]; opcode[5] = arg2; + arg3 = (unsigned int)mem[opcode[6]]; opcode[6] = arg3; + arg4 = (unsigned int)mem[opcode[7]]; opcode[7] = arg4; + arg5 = (unsigned int)mem[opcode[8]]; opcode[8] = arg5; + pos = vector(arg2*arg3*arg4*arg5); + opcode.resize(1,18,1,1,0,0,0,1); + opcode[0] = (ulongT)mp_vector_resize_ext; + opcode[1] = (ulongT)pos; + opcode[2] = (ulongT)p1; + opcode.move_to(code); + } + return_new_comp = true; _cimg_mp_return(pos); } else { // Image - if (!is_single) is_parallelizable = false; + if (!is_inside_critical) is_parallelizable = false; s0 = ss8; while (s0::vector((ulongT)mp_image_resize,_cimg_mp_slot_nan,p1,~0U,~0U,~0U,~0U,1,0,0,0,0,0). move_to(l_opcode); @@ -18733,21 +21814,18 @@ namespace cimg_library_suffixed { for (s = s0; s10) { - *se = saved_char; - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); + _cimg_mp_strerr; throw CImgArgumentException("[" cimg_appname "_math_parser] " - "CImg<%s>::%s: %s: %s arguments, in expression '%s%s%s'.", + "CImg<%s>::%s: %s: %s arguments, in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, - pos<1?"Missing":"Too much", - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + pos<1?"Missing":"Too much",s0); } l_opcode[0].move_to(code); _cimg_mp_return_nan(); @@ -18756,32 +21834,33 @@ namespace cimg_library_suffixed { if (!std::strncmp(ss,"reverse(",8)) { // Vector reverse _cimg_mp_op("Function 'reverse()'"); - arg1 = compile(ss8,se1,depth1,0,is_single); + arg1 = compile(ss8,se1,depth1,0,block_flags); if (!_cimg_mp_is_vector(arg1)) _cimg_mp_return(arg1); p1 = _cimg_mp_size(arg1); pos = vector(p1); CImg::vector((ulongT)mp_vector_reverse,pos,arg1,p1).move_to(code); + return_new_comp = true; _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 (s12) arg3 = arg2 + 1; } - arg4 = compile(++s1,se1,depth1,0,is_single); + arg4 = compile(++s1,se1,depth1,0,block_flags); } else { - s2 = s1 + 1; while (s2::vector((ulongT)mp_rot3d,pos,arg1,arg2,arg3,arg4).move_to(code); - } else { // 2d rotation + } else { // 2D rotation _cimg_mp_check_type(arg1,1,1,0); pos = vector(4); CImg::vector((ulongT)mp_rot2d,pos,arg1).move_to(code); } + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"round(",6)) { // Value rounding _cimg_mp_op("Function 'round()'"); s1 = ss6; while (s1::vector((ulongT)mp_run,0,0).move_to(l_opcode); + pos = 1; + for (s = ss4; s::vector(arg1,_cimg_mp_size(arg1)).move_to(l_opcode); + s = ns; + } + (l_opcode>'y').move_to(opcode); + pos = scalar(); + opcode[1] = pos; + opcode[2] = opcode._height; + opcode.move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } +#endif break; case 's' : if (*ss1=='(') { // Image spectrum _cimg_mp_op("Function 's()'"); if (*ss2=='#') { // Index specified - p1 = compile(ss3,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } - pos = scalar(); - CImg::vector((ulongT)mp_image_s,pos,p1).move_to(code); - _cimg_mp_return(pos); + _cimg_mp_scalar1(mp_image_s,p1); } if (!std::strncmp(ss,"same(",5)) { // Test if operands have the same values _cimg_mp_op("Function 'same()'"); s1 = ss5; while (s1::vector((ulongT)mp_set,arg2,p2,arg1,p1).move_to(code); + _cimg_mp_return_nan(); + } +#endif + if (!std::strncmp(ss,"shift(",6)) { // Shift vector _cimg_mp_op("Function 'shift()'"); s1 = ss6; while (s1::vector((ulongT)mp_shift,pos,arg1,p1,arg2,arg3).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"sign(",5)) { // Sign _cimg_mp_op("Function 'sign()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(cimg::sinc(mem[arg1])); _cimg_mp_scalar1(mp_sinc,arg1); } if (!std::strncmp(ss,"sinh(",5)) { // Hyperbolic sine _cimg_mp_op("Function 'sinh()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::sinh(mem[arg1])); _cimg_mp_scalar1(mp_sinh,arg1); } - if (!std::strncmp(ss,"size(",5)) { // Vector size. + if (!std::strncmp(ss,"size(",5)) { // Vector size _cimg_mp_op("Function 'size()'"); - arg1 = compile(ss5,se1,depth1,0,is_single); - _cimg_mp_constant(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_size(arg1)); + arg1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_const_scalar(_cimg_mp_is_scalar(arg1)?0:_cimg_mp_size(arg1)); } - if (!std::strncmp(ss,"solve(",6)) { // Solve linear system + if (!std::strncmp(ss,"solve(",6)) { // Solve square linear system _cimg_mp_op("Function 'solve()'"); s1 = ss6; while (s1expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); + arg6 = p1/arg5; + if (arg6*arg5!=p1 || arg5*p3!=p2) { + _cimg_mp_strerr; throw CImgArgumentException("[" cimg_appname "_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'.", + "in expression '%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()?"...":""); + s_type(arg1)._data,s_type(arg2)._data,p3,s0); } - pos = vector(arg4*p3); - CImg::vector((ulongT)mp_solve,pos,arg1,arg2,arg4,arg5,p3).move_to(code); + pos = vector(arg6*p3); + CImg::vector((ulongT)mp_solve,pos,arg1,arg2,arg6,arg5,p3,arg4).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } if (!std::strncmp(ss,"sort(",5)) { // Sort vector _cimg_mp_op("Function 'sort()'"); - if (*ss5!='#') { // Vector - s1 = ss5; while (s1expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); - throw CImgArgumentException("[" cimg_appname "_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::vector((ulongT)mp_sort,pos,arg1,p1,arg2,arg3).move_to(code); - _cimg_mp_return(pos); - - } else { // Image - s1 = ss6; while (s1::vector((ulongT)mp_image_sort,_cimg_mp_slot_nan,p1,arg1,arg2).move_to(code); - _cimg_mp_return_nan(); } + _cimg_mp_check_type(arg1,1,2,0); + _cimg_mp_check_type(arg2,2,1,0); + if (arg3!=~0U) _cimg_mp_check_type(arg3,3,1,0); + _cimg_mp_check_type(arg4,4,1,0); + p1 = _cimg_mp_size(arg1); + pos = vector(p1); + CImg::vector((ulongT)mp_sort,pos,arg1,p1,arg2,arg3,arg4).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); } if (!std::strncmp(ss,"sqr(",4)) { // Square _cimg_mp_op("Function 'sqr()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::sqrt(mem[arg1])); _cimg_mp_scalar1(mp_sqrt,arg1); } if (!std::strncmp(ss,"srand(",6)) { // Set RNG seed _cimg_mp_op("Function 'srand()'"); - arg1 = ss6::vector((ulongT)mp_image_stats,pos,p1).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } - if (!std::strncmp(ss,"stov(",5)) { // String to double - _cimg_mp_op("Function 'stov()'"); - s1 = ss5; while (s1::vector((ulongT)mp_store,_cimg_mp_slot_nan,arg2,p2,arg1,p1, + arg3,arg4,arg5,arg6,pos).move_to(code); + _cimg_mp_return_nan(); + } +#endif + + if (!std::strncmp(ss,"s2v(",4)) { // String to double + _cimg_mp_op("Function 's2v()'"); + s1 = ss4; while (s1::vector((ulongT)mp_s2v,pos,arg1,p1,arg2,arg3).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"string(",7)) { // Construct string from list of arguments + _cimg_mp_op("Function 'string()'"); + CImg::vector((ulongT)mp_string,0,0,0).move_to(l_opcode); + + if (*ss7=='#') { // Output vector size specified, with '#' + s0 = ss8; while (s0::vector(arg2,p2).move_to(l_opcode); + s = ns; + } + if (arg1==~0U) arg1 = p1; + pos = vector(arg1,0); + (l_opcode>'y').move_to(opcode); + opcode[1] = pos; + opcode[2] = arg1; + opcode[3] = opcode._height; + opcode.move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); } if (!std::strncmp(ss,"svd(",4)) { // Matrix SVD _cimg_mp_op("Function 'svd()'"); s1 = ss4; while (s1expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); + _cimg_mp_strerr; throw CImgArgumentException("[" cimg_appname "_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'.", + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, - s_type(arg1)._data,p2, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + s_type(arg1)._data,p2,s0); } pos = vector(p1 + p2 + p2*p2); CImg::vector((ulongT)mp_matrix_svd,pos,arg1,p2,p3).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } + + if (!std::strncmp(ss,"swap(",5)) { // Swap values + _cimg_mp_op("Function 'swap()'"); + s1 = ss5; while (s1::%s: %s: %s argument cannot be a constant, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op, + _cimg_mp_is_const_scalar(arg1)?"First":"Second",s0); + } + CImg::vector((ulongT)mp_swap,arg1,arg2,p1).move_to(code); + + // Write back values of linked arg1 and arg2. + const unsigned int *_ref = ref; + is_sth = true; // Is first argument? + do { + switch (*_ref) { + case 1 : // arg1: V[k] + arg3 = _ref[1]; // Vector slot + arg4 = _ref[2]; // Index + CImg::vector((ulongT)mp_vector_set_off,arg1,arg3,(ulongT)_cimg_mp_size(arg3),arg4). + move_to(code); + break; + case 2 : // arg1: i/j[_#ind,off] + if (!is_inside_critical) is_parallelizable = false; + p1 = _ref[1]; // Index + is_relative = (bool)_ref[2]; + arg3 = _ref[3]; // Offset + if (p1!=~0U) { + if (imglist) + CImg::vector((ulongT)(is_relative?mp_list_set_joff:mp_list_set_ioff), + arg1,p1,arg3).move_to(code); + } else { + if (imgout) + CImg::vector((ulongT)(is_relative?mp_set_joff:mp_set_ioff), + arg1,arg3).move_to(code); + } + break; + case 3 : // arg1: i/j(_#ind,_x,_y,_z,_c) + if (!is_inside_critical) 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 (p1!=~0U) { + if (imglist) + CImg::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::vector((ulongT)(is_relative?mp_set_jxyzc:mp_set_ixyzc), + arg1,arg3,arg4,arg5,arg6).move_to(code); + } + break; + case 4: // arg1: I/J[_#ind,off] + if (!is_inside_critical) is_parallelizable = false; + p1 = _ref[1]; // Index + is_relative = (bool)_ref[2]; + arg3 = _ref[3]; // Offset + if (p1!=~0U) { + if (imglist) { + if (_cimg_mp_is_scalar(arg1)) + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_s:mp_list_set_Ioff_s), + arg1,p1,arg3).move_to(code); + else { + _cimg_mp_check_const_index(p1); + CImg::vector((ulongT)(is_relative?mp_list_set_Joff_v:mp_list_set_Ioff_v), + arg1,p1,arg3,_cimg_mp_size(arg1)).move_to(code); + } + } + } else { + if (imgout) { + if (_cimg_mp_is_scalar(arg1)) + CImg::vector((ulongT)(is_relative?mp_set_Joff_s:mp_set_Ioff_s), + arg1,arg3).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Joff_v:mp_set_Ioff_v), + arg1,arg3,_cimg_mp_size(arg1)).move_to(code); + } + } + break; + case 5 : // arg1: I/J(_#ind,_x,_y,_z,_c) + if (!is_inside_critical) 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 (p1!=~0U) { + if (imglist) { + if (_cimg_mp_is_scalar(arg1)) + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_s:mp_list_set_Ixyz_s), + arg1,p1,arg3,arg4,arg5).move_to(code); + else { + _cimg_mp_check_const_index(p1); + CImg::vector((ulongT)(is_relative?mp_list_set_Jxyz_v:mp_list_set_Ixyz_v), + arg1,p1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + } + } else { + if (imgout) { + if (_cimg_mp_is_scalar(arg1)) + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_s:mp_set_Ixyz_s), + arg1,arg3,arg4,arg5).move_to(code); + else + CImg::vector((ulongT)(is_relative?mp_set_Jxyz_v:mp_set_Ixyz_v), + arg1,arg3,arg4,arg5,_cimg_mp_size(arg1)).move_to(code); + } + } + break; + } + + _ref+=7; + arg1 = arg2; + is_sth = !is_sth; + } while (!is_sth); + + if (p_ref) std::memcpy(p_ref,ref,siz_ref); + _cimg_mp_return_nan(); + } break; case 't' : if (!std::strncmp(ss,"tan(",4)) { // Tangent _cimg_mp_op("Function 'tan()'"); - arg1 = compile(ss4,se1,depth1,0,is_single); + arg1 = compile(ss4,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss5,se1,depth1,0,block_flags); 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])); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(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,is_single); + arg1 = compile(ss6,se1,depth1,0,block_flags); _cimg_mp_check_matrix_square(arg1,1); p1 = (unsigned int)cimg::round(std::sqrt((float)_cimg_mp_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 (s1expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); + _cimg_mp_strerr; throw CImgArgumentException("[" cimg_appname "_math_parser] " "CImg<%s>::%s: %s: Size of first argument ('%s') does not match " - "second argument 'nb_cols=%u', in expression '%s%s%s'.", + "second argument 'nb_cols=%u', in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, - s_type(arg1)._data,p2, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + s_type(arg1)._data,p2,s0); } pos = vector(p3*p2); - CImg::vector((ulongT)mp_transp,pos,arg1,p2,p3).move_to(code); + CImg::vector((ulongT)mp_transpose,pos,arg1,p2,p3).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } break; case 'u' : - if (*ss1=='(') { // Random value with uniform distribution + if (*ss1=='(') { // Random value with uniform distribution in specified range _cimg_mp_op("Function 'u()'"); if (*ss2==')') _cimg_mp_scalar2(mp_u,0,1); s1 = ss2; while (s1float conversion + _cimg_mp_op("Function 'ui2f()'"); + arg1 = compile(ss5,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_ui2f,arg1); + if (_cimg_mp_is_const_scalar(arg1)) + _cimg_mp_const_scalar((double)cimg::uint2float((unsigned int)mem[arg1])); + _cimg_mp_scalar1(mp_ui2f,arg1); + } + + if (!std::strncmp(ss,"unitnorm(",9)) { // Normalize vector to unit norm + _cimg_mp_op("Function 'unitnorm()'"); + s0 = ss + 9; + s1 = s0; while (s10) pos = is_comp_vector(arg1)?arg1:((return_new_comp = true), vector(p1)); + else { + pos = scalar(); + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) { + val = mem[arg1]; + _cimg_mp_const_scalar(val?(mem[arg2]?1:val):0); + } + } + CImg::vector((ulongT)mp_vector_unitnorm,pos,arg1,p1,arg2).move_to(code); + _cimg_mp_return(pos); } if (!std::strncmp(ss,"unref(",6)) { // Un-reference variable _cimg_mp_op("Function 'unref()'"); - arg1 = ~0U; + arg1=~0U; for (s0 = ss6; s0ss6 && *s0==',') ++s0; s1 = s0; while (s1s0) { *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::sequence(arg4,arg3 + 1,arg3 + arg4).move_to(l_opcode); @@ -19236,35 +22574,97 @@ namespace cimg_library_suffixed { s = ns; } if (arg1==~0U) arg1 = arg2; - _cimg_mp_check_vector0(arg1); + if (!arg1) _cimg_mp_return(0); pos = vector(arg1); l_opcode.insert(CImg::vector((ulongT)mp_vector_init,pos,0,arg1),0); (l_opcode>'y').move_to(opcode); opcode[2] = opcode._height; opcode.move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } - if (!std::strncmp(ss,"vtos(",5)) { // Double(s) to string - _cimg_mp_op("Function 'vtos()'"); - s1 = ss5; while (s1::vector((ulongT)op,0,0,0).move_to(l_opcode); + p1 = ~0U; + p3 = 1; + for (s = std::strchr(ss,'(') + 1; s::vector(arg2,p2).move_to(l_opcode); + s = ns; + ++p3; + } + (l_opcode>'y').move_to(opcode); + if (p1==~0U) { pos = scalar(); p1 = 0; } else pos = vector(p1); + opcode[1] = pos; + opcode[2] = p1; + opcode[3] = opcode._height; + opcode.move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"v2s(",4)) { // Double(s) to string + _cimg_mp_op("Function 'v2s()'"); + s1 = ss4; while (s1::vector((ulongT)mp_vtos,pos,p1,arg1,_cimg_mp_size(arg1),arg2).move_to(code); + CImg::vector((ulongT)mp_v2s,pos,p1,arg1,_cimg_mp_size(arg1),arg2).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } break; @@ -19273,61 +22673,57 @@ namespace cimg_library_suffixed { if (*ss1=='(') { // Image width _cimg_mp_op("Function 'w()'"); if (*ss2=='#') { // Index specified - p1 = compile(ss3,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } - pos = scalar(); - CImg::vector((ulongT)mp_image_w,pos,p1).move_to(code); - _cimg_mp_return(pos); + _cimg_mp_scalar1(mp_image_w,p1); } if (*ss1=='h' && *ss2=='(') { // Image width*height _cimg_mp_op("Function 'wh()'"); if (*ss3=='#') { // Index specified - p1 = compile(ss4,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss4,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss3!=se1) break; p1 = ~0U; } - pos = scalar(); - CImg::vector((ulongT)mp_image_wh,pos,p1).move_to(code); - _cimg_mp_return(pos); + _cimg_mp_scalar1(mp_image_wh,p1); } if (*ss1=='h' && *ss2=='d' && *ss3=='(') { // Image width*height*depth _cimg_mp_op("Function 'whd()'"); if (*ss4=='#') { // Index specified - p1 = compile(ss5,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss4!=se1) break; p1 = ~0U; } - pos = scalar(); - CImg::vector((ulongT)mp_image_whd,pos,p1).move_to(code); - _cimg_mp_return(pos); + _cimg_mp_scalar1(mp_image_whd,p1); } if (*ss1=='h' && *ss2=='d' && *ss3=='s' && *ss4=='(') { // Image width*height*depth*spectrum _cimg_mp_op("Function 'whds()'"); if (*ss5=='#') { // Index specified - p1 = compile(ss6,se1,depth1,0,is_single); - _cimg_mp_check_list(false); + p1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); } else { if (ss5!=se1) break; p1 = ~0U; } - pos = scalar(); - CImg::vector((ulongT)mp_image_whds,pos,p1).move_to(code); - _cimg_mp_return(pos); + _cimg_mp_scalar1(mp_image_whds,p1); } - if (!std::strncmp(ss,"while(",6) || !std::strncmp(ss,"whiledo(",8)) { // While...do - _cimg_mp_op("Function 'whiledo()'"); + if (!std::strncmp(ss,"while(",6)) { // While...do + _cimg_mp_op("Function 'while()'"); s0 = *ss5=='('?ss6:ss8; s1 = s0; while (s1::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::vector((ulongT)mp_while,pos,arg1,p2 - p1,code._width - p2,arg2, + pos>=arg6 && !_cimg_mp_is_const_scalar(pos), + arg1>=arg6 && !_cimg_mp_is_const_scalar(arg1)).move_to(code,p1); _cimg_mp_return(pos); } break; @@ -19336,64 +22732,72 @@ namespace cimg_library_suffixed { if (!std::strncmp(ss,"xor(",4)) { // Xor _cimg_mp_op("Function 'xor()'"); s1 = ss4; while (s1::vector((ulongT)op,pos,0).move_to(l_opcode); for (s = std::strchr(ss,'(') + 1; s::sequence(_cimg_mp_size(arg2),arg2 + 1, - arg2 + (ulongT)_cimg_mp_size(arg2)). - move_to(l_opcode); - else CImg::vector(arg2).move_to(l_opcode); - is_sth&=_cimg_mp_is_constant(arg2); + arg2 = compile(s,ns,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg2)) CImg::vector(arg2 + 1,_cimg_mp_size(arg2)).move_to(l_opcode); + else CImg::vector(arg2,1).move_to(l_opcode); + is_sth&=_cimg_mp_is_const_scalar(arg2); s = ns; } (l_opcode>'y').move_to(opcode); opcode[2] = opcode._height; - if (is_sth) _cimg_mp_constant(op(*this)); + if (is_sth) _cimg_mp_const_scalar(op(*this)); opcode.move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } @@ -19405,34 +22809,46 @@ namespace cimg_library_suffixed { // Count number of specified arguments. p1 = 0; for (s = s0 + 1; s<=se1; ++p1, s = ns + 1) { - while (*s && (signed char)*s<=' ') ++s; + while (*s && cimg::is_blank(*s)) ++s; if (*s==')' && !p1) break; ns = s; while (ns _expr = macro_body[l]; // Expression to be substituted - p1 = 1; // Indice of current parsed argument + p1 = 1; // Index 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 + while (*s && cimg::is_blank(*s)) ++s; + if (!is_variadic && *s==')' && p1==1) break; // Function has no arguments if (p1>p2) { ++p1; break; } - ns = s; while (ns1) { + _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; + } else { + std::memmove(_expr._data + k,_expr._data + k + 1,arg1 - k - 1); + --k; + } } ++arg2; } @@ -19442,7 +22858,7 @@ namespace cimg_library_suffixed { CImg _pexpr(_expr._width); ns = _pexpr._data; for (ps = _expr._data, c1 = ' '; *ps; ++ps) { - if ((signed char)*ps>' ') c1 = *ps; + if (!cimg::is_blank(*ps)) c1 = *ps; *(ns++) = c1; } *ns = 0; @@ -19453,7 +22869,7 @@ namespace cimg_library_suffixed { level.swap(_level); s0 = user_macro; user_macro = macro_def[l]; - pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref,is_single); + pos = compile(expr._data,expr._data + expr._width - 1,depth1,p_ref,block_flags); user_macro = s0; level.swap(_level); pexpr.swap(_pexpr); @@ -19466,10 +22882,8 @@ namespace cimg_library_suffixed { 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_mp_strerr; 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(); @@ -19477,18 +22891,16 @@ namespace cimg_library_suffixed { throw CImgArgumentException("[" cimg_appname "_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'.", + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function,variable_name._data, - p1,sig_nargs.value_string()._data,arg1, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + p1,sig_nargs.value_string()._data,arg1,s0); } else throw CImgArgumentException("[" cimg_appname "_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'.", + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function,variable_name._data, - p1,*sig_nargs,*sig_nargs!=1?"s":"", - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + p1,*sig_nargs,*sig_nargs!=1?"s":"",s0); } } } // if (se1==')') @@ -19499,7 +22911,7 @@ namespace cimg_library_suffixed { (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. + arg1 = (unsigned int)(se1 - s1); // Original string length if (arg1) { CImg(s1,arg1 + 1).move_to(variable_name).back() = 0; cimg::strunescape(variable_name); @@ -19507,33 +22919,31 @@ namespace cimg_library_suffixed { } if (!arg1) _cimg_mp_return(0); // Empty string -> 0 if (*ss=='_') { - if (arg1==1) _cimg_mp_constant(*variable_name); - *se = saved_char; + if (arg1==1) _cimg_mp_const_scalar((unsigned char)*variable_name); + _cimg_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); throw CImgArgumentException("[" cimg_appname "_math_parser] " - "CImg<%s>::%s: %s: Literal %s contains more than one character, " - "in expression '%s%s%s'.", + "CImg<%s>::%s: %s: Literal %s contains more than one byte, " + "in expression '%s'.", pixel_type(),_cimg_mp_calling_function,s_op, - ss1, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + ss1,s0); } pos = vector(arg1); CImg::vector((ulongT)mp_string_init,pos,arg1).move_to(l_opcode); CImg(1,arg1/sizeof(ulongT) + (arg1%sizeof(ulongT)?1:0)).move_to(l_opcode); std::memcpy((char*)l_opcode[1]._data,variable_name,arg1); (l_opcode>'y').move_to(code); + return_new_comp = true; _cimg_mp_return(pos); } // Vector initializer [ ... ]. if (*ss=='[' && *se1==']') { _cimg_mp_op("Vector initializer"); - s1 = ss1; while (s1s1 && (signed char)*s2<=' ') --s2; + s1 = ss1; while (s1s1 && cimg::is_blank(*s2)) --s2; if (s2>s1 && *s1=='\'' && *s2=='\'') { // Vector values provided as a string - arg1 = (unsigned int)(s2 - s1 - 1); // Original string length. + arg1 = (unsigned int)(s2 - s1 - 1); // Original string length if (arg1) { CImg(s1 + 1,arg1 + 1).move_to(variable_name).back() = 0; cimg::strunescape(variable_name); @@ -19546,11 +22956,11 @@ namespace cimg_library_suffixed { std::memcpy((char*)l_opcode[1]._data,variable_name,arg1); (l_opcode>'y').move_to(code); } else { // Vector values provided as list of items - arg1 = 0; // Number of specified values. + arg1 = 0; // Number of specified values if (*ss1!=']') for (s = ss1; s::sequence(arg3,arg2 + 1,arg2 + arg3).move_to(l_opcode); @@ -19558,102 +22968,130 @@ namespace cimg_library_suffixed { } else { CImg::vector(arg2).move_to(l_opcode); ++arg1; } s = ns; } - _cimg_mp_check_vector0(arg1); + if (!arg1) _cimg_mp_return(0); pos = vector(arg1); l_opcode.insert(CImg::vector((ulongT)mp_vector_init,pos,0,arg1),0); (l_opcode>'y').move_to(opcode); opcode[2] = opcode._height; opcode.move_to(code); } + return_new_comp = true; _cimg_mp_return(pos); } // Variables related to the input list of images. if (*ss1=='#' && ss2::vector((ulongT)mp_list_Joff,pos,p1,0,0,p2).move_to(code); + return_new_comp = true; _cimg_mp_return(pos); case 'R' : // R#ind - if (!listin) _cimg_mp_return(0); + if (!imglist) _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); + if (!imglist) _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); + if (!imglist) _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); + if (!imglist) _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::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); + if (!imglist) _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); } + + if (*ss1=='c') { // ic#ind + if (!imglist) _cimg_mp_return(0); + if (_cimg_mp_is_const_scalar(arg1)) { + if (!list_median) list_median.assign(imglist._width); + if (!list_median[p1]) CImg::vector(imglist[p1].median()).move_to(list_median[p1]); + _cimg_mp_const_scalar(*list_median[p1]); + } + _cimg_mp_scalar1(mp_list_id,arg1); + } + + if (*ss1=='d') { // id#ind + if (!imglist) _cimg_mp_return(0); + if (_cimg_mp_is_const_scalar(arg1)) { + if (!list_stats) list_stats.assign(imglist._width); + if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(imglist[p1].get_stats(),false); + _cimg_mp_const_scalar(std::sqrt(list_stats(p1,3))); + } + _cimg_mp_scalar1(mp_list_id,arg1); + } + + if (*ss1=='n') { // in#ind + if (!imglist) _cimg_mp_return(0); + if (_cimg_mp_is_const_scalar(arg1)) { + if (!list_norm) list_norm.assign(imglist._width); + if (!list_norm[p1]) CImg::vector(imglist[p1].magnitude(2)).move_to(list_norm[p1]); + _cimg_mp_const_scalar(*list_norm[p1]); + } + _cimg_mp_scalar1(mp_list_norm,arg1); + } + switch (*ss1) { + case 'a' : arg2 = 2; break; // ia#ind 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 + case 's' : arg2 = 12; break; // is#ind + case 'v' : arg2 = 3; break; // iv#ind } } else if (*ss1=='m') switch (*ss) { case 'x' : arg2 = 4; break; // xm#ind @@ -19667,66 +23105,66 @@ namespace cimg_library_suffixed { 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)); + if (!imglist) _cimg_mp_return(0); + if (_cimg_mp_is_const_scalar(arg1)) { + if (!list_stats) list_stats.assign(imglist._width); + if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(imglist[p1].get_stats(),false); + _cimg_mp_const_scalar(list_stats(p1,arg2)); } _cimg_mp_scalar2(mp_list_stats,arg1,arg2); } } if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4 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_mp_strerr; cimg::strellipsize(variable_name,64); - s0 = ss - 4>expr._data?ss - 4:expr._data; - cimg::strellipsize(s0,64); if (is_sth) throw CImgArgumentException("[" cimg_appname "_math_parser] " - "CImg<%s>::%s: Undefined variable '%s' in expression '%s%s%s'.", + "CImg<%s>::%s: Undefined variable '%s' in expression '%s'.", pixel_type(),_cimg_mp_calling_function, - variable_name._data, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + variable_name._data,s0); s1 = std::strchr(ss,'('); s_op = s1 && c1==')'?"function call":"item"; throw CImgArgumentException("[" cimg_appname "_math_parser] " - "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s%s%s'.", + "CImg<%s>::%s: Unrecognized %s '%s' in expression '%s'.", pixel_type(),_cimg_mp_calling_function, - s_op,variable_name._data, - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + s_op,variable_name._data,s0); } // Evaluation procedure. @@ -19756,14 +23194,44 @@ namespace cimg_library_suffixed { } else *output = (t)*result; } - // Evaluation procedure for the end() blocks. - void end() { - if (code_end.is_empty()) return; + // Evaluation procedure for begin_t() bloc. + void begin_t() { + if (!code_begin_t) return; + 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_begin_t.end(); + for (p_code = code_begin_t; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + p_code_end = code.end(); + } + + // Evaluation procedure for end_t() bloc. + void end_t() { + if (!code_end_t) 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; + mem[_cimg_mp_slot_x] = imgin._width - 1.; + mem[_cimg_mp_slot_y] = imgin._height - 1.; + mem[_cimg_mp_slot_z] = imgin._depth - 1.; + mem[_cimg_mp_slot_c] = imgin._spectrum - 1.; + } 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_t.end(); + for (p_code = code_end_t; p_code_data; + const ulongT target = opcode[1]; + mem[target] = _cimg_mp_defunc(*this); + } + } + + // Evaluation procedure the end() bloc. + void end() { + if (!code_end) return; + if (imgin) { + mem[_cimg_mp_slot_x] = imgin._width - 1.; + mem[_cimg_mp_slot_y] = imgin._height - 1.; + mem[_cimg_mp_slot_z] = imgin._depth - 1.; + mem[_cimg_mp_slot_c] = imgin._spectrum - 1.; } 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(&mp.mem[pos + 1],siz,1,1,1,true) = CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 1 : // Operator+ + CImg(&mp.mem[pos + 1],siz,1,1,1,true)+=CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 2 : // Operator- + CImg(&mp.mem[pos + 1],siz,1,1,1,true)-=CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 3 : // Operator* + CImg(&mp.mem[pos + 1],siz,1,1,1,true)*=CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 4 : // Operator/ + CImg(&mp.mem[pos + 1],siz,1,1,1,true)/=CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 5 : // Operator& + CImg(&mp.mem[pos + 1],siz,1,1,1,true)&=CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 6 : // Operator| + CImg(&mp.mem[pos + 1],siz,1,1,1,true)|=CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 7 : // Operator 'xor' + CImg(&mp.mem[pos + 1],siz,1,1,1,true)^=CImg(&mem[pos + 1],siz,1,1,1,true); + break; + case 8 : { // Operator&& + CImg + arg1(&mp.mem[pos + 1],siz,1,1,1,true), + arg2(&mem[pos + 1],siz,1,1,1,true); + cimg_foroff(arg1,off) arg1[off] = arg1[off] && arg2[off]; + } break; + case 9 : { // Operator|| + CImg + arg1(&mp.mem[pos + 1],siz,1,1,1,true), + arg2(&mem[pos + 1],siz,1,1,1,true); + cimg_foroff(arg1,off) arg1[off] = arg1[off] || arg2[off]; + } break; + case 10 : // Operator 'min' + CImg(&mp.mem[pos + 1],siz,1,1,1,true).min(CImg(&mem[pos + 1],siz,1,1,1,true)); + break; + case 11 : // Operator 'max' + CImg(&mp.mem[pos + 1],siz,1,1,1,true).max(CImg(&mem[pos + 1],siz,1,1,1,true)); + break; + } + } + } + + // Return specified argument number as a string. + static const char *s_argth(const unsigned int n_arg) { + const char + *_s_arg[] = { "", "First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh", "Eighth","Ninth", + "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", + "20th", "21st", "22nd", "23rd", "24th", "25th", "26th", "27th", "28th", "One of the" }; + return _s_arg[n_arg s_calling_function() const { + CImg 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 type of a memory slot as a string. CImg s_type(const unsigned int arg) const { CImg res; if (_cimg_mp_is_vector(arg)) { // Vector CImg::string("vectorXXXXXXXXXXXXXXXX").move_to(res); - std::sprintf(res._data + 6,"%u",_cimg_mp_size(arg)); - } else CImg::string("scalar").move_to(res); + cimg_snprintf(res._data + 6,res._width - 6,"%u",_cimg_mp_size(arg)); + } else if (_cimg_mp_is_const_scalar(arg)) CImg::string("const scalar").move_to(res); // Const scalar + else CImg::string("scalar").move_to(res); // Scalar return res; } + // Return reference state of a memory slot as a string. + CImg s_ref(const unsigned int *const p_ref) const { + CImg res; + if (!p_ref || !*p_ref) return res.assign(1,1,1,1,0); + res.assign(32); + switch (p_ref[0]) { + case 1 : // Reference to vector value as a scalar + cimg_snprintf(res,res._width,", ref: ([%u])[%u]", + p_ref[1],p_ref[2]); + break; + case 2 : // Reference to image value as a scalar (offset) + if (p_ref[1]==~0U) + cimg_snprintf(res,res._width,", ref: %c[%u]", + p_ref[2]?'j':'i',p_ref[3]); + else + cimg_snprintf(res,res._width,", ref: %c[#%u,%u]", + p_ref[2]?'j':'i',p_ref[1],p_ref[3]); + break; + case 3 : // Reference to image value as a scalar (coordinates) + if (p_ref[1]==~0U) + cimg_snprintf(res,res._width,", ref: %c(%u,%u,%u,%u)", + p_ref[2]?'j':'i',p_ref[3],p_ref[4],p_ref[5],p_ref[6]); + else + cimg_snprintf(res,res._width,", ref: %c(#%u,%u,%u,%u,%u)", + p_ref[2]?'j':'i',p_ref[1],p_ref[3],p_ref[4],p_ref[5],p_ref[6]); + break; + case 4 : // Reference to image value as a vector (offset) + if (p_ref[1]==~0U) + cimg_snprintf(res,res._width,", ref: %c[%u]", + p_ref[2]?'J':'I',p_ref[3]); + else + cimg_snprintf(res,res._width,", ref: %c[#%u,%u]", + p_ref[2]?'J':'I',p_ref[1],p_ref[3]); + break; + case 5 : // Reference to image value as a vector (coordinates) + if (p_ref[1]==~0U) + cimg_snprintf(res,res._width,", ref: %c(%u,%u,%u)", + p_ref[2]?'J':'I',p_ref[3],p_ref[4],p_ref[5]); + else + cimg_snprintf(res,res._width,", ref: %c(#%u,%u,%u,%u)", + p_ref[2]?'J':'I',p_ref[1],p_ref[3],p_ref[4],p_ref[5]); + break; + } + return res; + } + + // Count parentheses/brackets level of each character of the expression. + CImg get_level(CImg& _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 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_appname "_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_appname "_math_parser] " + "CImg<%s>::%s: Unbalanced parentheses/brackets, in expression '%s'.", + pixel_type(),_cimg_mp_calling_function, + _expr._data); + } + return res; + } + + // Find and return index of current image 'imgin' within image list 'imglist'. + unsigned int get_mem_img_index() { + if (mem_img_index==~0U) { + if (&imgout>imglist.data() && &imgout='0' && c2<='9') rp = 21 + c2 - '0'; // i0...i9 + else if (c2=='m') rp = 4; // im + else if (c2=='M') rp = 5; // iM + else if (c2=='a') rp = 6; // ia + else if (c2=='v') rp = 7; // iv + else if (c2=='d') rp = 8; // id + else if (c2=='s') rp = 9; // is + else if (c2=='p') rp = 10; // ip + else if (c2=='c') rp = 11; // ic + else if (c2=='n') rp = 12; // in + } else if (c2=='m') { + if (c1=='x') rp = 13; // xm + else if (c1=='y') rp = 14; // ym + else if (c1=='z') rp = 15; // zm + else if (c1=='c') rp = 16; // cm + } else if (c2=='M') { + if (c1=='x') rp = 17; // xM + else if (c1=='y') rp = 18; // yM + else if (c1=='z') rp = 19; // zM + else if (c1=='c') rp = 20; // 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') rp = 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') rp = 2; // whds + } else if (!std::strcmp(variable_name,"interpolation")) rp = 31; // interpolation + else if (!std::strcmp(variable_name,"boundary")) rp = 32; // boundary + + if (rp!=~0U) { rpos = rp; return; } // One of the reserved labels + + // Multi-char variable name : check for existing variable with same name + cimglist_for(variable_def,i) + if (!std::strcmp(variable_name,variable_def[i])) { pos = i; break; } + } + + // Return true if all values of a vector are computation values. + bool is_comp_vector(const unsigned int arg) const { + unsigned int siz = _cimg_mp_size(arg); + if (siz>128) 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; + } + + // 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_const_scalar(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_const_scalar(arg)) { + const char *const s_arg = s_argth(n_arg); + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s %s%s (of type '%s') is not a constant, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", + s_arg,*s_arg?" argument":" Argument",s_type(arg)._data,s0); + } + const double val = mem[arg]; + + if (!((!mode || (double)(int)mem[arg]==mem[arg]) && + (mode<2 || mem[arg]>=(mode==3)))) { + const char *const s_arg = s_argth(n_arg); + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s %s%s (of type '%s' and value %g) is not a%s constant, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", + s_arg,*s_arg?" argument":" Argument",s_type(arg)._data,val, + !mode?"":mode==1?"n integer": + mode==2?" positive integer":" strictly positive integer",s0); + } + } + + // Check if an image index is a constant value. + void check_const_index(const unsigned int arg, + char *const ss, char *const se, const char saved_char) { + if (arg!=~0U && !_cimg_mp_is_const_scalar(arg)) { + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s Specified image index is not a constant, " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",s0); + } + } + + // Check that specified constant is not nan. + void check_notnan_index(const unsigned int arg, + char *const ss, char *const se, const char saved_char) { + if (arg!=~0U && + (arg==_cimg_mp_slot_nan || (_cimg_mp_is_const_scalar(arg) && cimg::type::is_nan(mem[arg])))) { + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s Specified index is NaN.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":""); + } + } + + // 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_size(arg), + n = (unsigned int)cimg::round(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"; + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s %s%s (of type '%s') " + "cannot be considered as a square matrix, in expression '%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); + } + } + + // 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_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 = s_argth(n_arg); + CImg 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'"); + } + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s %s%s has invalid type '%s' (should be %s), " + "in expression '%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); + } + } + + // Check that imglist are not empty. + void check_list(char *const ss, char *const se, const char saved_char) { + if (!imglist) { + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s Image list cannot be empty, for expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"",s0); + } + } + + static void mp_check_list(_cimg_math_parser& mp, const char *const funcname) { + if (!mp.imglist) + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>: Function '%s()': Images list cannot be empty.", + pixel_type(),funcname); + } + // Insert constant value in memory. - unsigned int constant(const double val) { + unsigned int const_scalar(const double val) { // Search for built-in constant. if (cimg::type::is_nan(val)) return _cimg_mp_slot_nan; @@ -19846,136 +23705,14 @@ namespace cimg_library_suffixed { return pos; } - // Insert code instructions for processing scalars. - unsigned int scalar() { // Insert new scalar in memory. + // Insert new scalar in memory. + unsigned int scalar() { 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::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::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::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::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::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::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::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::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 calling_function_s() const { - CImg 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_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_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 + // Insert new vector of specified size in memory. + unsigned int vector(const unsigned int siz) { if (mempos + siz>=mem._width) { mem.resize(2*mem._width + siz,1,1,1,0); memtype.resize(mem._width,1,1,1,0); @@ -19987,14 +23724,16 @@ namespace cimg_library_suffixed { return pos; } - unsigned int vector(const unsigned int siz, const double value) { // Insert new initialized vector + // Insert new initialized vector. + unsigned int vector(const unsigned int siz, const double value) { const unsigned int pos = vector(siz); double *ptr = &mem[pos] + 1; for (unsigned int i = 0; i0) *(ptr++) = -1; + } + + unsigned int scalar0(const mp_func op) { + const unsigned int pos = scalar(); + CImg::vector((ulongT)op,pos).move_to(code); + return_new_comp = true; + return pos; + } + + unsigned int scalar1(const mp_func op, const unsigned int arg1) { + const unsigned int pos = + arg1!=~0U && arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1) && op!=mp_copy?arg1: + ((return_new_comp = true), scalar()); + CImg::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!=~0U && arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: + arg2!=~0U && arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: + ((return_new_comp = true), scalar()); + CImg::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!=~0U && arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: + arg2!=~0U && arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: + arg3!=~0U && arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: + ((return_new_comp = true), scalar()); + CImg::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!=~0U && arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: + arg2!=~0U && arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: + arg3!=~0U && arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: + arg4!=~0U && arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4: + ((return_new_comp = true), scalar()); + CImg::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!=~0U && arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: + arg2!=~0U && arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: + arg3!=~0U && arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: + arg4!=~0U && arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4: + arg5!=~0U && arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5: + ((return_new_comp = true), scalar()); + CImg::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!=~0U && arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: + arg2!=~0U && arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: + arg3!=~0U && arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: + arg4!=~0U && arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4: + arg5!=~0U && arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5: + arg6!=~0U && arg6>_cimg_mp_slot_c && _cimg_mp_is_comp(arg6)?arg6: + ((return_new_comp = true), scalar()); + CImg::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!=~0U && arg1>_cimg_mp_slot_c && _cimg_mp_is_comp(arg1)?arg1: + arg2!=~0U && arg2>_cimg_mp_slot_c && _cimg_mp_is_comp(arg2)?arg2: + arg3!=~0U && arg3>_cimg_mp_slot_c && _cimg_mp_is_comp(arg3)?arg3: + arg4!=~0U && arg4>_cimg_mp_slot_c && _cimg_mp_is_comp(arg4)?arg4: + arg5!=~0U && arg5>_cimg_mp_slot_c && _cimg_mp_is_comp(arg5)?arg5: + arg6!=~0U && arg6>_cimg_mp_slot_c && _cimg_mp_is_comp(arg6)?arg6: + arg7!=~0U && arg7>_cimg_mp_slot_c && _cimg_mp_is_comp(arg7)?arg7: + ((return_new_comp = true), scalar()); + CImg::vector((ulongT)op,pos,arg1,arg2,arg3,arg4,arg5,arg6,arg7).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_size(pos); if (siz>24) CImg::vector((ulongT)mp_self_map_vector_s,pos,siz,(ulongT)op,arg1).move_to(code); @@ -20025,8 +23865,9 @@ namespace cimg_library_suffixed { unsigned int vector1_v(const mp_func op, const unsigned int arg1) { const unsigned int siz = _cimg_mp_size(arg1), - pos = is_comp_vector(arg1)?arg1:vector(siz); - if (siz>24) CImg::vector((ulongT)mp_vector_map_v,pos,siz,(ulongT)op,arg1).move_to(code); + pos = is_comp_vector(arg1)?arg1: + ((return_new_comp = true), vector(siz)); + if (siz>24) CImg::vector((ulongT)mp_vector_map_v,pos,1,siz,(ulongT)op,arg1).move_to(code); else { code.insert(siz); for (unsigned int k = 1; k<=siz; ++k) @@ -20038,8 +23879,9 @@ namespace cimg_library_suffixed { unsigned int vector2_vv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { const unsigned int siz = _cimg_mp_size(arg1), - pos = is_comp_vector(arg1)?arg1:is_comp_vector(arg2)?arg2:vector(siz); - if (siz>24) CImg::vector((ulongT)mp_vector_map_vv,pos,siz,(ulongT)op,arg1,arg2).move_to(code); + pos = is_comp_vector(arg1)?arg1:is_comp_vector(arg2)?arg2: + ((return_new_comp = true), vector(siz)); + if (siz>24) CImg::vector((ulongT)mp_vector_map_vv,pos,2,siz,(ulongT)op,arg1,arg2).move_to(code); else { code.insert(siz); for (unsigned int k = 1; k<=siz; ++k) @@ -20051,8 +23893,9 @@ namespace cimg_library_suffixed { unsigned int vector2_vs(const mp_func op, const unsigned int arg1, const unsigned int arg2) { const unsigned int siz = _cimg_mp_size(arg1), - pos = is_comp_vector(arg1)?arg1:vector(siz); - if (siz>24) CImg::vector((ulongT)mp_vector_map_vs,pos,siz,(ulongT)op,arg1,arg2).move_to(code); + pos = is_comp_vector(arg1)?arg1: + ((return_new_comp = true), vector(siz)); + if (siz>24) CImg::vector((ulongT)mp_vector_map_v,pos,2,siz,(ulongT)op,arg1,arg2).move_to(code); else { code.insert(siz); for (unsigned int k = 1; k<=siz; ++k) @@ -20064,8 +23907,9 @@ namespace cimg_library_suffixed { unsigned int vector2_sv(const mp_func op, const unsigned int arg1, const unsigned int arg2) { const unsigned int siz = _cimg_mp_size(arg2), - pos = is_comp_vector(arg2)?arg2:vector(siz); - if (siz>24) CImg::vector((ulongT)mp_vector_map_sv,pos,siz,(ulongT)op,arg1,arg2).move_to(code); + pos = is_comp_vector(arg2)?arg2: + ((return_new_comp = true), vector(siz)); + if (siz>24) CImg::vector((ulongT)mp_vector_map_sv,pos,2,siz,(ulongT)op,arg1,arg2).move_to(code); else { code.insert(siz); for (unsigned int k = 1; k<=siz; ++k) @@ -20078,8 +23922,9 @@ namespace cimg_library_suffixed { const unsigned int arg3) { const unsigned int siz = _cimg_mp_size(arg1), - pos = is_comp_vector(arg1)?arg1:vector(siz); - if (siz>24) CImg::vector((ulongT)mp_vector_map_vss,pos,siz,(ulongT)op,arg1,arg2,arg3).move_to(code); + pos = is_comp_vector(arg1)?arg1: + ((return_new_comp = true), vector(siz)); + if (siz>24) CImg::vector((ulongT)mp_vector_map_v,pos,3,siz,(ulongT)op,arg1,arg2,arg3).move_to(code); else { code.insert(siz); for (unsigned int k = 1; k<=siz; ++k) @@ -20088,136 +23933,55 @@ namespace cimg_library_suffixed { 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_appname "_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); + unsigned int vector4_vvss(const mp_func op, const unsigned int arg1, const unsigned int arg2, + const unsigned int arg3, const unsigned int arg4) { const unsigned int - siz = _cimg_mp_size(arg), - n = (unsigned int)cimg::round(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_appname "_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()?"...":""); + siz = _cimg_mp_size(arg1), + pos = is_comp_vector(arg1)?arg1:is_comp_vector(arg2)?arg2: + ((return_new_comp = true), vector(siz)); + if (siz>24) + CImg::vector((ulongT)mp_vector_map_vv,pos,4,siz,(ulongT)op,arg1,arg2,arg3,arg4).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2 + k,arg3,arg4). + move_to(code[code._width - 1 - siz + k]); } + return pos; } - // 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_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 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_appname "_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()?"...":""); + unsigned int vector4_vsss(const mp_func op, const unsigned int arg1, const unsigned int arg2, + const unsigned int arg3, const unsigned int arg4) { + const unsigned int + siz = _cimg_mp_size(arg1), + pos = is_comp_vector(arg1)?arg1: + ((return_new_comp = true), vector(siz)); + if (siz>24) + CImg::vector((ulongT)mp_vector_map_v,pos,4,siz,(ulongT)op,arg1,arg2,arg3,arg4).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1 + k,arg2,arg3,arg4). + move_to(code[code._width - 1 - siz + k]); } + return pos; } - // 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_appname "_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_appname "_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_appname "_math_parser] " - "CImg<%s>::%s: %s%s Invalid construction of a vector with possible dynamic size, " - "in expression '%s%s%s'.", - pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":"", - s0!=expr._data?"...":"",s0,se<&expr.back()?"...":""); + unsigned int vector4_svss(const mp_func op, const unsigned int arg1, const unsigned int arg2, + const unsigned int arg3, const unsigned int arg4) { + const unsigned int + siz = _cimg_mp_size(arg2), + pos = is_comp_vector(arg2)?arg2: + ((return_new_comp = true), vector(siz)); + if (siz>24) + CImg::vector((ulongT)mp_vector_map_sv,pos,4,siz,(ulongT)op,arg1,arg2,arg3,arg4).move_to(code); + else { + code.insert(siz); + for (unsigned int k = 1; k<=siz; ++k) + CImg::vector((ulongT)op,pos + k,arg1,arg2 + k,arg3,arg4). + move_to(code[code._width - 1 - siz + k]); } + return pos; } // Evaluation functions, known by the parser. @@ -20228,6 +23992,14 @@ namespace cimg_library_suffixed { #endif #define _mp_arg(x) mp.mem[mp.opcode[x]] +#ifdef cimg_mp_func_abort + static double mp_abort(_cimg_math_parser& mp) { + cimg::unused(mp); + cimg_mp_func_abort(); + return cimg::type::nan(); + } +#endif + static double mp_abs(_cimg_math_parser& mp) { return cimg::abs(_mp_arg(2)); } @@ -20267,33 +24039,122 @@ namespace cimg_library_suffixed { return _mp_arg(ind + 4); } + static double mp_arg0(_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:_ind + 1U, + 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::nan(); + } + if (ind>=nb_args) return 0; + return _mp_arg(ind + 4); + } + static double mp_argkth(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - const double val = mp_kth(mp); - for (unsigned int i = 4; i values; + if (i_end==5) values.assign(&_mp_arg(3),(unsigned int)mp.opcode[4],1,1,1,true); // Only a single argument + else { + unsigned int siz = 0; + for (unsigned int i = 4; i1) std::memcpy(ptr,&_mp_arg(i),len*sizeof(double)); + else *ptr = _mp_arg(i); + ptr+=len; + } + } + longT ind = (longT)cimg::round(_mp_arg(3)); + ++values._data; --values._width; // Skip first value + if (ind<0) ind+=values.width() + 1; + ind = cimg::cut(ind,(longT)1,(longT)values.width()); + const double kth = values.kth_smallest((ulongT)(ind - 1)); + --values._data; ++values._width; + for (unsigned int argkth = 1; argkth::nan(); } 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::inf(); + unsigned int siz = 0, argmin = 0; + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k::inf(); + unsigned int siz = 0, argminabs = 0; + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; kval) { val = _val; argval = i - 3; } + double val, valmax = -cimg::type::inf(); + unsigned int siz = 0, argmax = 0; + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; kvalmax) { valmax = val; argmax = siz + k; } } + } else { val = _mp_arg(i); if (val>valmax) { valmax = val; argmax = siz; } } + siz+=len; } - return (double)argval; + return (double)argmax; + } + + static double mp_argmaxabs(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val, abs_val, abs_valmaxabs = 0; + unsigned int siz = 0, argmaxabs = 0; + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; kabs_valmaxabs) { abs_valmaxabs = abs_val; argmaxabs = siz + k; } + } + } else { + val = _mp_arg(i); + abs_val = cimg::abs(val); + if (abs_val>abs_valmaxabs) { abs_valmaxabs = abs_val; argmaxabs = siz; } + } + siz+=len; + } + return (double)argmaxabs; } static double mp_asin(_cimg_math_parser& mp) { @@ -20310,9 +24171,17 @@ namespace cimg_library_suffixed { static double mp_avg(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - for (unsigned int i = 4; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k::nan(); } - static double mp_cats(_cimg_math_parser& mp) { - const double *ptrd = &_mp_arg(1) + 1; - const unsigned int - sizd = (unsigned int)mp.opcode[2], - nb_args = (unsigned int)(mp.opcode[3] - 4)/2; - CImgList _str; - for (unsigned int n = 0; n(ptrs,l,1,1,1,true).move_to(_str); - } else CImg::vector((char)_mp_arg(4 + 2*n)).move_to(_str); // Scalar argument - } - CImg(1,1,1,1,0).move_to(_str); - const CImg str = _str>'x'; - const unsigned int l = std::min(str._width,sizd); - CImg(ptrd,l,1,1,1,true) = str.get_shared_points(0,l - 1); - return cimg::type::nan(); + static double mp_c2o(_cimg_math_parser& mp) { + mp_check_list(mp,"c2o"); + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + const CImg &img = ind==~0U?mp.imgin:mp.imglist[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5), + c = (int)_mp_arg(6); + return (double)img.offset(x,y,z,c); } static double mp_cbrt(_cimg_math_parser& mp) { @@ -20392,10 +24252,10 @@ namespace cimg_library_suffixed { } static double mp_complex_conj(_cimg_math_parser& mp) { - const double *ptrs = &_mp_arg(2) + 1; + const double real = _mp_arg(2), imag = _mp_arg(3); double *ptrd = &_mp_arg(1) + 1; - *(ptrd++) = *(ptrs++); - *ptrd = -*(ptrs); + ptrd[0] = real; + ptrd[1] = -imag; return cimg::type::nan(); } @@ -20424,18 +24284,18 @@ namespace cimg_library_suffixed { } static double mp_complex_exp(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3), exp_real = std::exp(real); 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); + ptrd[0] = exp_real*std::cos(imag); + ptrd[1] = exp_real*std::sin(imag); return cimg::type::nan(); } static double mp_complex_log(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3); 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); + ptrd[0] = 0.5*std::log(real*real + imag*imag); + ptrd[1] = std::atan2(imag,real); return cimg::type::nan(); } @@ -20509,12 +24369,140 @@ namespace cimg_library_suffixed { return cimg::type::nan(); } + static double mp_complex_cos(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = std::cos(real)*std::cosh(imag); + ptrd[1] = -std::sin(real)*std::sinh(imag); + return cimg::type::nan(); + } + + static double mp_complex_sin(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = std::sin(real)*std::cosh(imag); + ptrd[1] = std::cos(real)*std::sinh(imag); + return cimg::type::nan(); + } + + static double mp_complex_sqrt(_cimg_math_parser& mp) { + const double + real = _mp_arg(2), imag = _mp_arg(3), + r = std::sqrt(cimg::_hypot(real,imag)), + theta = std::atan2(imag,real)/2; + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = r*std::cos(theta); + ptrd[1] = r*std::sin(theta); + return cimg::type::nan(); + } + + static double mp_complex_tan(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3), denom = std::cos(2*real) + std::cosh(2*imag); + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = std::sin(2*real)/denom; + ptrd[1] = std::sinh(2*imag)/denom; + return cimg::type::nan(); + } + + static double mp_complex_cosh(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = std::cosh(real)*std::cos(imag); + ptrd[1] = std::sinh(real)*std::sin(imag); + return cimg::type::nan(); + } + + static double mp_complex_sinh(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = std::sinh(real)*std::cos(imag); + ptrd[1] = std::cosh(real)*std::sin(imag); + return cimg::type::nan(); + } + + static double mp_complex_tanh(_cimg_math_parser& mp) { + const double real = _mp_arg(2), imag = _mp_arg(3), denom = std::cosh(2*real) + std::cos(2*imag); + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = std::sinh(2*real)/denom; + ptrd[1] = std::sin(2*imag)/denom; + return cimg::type::nan(); + } + static double mp_continue(_cimg_math_parser& mp) { mp.break_type = 2; mp.p_code = mp.p_break - 1; return cimg::type::nan(); } + static double mp_convolve(_cimg_math_parser &mp) { + return _mp_correlate(mp,true); + } + + static double mp_copy(_cimg_math_parser& mp) { + return _mp_arg(2); + } + + static double mp_correlate(_cimg_math_parser &mp) { + return _mp_correlate(mp,false); + } + + static double _mp_correlate(_cimg_math_parser &mp, bool is_convolve) { + double *ptrd = &_mp_arg(1) + 1; + const double *const ptrA = &_mp_arg(2) + 1, *const ptrM = &_mp_arg(7) + 1; + const unsigned int + wA = (unsigned int)mp.opcode[3], + hA = (unsigned int)mp.opcode[4], + dA = (unsigned int)mp.opcode[5], + sA = (unsigned int)mp.opcode[6], + wM = (unsigned int)mp.opcode[8], + hM = (unsigned int)mp.opcode[9], + dM = (unsigned int)mp.opcode[10], + sM = (unsigned int)mp.opcode[11], + boundary_conditions = (unsigned int)_mp_arg(12), + channel_mode = (unsigned int)mp.opcode[14]; + const bool + is_normalized = (bool)_mp_arg(13), + interpolation_type = (bool)_mp_arg(30); + const int + xcenter = mp.opcode[15]!=~0U?(int)_mp_arg(15):(int)(~0U>>1), + ycenter = mp.opcode[16]!=~0U?(int)_mp_arg(16):(int)(~0U>>1), + zcenter = mp.opcode[17]!=~0U?(int)_mp_arg(17):(int)(~0U>>1), + xstart = (int)mp.opcode[18], + ystart = (int)mp.opcode[19], + zstart = (int)mp.opcode[20], + xend = (int)mp.opcode[21], + yend = (int)mp.opcode[22], + zend = (int)mp.opcode[23]; + const float + xstride = (float)_mp_arg(24), + ystride = (float)_mp_arg(25), + zstride = (float)_mp_arg(26), + xdilation = (float)_mp_arg(27), + ydilation = (float)_mp_arg(28), + zdilation = (float)_mp_arg(29); + CImg res; + if (is_convolve) res = CImg(ptrA,wA,hA,dA,sA,true). + get_convolve(CImg(ptrM,wM,hM,dM,sM,true), + boundary_conditions,is_normalized,channel_mode, + xcenter,ycenter,zcenter, + xstart,ystart,zstart, + xend,yend,zend, + xstride,ystride,zstride, + xdilation,ydilation,zdilation, + interpolation_type); + else res = CImg(ptrA,wA,hA,dA,sA,true). + get_correlate(CImg(ptrM,wM,hM,dM,sM,true), + boundary_conditions,is_normalized,channel_mode, + xcenter,ycenter,zcenter, + xstart,ystart,zstart, + xend,yend,zend, + xstride,ystride,zstride, + xdilation,ydilation,zdilation, + interpolation_type); + CImg(ptrd,res._width,res._height,res._depth,res._spectrum,true) = res; + return cimg::type::nan(); + } + static double mp_cos(_cimg_math_parser& mp) { return std::cos(_mp_arg(2)); } @@ -20523,8 +24511,25 @@ namespace cimg_library_suffixed { return std::cosh(_mp_arg(2)); } + static double mp_cov(_cimg_math_parser& mp) { + const unsigned int + _siz = (unsigned int)mp.opcode[4], + siz = std::max(_siz,1U), + off = _siz?1:0, + sizm1 = siz>1?siz - 1:1; + const CImg + A(&_mp_arg(2) + off,1,siz,1,1,true), + B(&_mp_arg(3) + off,1,siz,1,1,true); + const double + avgA = (unsigned int)mp.opcode[5]==~0U?A.mean():_mp_arg(5), + avgB = (unsigned int)mp.opcode[6]==~0U?B.mean():_mp_arg(6); + double res = 0; + cimg_forY(A,k) res+=(A[k] - avgA)*(B[k] - avgB); + return res/sizm1; + } + static double mp_critical(_cimg_math_parser& mp) { - const double res = _mp_arg(1); + const ulongT g_target = mp.opcode[1]; cimg_pragma_openmp(critical(mp_critical)) { for (const CImg *const p_end = ++mp.p_code + mp.opcode[2]; @@ -20535,10 +24540,10 @@ namespace cimg_library_suffixed { } } --mp.p_code; - return res; + return mp.mem[g_target]; } - static double mp_crop(_cimg_math_parser& mp) { + 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 @@ -20546,10 +24551,10 @@ namespace cimg_library_suffixed { 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); + const unsigned int boundary_conditions = (unsigned int)_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 &img = ind==~0U?mp.imgin:mp.listin[ind]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + const CImg &img = ind==~0U?mp.imgin:mp.imglist[ind]; if (!img) std::memset(ptrd,0,dx*dy*dz*dc*sizeof(double)); else CImg(ptrd,dx,dy,dz,dc,true) = img.get_crop(x,y,z,c, x + dx - 1,y + dy - 1, @@ -20572,33 +24577,187 @@ namespace cimg_library_suffixed { return valcmax?cmax:val; } + static double mp_da_back_or_pop(_cimg_math_parser& mp) { + const bool is_pop = (bool)mp.opcode[4]; + const char *const s_op = is_pop?"da_pop":"da_back"; + mp_check_list(mp,s_op); + const unsigned int + dim = (unsigned int)mp.opcode[2], + ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width()); + double *const ptrd = &_mp_arg(1) + (dim>1?1:0); + CImg &img = mp.imglist[ind]; + int siz = img?(int)img[img._height - 1]:0; + if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': " + "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", + mp.imgout.pixel_type(),s_op,ind, + img.width(),img.height(),img.depth(),img.spectrum(), + img._width==1 && img._depth==1?"":" (contains invalid element counter)"); + if (!siz) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': " + "Specified dynamic array #%u contains no elements.", + mp.imgout.pixel_type(),s_op,ind); + + double ret = cimg::type::nan(); + if (dim==1) ret = img[siz - 1]; // Scalar element + else cimg_forC(img,c) ptrd[c] = img(0,siz - 1,0,c); // Vector element + if (is_pop) { // Remove element from array + --siz; + if (img.height()>32 && siz<2*img.height()/3) // Reduce size of dynamic array + img.resize(1,std::max(2*siz + 1,32),1,-100,0); + img[img._height - 1] = (T)siz; + } + return ret; + } + + static double mp_da_freeze(_cimg_math_parser& mp) { + const char *const s_op = "da_freeze"; + mp_check_list(mp,s_op); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; + int siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0; + if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': " + "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", + mp.imgout.pixel_type(),s_op,ind, + img.width(),img.height(),img.depth(),img.spectrum(), + img._width==1 && img._depth==1?"":" (contains invalid element counter)"); + if (siz) img.resize(1,siz,1,-100,0,0); else img.assign(); + return cimg::type::nan(); + } + + static double mp_da_insert_or_push(_cimg_math_parser& mp) { + const char *const s_op = mp.opcode[3]==~0U?"da_push":"da_insert"; + mp_check_list(mp,s_op); + const unsigned int + dim = (unsigned int)mp.opcode[4], + _dim = std::max(1U,dim), + nb_elts = (unsigned int)mp.opcode[5] - 6, + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; + const int + siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0, + pos0 = mp.opcode[3]==~0U?siz:(int)_mp_arg(3), + pos = pos0<0?pos0 + siz:pos0; + + if (img && _dim!=img._spectrum) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': " + "Element to insert has invalid size %u (should be %u).", + mp.imgout.pixel_type(),s_op,_dim,img._spectrum); + if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': " + "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", + mp.imgout.pixel_type(),s_op,ind, + img.width(),img.height(),img.depth(),img.spectrum(), + img._width==1 && img._depth==1?"":" (contains invalid element counter)"); + if (pos<0 || pos>siz) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': " + "Invalid position %d (not in range -%d...%d).", + mp.imgout.pixel_type(),s_op,pos0,siz,siz); + + if (siz + nb_elts + 1>=img._height) // Increase size of dynamic array, if necessary + img.resize(1,2*siz + nb_elts + 1,1,_dim,0); + + if (pos!=siz) // Move existing data in dynamic array + cimg_forC(img,c) std::memmove(img.data(0,pos + nb_elts,0,c),img.data(0,pos,0,c),(siz - pos)*sizeof(T)); + + if (!dim) // Scalar or vector1() elements + for (unsigned int k = 0; k1 + for (unsigned int k = 0; k::nan(); + } + + static double mp_da_remove(_cimg_math_parser& mp) { + mp_check_list(mp,"da_remove"); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; + int siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0; + if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'da_remove()': " + "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", + mp.imgout.pixel_type(),ind, + img.width(),img.height(),img.depth(),img.spectrum(), + img._width==1 && img._depth==1?"":" (contains invalid element counter)"); + if (!siz) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'da_remove()': " + "Dynamic array is empty.", + mp.imgout.pixel_type()); + int + start0 = mp.opcode[3]==~0U?siz - 1:_mp_arg(3), + end0 = mp.opcode[4]==~0U?start0:_mp_arg(4), + start = start0<0?start0 + siz:start0, + end = end0<0?end0 + siz:end0; + if (start<0 || start>=siz || end<0 || end>=siz || start>end) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'da_remove()': " + "Invalid starting (%d) and ending (%d) positions " + "(not ordered, in range -%d...%d).", + mp.imgout.pixel_type(),start0,end0,siz,siz - 1); + if (end32 && siz<2*img.height()/3) // Reduce size of dynamic array + img.resize(1,std::max(2*siz + 1,32),1,-100,0); + img[img._height - 1] = (T)cimg::uint2float(siz); + return cimg::type::nan(); + } + + static double mp_da_size(_cimg_math_parser& mp) { + mp_check_list(mp,"da_size"); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; + const int siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0; + if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'da_size()': " + "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", + mp.imgout.pixel_type(),ind, + img.width(),img.height(),img.depth(),img.spectrum(), + img._width==1 && img._depth==1?"":" (contains invalid element counter)"); + return siz; + } + static double mp_date(_cimg_math_parser& mp) { const unsigned int - _arg = (unsigned int)mp.opcode[3], - _siz = (unsigned int)mp.opcode[4], - siz = _siz?_siz:1; - const double *const arg_in = _arg==~0U?0:&_mp_arg(3) + (_siz?1:0); - double *const arg_out = &_mp_arg(1) + (_siz?1:0); - if (arg_in) std::memcpy(arg_out,arg_in,siz*sizeof(double)); - else for (unsigned int i = 0; i filename(mp.opcode[2] - 5); - if (filename) { - const ulongT *ptrs = mp.opcode._data + 5; - cimg_for(filename,ptrd,char) *ptrd = (char)*(ptrs++); - cimg::fdate(filename,arg_out,siz); - } else cimg::date(arg_out,siz); - return _siz?cimg::type::nan():*arg_out; + if (!ptr_arg2) { // No filename specified + if (!siz_arg1) return cimg::date((unsigned int)*ptr_arg1); + if (siz_arg1==~0U) for (unsigned int k = 0; k::nan(); + } + + // Filename specified. + CImg ss(siz_arg2 + 1); + cimg_forX(ss,i) ss[i] = (char)ptr_arg2[i]; + ss.back() = 0; + if (!siz_arg1) return cimg::fdate(ss,(unsigned int)*ptr_arg1); + for (unsigned int k = 0; k::nan(); } static double mp_debug(_cimg_math_parser& mp) { CImg expr(mp.opcode[2] - 4); - const ulongT *ptrs = mp.opcode._data + 4; - cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); + { + 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 +#if cimg_use_openmp==0 const unsigned int n_thread = 0; #else const unsigned int n_thread = omp_get_thread_num(); @@ -20607,13 +24766,13 @@ namespace cimg_library_suffixed { { std::fprintf(cimg::output(), "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" - "Start debugging expression '%s', code length %u -> mem[%u] (memsize: %u)", + "Start debugging '%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 *const p_end = (++mp.p_code) + mp.opcode[3]; + const CImg *const p_end = ++mp.p_code + mp.opcode[3]; CImg _op; for ( ; mp.p_code &op = *mp.p_code; @@ -20630,7 +24789,7 @@ namespace cimg_library_suffixed { { std::fprintf(cimg::output(), "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" - "Opcode %p = [ %p,%s ] -> mem[%u] = %g", + "Opcode %p = [ %p,%s ] -> mem[%u] = %.17g", (void*)&mp,n_thread,mp.debug_indent,' ', (void*)mp.opcode._data,(void*)*mp.opcode,_op.value_string().data(), (unsigned int)target,mp.mem[target]); @@ -20642,7 +24801,7 @@ namespace cimg_library_suffixed { mp.debug_indent-=3; std::fprintf(cimg::output(), "\n[" cimg_appname "_math_parser] %p[thread #%u]:%*c" - "End debugging expression '%s' -> mem[%u] = %g (memsize: %u)", + "End debugging '%s' -> mem[%u] = %.17g (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()); @@ -20655,6 +24814,10 @@ namespace cimg_library_suffixed { return _mp_arg(2) - 1; } + static double mp_deg2rad(_cimg_math_parser& mp) { + return _mp_arg(2)*cimg::PI/180; + } + static double mp_det(_cimg_math_parser& mp) { const double *ptrs = &_mp_arg(2) + 1; const unsigned int k = (unsigned int)mp.opcode[3]; @@ -20662,17 +24825,19 @@ namespace cimg_library_suffixed { } static double mp_diag(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2], siz = mp.opcode[2] - 3; double *ptrd = &_mp_arg(1) + 1; - const double *ptrs = &_mp_arg(2) + 1; - const unsigned int k = (unsigned int)mp.opcode[3]; - CImg(ptrd,k,k,1,1,true) = CImg(ptrs,1,k,1,1,true).get_diagonal(); + std::memset(ptrd,0,siz*siz*sizeof(double)); + for (unsigned int i = 3; i::nan(); } static double mp_display_memory(_cimg_math_parser& mp) { cimg::unused(mp); std::fputc('\n',cimg::output()); - mp.mem.display("[" cimg_appname "_math_parser] Memory snapshot"); + CImg title(128); + cimg_snprintf(title,title._width,"%s (%u)","[" cimg_appname "_math_parser] Memory snapshot",mp.mem._width); + mp.mem.display(title); return cimg::type::nan(); } @@ -20712,7 +24877,7 @@ namespace cimg_library_suffixed { dot(CImg(&_mp_arg(3) + 1,1,siz,1,1,true)); } - static double mp_dowhile(_cimg_math_parser& mp) { + static double mp_do(_cimg_math_parser& mp) { const ulongT mem_body = mp.opcode[1], mem_cond = mp.opcode[2]; @@ -20748,48 +24913,9 @@ namespace cimg_library_suffixed { return mp.mem[mem_body]; } - static double mp_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 &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_appname "_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 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_appname "_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 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::nan(); - } - static double mp_echo(_cimg_math_parser& mp) { const unsigned int nb_args = (unsigned int)(mp.opcode[2] - 3)/2; + if (!nb_args) { std::fputc('\n',cimg::output()); return cimg::type::nan(); } // No arguments CImgList _str; CImg it; for (unsigned int n = 0; n(ptr,l,1,1,1,true).move_to(_str); } else { // Scalar argument -> number - it.assign(256); + it.assign(24); cimg_snprintf(it,it._width,"%.17g",_mp_arg(3 + 2*n)); CImg::string(it,false,true).move_to(_str); } @@ -20812,14 +24938,15 @@ namespace cimg_library_suffixed { } static double mp_ellipse(_cimg_math_parser& mp) { + mp_check_list(mp,"ellipse"); const unsigned int i_end = (unsigned int)mp.opcode[2]; unsigned int ind = (unsigned int)mp.opcode[3]; - if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.listin.width()); - CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width()); + CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; CImg color(img._spectrum,1,1,1,0); - bool is_invalid_arguments = false; - unsigned int i = 4; + bool is_invalid_arguments = false, is_outlined = false; float r1 = 0, r2 = 0, angle = 0, opacity = 1; + unsigned int i = 4, pattern = ~0U; int x0 = 0, y0 = 0; if (i>=i_end) is_invalid_arguments = true; else { @@ -20834,9 +24961,14 @@ namespace cimg_library_suffixed { else { r2 = (float)_mp_arg(i++); if (i args(i_end - 4); cimg_forX(args,k) args[k] = _mp_arg(4 + k); if (ind==~0U) @@ -20868,35 +25002,38 @@ namespace cimg_library_suffixed { return (double)(_mp_arg(2)==_mp_arg(3)); } - static double mp_ext(_cimg_math_parser& mp) { - const unsigned int nb_args = (unsigned int)(mp.opcode[2] - 3)/2; - CImgList _str; - CImg it; - for (unsigned int n = 0; n string - const double *ptr = &_mp_arg(3 + 2*n) + 1; - unsigned int l = 0; - while (l(ptr,l,1,1,1,true).move_to(_str); - } else { // Scalar argument -> number - it.assign(256); - cimg_snprintf(it,it._width,"%.17g",_mp_arg(3 + 2*n)); - CImg::string(it,false,true).move_to(_str); - } - } - CImg(1,1,1,1,0).move_to(_str); - CImg str = _str>'x'; -#ifdef cimg_mp_ext_function - cimg_mp_ext_function(str); +#if cimg_use_cpp11==1 + static double mp_erf(_cimg_math_parser& mp) { + return std::erf(_mp_arg(2)); + } #endif - return cimg::type::nan(); + + static double mp_erfinv(_cimg_math_parser& mp) { + return cimg::erfinv(_mp_arg(2)); } static double mp_exp(_cimg_math_parser& mp) { return std::exp(_mp_arg(2)); } + static double mp_expr(_cimg_math_parser& mp) { + const unsigned int + sizs = (unsigned int)mp.opcode[3], + w = (unsigned int)mp.opcode[4], + h = (unsigned int)mp.opcode[5], + d = (unsigned int)mp.opcode[6], + s = (unsigned int)mp.opcode[7], + sizd = w*h*d*s; + const double *ptrs = &_mp_arg(2) + 1; + double *ptrd = &_mp_arg(1); + CImg ss(sizs + 1); + cimg_for_inX(ss,0,ss.width() - 1,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + if (!sizd) return CImg(w,h,d,s,0).eval(ss,0,0,0,0,&mp.imglist); // Scalar result + CImg(++ptrd,w,h,d,s,true) = CImg(w,h,d,s,0).fill(ss,true,true,&mp.imglist); + return cimg::type::nan(); + } + static double mp_eye(_cimg_math_parser& mp) { double *ptrd = &_mp_arg(1) + 1; const unsigned int k = (unsigned int)mp.opcode[2]; @@ -20904,6 +25041,10 @@ namespace cimg_library_suffixed { return cimg::type::nan(); } + static double mp_f2ui(_cimg_math_parser& mp) { + return (double)cimg::float2uint((float)_mp_arg(2)); + } + static double mp_factorial(_cimg_math_parser& mp) { return cimg::factorial((int)_mp_arg(2)); } @@ -20912,10 +25053,54 @@ namespace cimg_library_suffixed { return cimg::fibonacci((int)_mp_arg(2)); } + static double mp_fill(_cimg_math_parser& mp) { + unsigned int siz = (unsigned int)mp.opcode[2]; + double + *ptrd = &_mp_arg(1), + *const ptrc = mp.opcode[3]!=~0U?&_mp_arg(3):0, + *const ptrs = &_mp_arg(4); + if (siz) ++ptrd; else ++siz; // Fill vector-valued slot + const CImg + *const p_body = ++mp.p_code, + *const p_end = p_body + mp.opcode[5]; + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + + unsigned int it = 0; + if (ptrc) { // Version with loop variable (3 arguments) + while (it_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; + else ptrd[it] = *ptrs; + ++it; + } + *ptrc = (double)it; + } else // Version without loop variable (2 arguments) + while (it_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; + else ptrd[it] = *ptrs; + ++it; + } + + mp.break_type = _break_type; + mp.p_code = p_end - 1; + return *ptrd; + } + static double mp_find(_cimg_math_parser& mp) { - const bool is_forward = (bool)_mp_arg(5); + const int _step = (int)_mp_arg(6), step = _step?_step:-1; 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); + longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):step>0?0:siz - 1); if (ind<0 || ind>=(longT)siz) return -1.; const double *const ptrb = &_mp_arg(2) + 1, @@ -20924,22 +25109,22 @@ namespace cimg_library_suffixed { *ptr = ptrb + ind; // Forward search - if (is_forward) { - while (ptr0) { + while (ptr=ptre?-1.:(double)(ptr - ptrb); } // Backward search. - while (ptr>=ptrb && *ptr!=val) --ptr; + while (ptr>=ptrb && *ptr!=val) ptr+=step; return ptr0?0:siz1 - 1); if (ind<0 || ind>=(longT)siz1) return -1.; const double *const ptr1b = &_mp_arg(2) + 1, @@ -20951,24 +25136,26 @@ namespace cimg_library_suffixed { *p2 = 0; // Forward search. - if (is_forward) { + if (step>0) { do { - while (ptr1=ptr1e) return -1.; p1 = ptr1 + 1; p2 = ptr2b + 1; while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + while (ptr1>=ptr1b && *ptr1!=*ptr2b) ptr1+=step; + if (ptr1=ptr1b); - return p2=ptr1b); + return p2 filename(mp.opcode._data + 3,mp.opcode[2] - 3); - return (double)cimg::fsize(filename); + const double *ptrs = &_mp_arg(2) + 1; + const ulongT siz = (ulongT)mp.opcode[3]; + CImg ss(siz + 1); + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + return (double)cimg::fsize(ss); } static double mp_g(_cimg_math_parser& mp) { cimg::unused(mp); - return cimg::grand(); + return cimg::grand(&mp.rng); } +#if cimg_use_cpp11==1 + static double mp_gamma(_cimg_math_parser& mp) { + return std::tgamma(_mp_arg(2)); + } +#endif + 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))/(_mp_arg(4)?std::sqrt(2*s*s*cimg::PI):1); } +#ifdef cimg_mp_func_get + static double mp_get(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + double *ptrd = &_mp_arg(1); + const unsigned int + sizs = (unsigned int)mp.opcode[3], + sizd = (unsigned int)mp.opcode[4]; + const bool to_string = (bool)mp.opcode[5]; + CImg ss(sizs + 1); + cimg_for_inX(ss,0,ss.width() - 1,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + if (sizd) cimg_mp_func_get(ptrd + 1,sizd,to_string,ss._data); + else cimg_mp_func_get(ptrd,0,to_string,ss._data); + return cimg::type::nan(); + } +#endif + static double mp_gcd(_cimg_math_parser& mp) { return cimg::gcd((long)_mp_arg(2),(long)_mp_arg(3)); } +#ifdef cimg_mp_func_name + static double mp_name(_cimg_math_parser& mp) { + double *const ptr = &_mp_arg(1) + 1; + const unsigned int siz = (unsigned int)mp.opcode[3]; + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind==~0U) std::memset(ptr,0,siz*sizeof(double)); + else { + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + cimg_mp_func_name(ind,ptr,siz); + } + return cimg::type::nan(); + } +#endif + static double mp_gt(_cimg_math_parser& mp) { return (double)(_mp_arg(2)>_mp_arg(3)); } @@ -21091,15 +25319,19 @@ namespace cimg_library_suffixed { 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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; return (double)img.depth(); } static double mp_image_display(_cimg_math_parser& mp) { - const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + mp_check_list(mp,"display"); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); cimg::mutex(6); - CImg &img = mp.listout[ind]; + CImg &img = mp.imglist[ind]; CImg title(256); std::fputc('\n',cimg::output()); cimg_snprintf(title,title._width,"[ Image #%u ]",ind); @@ -21108,24 +25340,83 @@ namespace cimg_library_suffixed { return cimg::type::nan(); } + 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) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width()); + } + CImg &img = ind==~0U?mp.imgout:mp.imglist[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_appname "_math_parser] CImg<%s>: Function 'draw()': " + "Sprite vector (%lu values) and its specified 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 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_appname "_math_parser] CImg<%s>: Function 'draw()': " + "Mask vector (%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 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::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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; return (double)img.height(); } static double mp_image_median(_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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; return (double)img.median(); } + static double mp_image_norm(_cimg_math_parser& mp) { + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; + return (double)img.magnitude(2); + } + static double mp_image_print(_cimg_math_parser& mp) { - const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + mp_check_list(mp,"print"); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); cimg::mutex(6); - CImg &img = mp.listout[ind]; + CImg &img = mp.imglist[ind]; CImg title(256); std::fputc('\n',cimg::output()); cimg_snprintf(title,title._width,"[ Image #%u ]",ind); @@ -21135,9 +25426,10 @@ namespace cimg_library_suffixed { } static double mp_image_resize(_cimg_math_parser& mp) { - const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()); + mp_check_list(mp,"resize"); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); cimg::mutex(6); - CImg &img = mp.listout[ind]; + CImg &img = mp.imglist[ind]; const double _w = mp.opcode[3]==~0U?-100:_mp_arg(3), _h = mp.opcode[4]==~0U?-100:_mp_arg(4), @@ -21170,18 +25462,22 @@ namespace cimg_library_suffixed { 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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; return (double)img.spectrum(); } static double mp_image_sort(_cimg_math_parser& mp) { + mp_check_list(mp,"sort"); const bool is_increasing = (bool)_mp_arg(3); const unsigned int - ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listout.width()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()), axis = (unsigned int)_mp_arg(4); cimg::mutex(6); - CImg &img = mp.listout[ind]; + CImg &img = mp.imglist[ind]; img.sort(is_increasing, axis==0 || axis=='x'?'x': axis==1 || axis=='y'?'y': @@ -21194,39 +25490,53 @@ namespace cimg_library_suffixed { static double mp_image_stats(_cimg_math_parser& mp) { double *ptrd = &_mp_arg(1) + 1; unsigned int ind = (unsigned int)mp.opcode[2]; - if (ind==~0U) CImg(ptrd,14,1,1,1,true) = mp.imgout.get_stats(); + if (ind==~0U) + CImg(ptrd,14,1,1,1,true) = mp.imgout.get_stats(); else { - ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()); - CImg(ptrd,14,1,1,1,true) = mp.listout[ind].get_stats(); + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg(ptrd,14,1,1,1,true) = mp.imglist[ind].get_stats(); } return cimg::type::nan(); } 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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[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 &img = ind==~0U?mp.imgout:mp.listout[ind]; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + } + const CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; return (double)img.width()*img.height()*img.depth()*img.spectrum(); } @@ -21234,6 +25544,45 @@ namespace cimg_library_suffixed { return _mp_arg(2) + 1; } + static double mp_inrange(_cimg_math_parser& mp) { + const unsigned int sizd = (unsigned int)mp.opcode[2]; + const bool + include_m = (bool)_mp_arg(9), + include_M = (bool)_mp_arg(10); + if (!sizd) { // Scalar result + const double val = _mp_arg(3); + const double m = _mp_arg(5), M = _mp_arg(7); + if (M>=m) return (double)((include_m?(val>=m):(val>m)) && (include_M?(val<=M):(val=M):(val>M)) && (include_m?(val<=m):(val=m) + ptrd[k] = (double)((include_m?(val>=m):(val>m)) && (include_M?(val<=M):(val=M):(val>M)) && (include_m?(val<=m):(val::nan(); + } + static double mp_int(_cimg_math_parser& mp) { return (double)(longT)_mp_arg(2); } @@ -21263,15 +25612,38 @@ namespace cimg_library_suffixed { static double mp_isbool(_cimg_math_parser& mp) { const double val = _mp_arg(2); - return (double)(val==0.0 || val==1.0); + return (double)(val==0. || val==1.); + } + + static double mp_isdir(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[3]; + const double *const ptrs = &_mp_arg(2) + (siz?1:0); + if (!siz) { char str[2] = {}; *str = *ptrs; return (double)cimg::is_directory(str); } + CImg ss(siz + 1); + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + return (double)cimg::is_directory(ss); } 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 ref(&_mp_arg(3) + 1,siz_ref,1,1,1,true); + for (unsigned int i = 5; i(&_mp_arg(i) + 1,siz,1,1,1,true)==ref) { res = true; break; } + } + } else { // Reference value is a scalar + const double ref = _mp_arg(3); + for (unsigned i = 5; i ss(siz + 1); + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + return (double)cimg::is_file(ss); } static double mp_isnan(_cimg_math_parser& mp) { return (double)cimg::type::is_nan(_mp_arg(2)); } + static double mp_isvarname(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[3]; + const double *ptrs = &_mp_arg(2) + (siz?1:0); + if (!siz) { + const char c = (char)*ptrs; + return (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='_'; + } + if (*ptrs>='0' && *ptrs<='9') return 0; + for (unsigned int k = 0; k=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.cubic_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + case 1 : // Linear interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), s2 = 2.f*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_atXYZ(mx=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.linear_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + default : // Nearest neighbor interpolation + switch (boundary_conditions) { case 3 : { // Mirror const int w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), @@ -21306,35 +25746,16 @@ namespace cimg_library_suffixed { mc=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.cubic_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + case 1 : // Linear interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), s2 = 2.f*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_atXYZ(mx=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.linear_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + default : // Nearest neighbor interpolation + switch (boundary_conditions) { case 3 : { // Mirror const int w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), @@ -21385,46 +25852,46 @@ namespace cimg_library_suffixed { mc vals(i_end - 4); - double *p = vals.data(); - for (unsigned int i = 4; i values; + if (i_end==5) values.assign(&_mp_arg(3),(unsigned int)mp.opcode[4],1,1,1,true); // Only a single argument + else { + unsigned int siz = 0; + for (unsigned int i = 4; i1) std::memcpy(ptr,&_mp_arg(i),len*sizeof(double)); + else *ptr = _mp_arg(i); + ptr+=len; + } + } + longT ind = (longT)values[0]; + ++values._data; --values._width; // Skip first value + if (ind<0) ind+=values.width() + 1; + ind = cimg::cut(ind,(longT)1,(longT)values.width()); + const double &kth = values.kth_smallest((ulongT)(ind - 1)); + --values._data; ++values._width; + return kth; + } + + static double mp_lerp(_cimg_math_parser& mp) { + const double t = _mp_arg(4); + return _mp_arg(2)*(1-t) + _mp_arg(3)*t; } static double mp_linear_add(_cimg_math_parser& mp) { @@ -21440,17 +25907,17 @@ namespace cimg_library_suffixed { } 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; + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + return (double)mp.imglist[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 &img = mp.listin[indi]; - const bool is_forward = (bool)_mp_arg(4); + indi = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + const CImg &img = mp.imglist[indi]; + const int _step = (int)_mp_arg(5), step = _step?_step:-1; const ulongT siz = (ulongT)img.size(); - longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):is_forward?0:siz - 1); + longT ind = (longT)(mp.opcode[4]!=_cimg_mp_slot_nan?_mp_arg(4):step>0?0:siz - 1); if (ind<0 || ind>=(longT)siz) return -1.; const T *const ptrb = img.data(), @@ -21459,25 +25926,25 @@ namespace cimg_library_suffixed { const double val = _mp_arg(3); // Forward search - if (is_forward) { - while (ptr0) { + while (ptr=ptre?-1.:(double)(ptr - ptrb); } // Backward search. - while (ptr>=ptrb && (double)*ptr!=val) --ptr; + while (ptr>=ptrb && (double)*ptr!=val) ptr+=step; return ptr &img = mp.listin[indi]; - const bool is_forward = (bool)_mp_arg(5); + indi = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + const CImg &img = mp.imglist[indi]; + const int _step = (int)_mp_arg(6), step = _step?_step:-1; 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); + longT ind = (longT)(mp.opcode[5]!=_cimg_mp_slot_nan?_mp_arg(5):step>0?0:siz1 - 1); if (ind<0 || ind>=(longT)siz1) return -1.; const T *const ptr1b = img.data(), @@ -21490,36 +25957,38 @@ namespace cimg_library_suffixed { *p2 = 0; // Forward search. - if (is_forward) { + if (step>0) { do { - while (ptr1=ptr1e) return -1.; p1 = ptr1 + 1; p2 = ptr2b + 1; while (p1=ptr1b && *ptr1!=*ptr2b) --ptr1; + while (ptr1>=ptr1b && *ptr1!=*ptr2b) ptr1+=step; + if (ptr1=ptr1b); - return p2=ptr1b); + return p2 &img = mp.listin[ind]; + const CImg &img = mp.imglist[ind]; const longT off = (longT)_mp_arg(3), whds = (longT)img.size(); @@ -21540,20 +26009,66 @@ namespace cimg_library_suffixed { } 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; + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + return (double)mp.imglist[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()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()), interpolation = (unsigned int)_mp_arg(7), boundary_conditions = (unsigned int)_mp_arg(8); - const CImg &img = mp.listin[ind]; + const CImg &img = mp.imglist[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 + switch (interpolation) { + case 2 : // Cubic interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), s2 = 2.f*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._cubic_atXYZ(mx=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.cubic_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + case 1 : // Linear interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), s2 = 2.f*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_atXYZ(mx=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.linear_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + default : // Nearest neighbor interpolation + switch (boundary_conditions) { case 3 : { // Mirror const int w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), @@ -21565,45 +26080,26 @@ namespace cimg_library_suffixed { mc &img = mp.listin[ind]; + const CImg &img = mp.imglist[ind]; const longT off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(3), whds = (longT)img.size(); @@ -21625,16 +26121,62 @@ namespace cimg_library_suffixed { static double mp_list_jxyzc(_cimg_math_parser& mp) { const unsigned int - ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.listin.width()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()), interpolation = (unsigned int)_mp_arg(7), boundary_conditions = (unsigned int)_mp_arg(8); - const CImg &img = mp.listin[ind]; + const CImg &img = mp.imglist[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 + switch (interpolation) { + case 2 : // Cubic interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), s2 = 2.f*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._cubic_atXYZ(mx=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.cubic_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + case 1 : // Linear interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), s2 = 2.f*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_atXYZ(mx=img._spectrum?img._spectrum - 1:c)); + default : // Dirichlet + if (c<0 || c>=img._spectrum) return (T)0; + return (double)img.linear_atXYZ((float)x,(float)y,(float)z,(int)c,(T)0); + } + default : // Nearest neighbor interpolation + switch (boundary_conditions) { case 3 : { // Mirror const int w2 = 2*img.width(), h2 = 2*img.height(), d2 = 2*img.depth(), s2 = 2*img.spectrum(), @@ -21646,51 +26188,57 @@ namespace cimg_library_suffixed { mc::vector(mp.listin[ind].median()).move_to(mp.list_median[ind]); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + if (!mp.list_median) mp.list_median.assign(mp.imglist._width); + if (!mp.list_median[ind]) CImg::vector(mp.imglist[ind].median()).move_to(mp.list_median[ind]); return *mp.list_median[ind]; } + static double mp_list_norm(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + if (!mp.list_norm) mp.list_norm.assign(mp.imglist._width); + if (!mp.list_norm[ind]) CImg::vector(mp.imglist[ind].magnitude(2)).move_to(mp.list_norm[ind]); + return *mp.list_norm[ind]; + } + + static double mp_list_id(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + bool get_stats = false; + cimg::mutex(13); + if (!mp.list_stats || mp.list_stats.size()!=mp.imglist._width) mp.list_stats.assign(mp.imglist._width); + if (!mp.list_stats[ind]) get_stats = true; + cimg::mutex(13,0); + + if (get_stats) { + CImg st = mp.imglist[ind].get_stats(); + cimg::mutex(13); + st.move_to(mp.list_stats[ind]); + cimg::mutex(13,0); + } + return std::sqrt(mp.list_stats(ind,3)); + } + 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; const longT off = (longT)_mp_arg(3), whds = (longT)img.size(); @@ -21700,8 +26248,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), z = (int)_mp_arg(5), c = (int)_mp_arg(6); @@ -21713,8 +26262,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[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]; @@ -21727,8 +26277,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[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]; @@ -21743,8 +26294,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; const longT off = (longT)_mp_arg(3), whd = (longT)img.width()*img.height()*img.depth(); @@ -21757,8 +26309,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; const longT off = (longT)_mp_arg(3), whd = (longT)img.width()*img.height()*img.depth(); @@ -21772,8 +26325,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), @@ -21788,8 +26342,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[ind]; const int x = (int)_mp_arg(3), y = (int)_mp_arg(4), @@ -21805,8 +26360,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[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]; @@ -21822,8 +26378,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[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]; @@ -21840,8 +26397,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[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)), @@ -21857,8 +26415,9 @@ namespace cimg_library_suffixed { } 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 &img = mp.listout[ind]; + if (!mp.imglist.width()) return cimg::type::nan(); + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + CImg &img = mp.imglist[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)), @@ -21875,46 +26434,56 @@ namespace cimg_library_suffixed { } 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; + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + return (double)mp.imglist[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()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()), k = (unsigned int)mp.opcode[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); + bool get_stats = false; + cimg::mutex(13); + if (!mp.list_stats || mp.list_stats.size()!=mp.imglist._width) mp.list_stats.assign(mp.imglist._width); + if (!mp.list_stats[ind]) get_stats = true; + cimg::mutex(13,0); + + if (get_stats) { + CImg st = mp.imglist[ind].get_stats(); + cimg::mutex(13); + st.move_to(mp.list_stats[ind]); + cimg::mutex(13,0); + } 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; + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + return (double)mp.imglist[ind]._width*mp.imglist[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; + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + return (double)mp.imglist[ind]._width*mp.imglist[ind]._height*mp.imglist[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; + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + return (double)mp.imglist[ind]._width*mp.imglist[ind]._height*mp.imglist[ind]._depth*mp.imglist[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; + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + return (double)mp.imglist[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()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()), boundary_conditions = (unsigned int)_mp_arg(4), vsiz = (unsigned int)mp.opcode[5]; - const CImg &img = mp.listin[ind]; + const CImg &img = mp.imglist[ind]; const longT off = (longT)_mp_arg(3), whd = (longT)img.width()*img.height()*img.depth(); @@ -21950,15 +26519,57 @@ namespace cimg_library_suffixed { 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()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()), interpolation = (unsigned int)_mp_arg(6), boundary_conditions = (unsigned int)_mp_arg(7), vsiz = (unsigned int)mp.opcode[8]; - const CImg &img = mp.listin[ind]; + const CImg &img = mp.imglist[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 + switch (interpolation) { + case 2 : // Cubic interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), + mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2), + cx = mx::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()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.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 &img = mp.listin[ind]; + const CImg &img = mp.imglist[ind]; const longT off = img.offset(ox,oy,oz) + (longT)_mp_arg(3), whd = (longT)img.width()*img.height()*img.depth(); @@ -22056,17 +26646,59 @@ namespace cimg_library_suffixed { 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()), + ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()), interpolation = (unsigned int)_mp_arg(6), boundary_conditions = (unsigned int)_mp_arg(7), vsiz = (unsigned int)mp.opcode[8]; - const CImg &img = mp.listin[ind]; + const CImg &img = mp.imglist[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 + switch (interpolation) { + case 2 : // Cubic interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), + mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2), + cx = mx::nan(); } @@ -22176,6 +26787,23 @@ namespace cimg_library_suffixed { return (double)(_mp_arg(2)<=_mp_arg(3)); } + static double mp_map(_cimg_math_parser& mp) { + double *ptrd = &_mp_arg(1) + 1; + const double + *ptrX = &_mp_arg(2) + 1, + *ptrP = &_mp_arg(3) + 1; + const unsigned int + sizX = (unsigned int)mp.opcode[4], + sizP = (unsigned int)mp.opcode[5], + nb_channelsX = (unsigned int)mp.opcode[6], + nb_channelsP = (unsigned int)mp.opcode[7], + boundary_conditions = (unsigned int)_mp_arg(8); + CImg(ptrd,sizX/nb_channelsX,1,1,nb_channelsX*nb_channelsP,true) = + CImg(ptrX,sizX/nb_channelsX,1,1,nb_channelsX,true). + get_map(CImg(ptrP,sizP/nb_channelsP,1,1,nb_channelsP,true),boundary_conditions); + return cimg::type::nan(); + } + static double mp_matrix_eig(_cimg_math_parser& mp) { double *ptrd = &_mp_arg(1) + 1; const double *ptr1 = &_mp_arg(2) + 1; @@ -22187,11 +26815,15 @@ namespace cimg_library_suffixed { return cimg::type::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(ptrd,k,k,1,1,true) = CImg(ptr1,k,k,1,1,true).get_invert(); + static double mp_matrix_invert(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptr1 = &_mp_arg(2) + 1; + const unsigned int + w = (unsigned int)mp.opcode[3], + h = (unsigned int)mp.opcode[4]; + const bool use_LU = (bool)_mp_arg(5); + const float lambda = (float)_mp_arg(6); + CImg(ptrd,h,w,1,1,true) = CImg(ptr1,w,h,1,1,true).get_invert(use_LU,lambda); return cimg::type::nan(); } @@ -22208,16 +26840,6 @@ namespace cimg_library_suffixed { return cimg::type::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(ptrd,l,k,1,1,true) = CImg(ptr1,k,l,1,1,true).get_pseudoinvert(); - return cimg::type::nan(); - } - static double mp_matrix_svd(_cimg_math_parser& mp) { double *ptrd = &_mp_arg(1) + 1; const double *ptr1 = &_mp_arg(2) + 1; @@ -22234,9 +26856,36 @@ namespace cimg_library_suffixed { 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::inf(); + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; kvalmax) valmax = val; } + } else { val = _mp_arg(i); if (val>valmax) valmax = val; } + } + return valmax; + } + + static double mp_maxabs(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + double val, abs_val, valmaxabs = 0, abs_valmaxabs = 0; + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; kabs_valmaxabs) { valmaxabs = val; abs_valmaxabs = abs_val; } + } + } else { + val = _mp_arg(i); + abs_val = cimg::abs(val); + if (abs_val>abs_valmaxabs) { valmaxabs = val; abs_valmaxabs = abs_val; } + } + } + return valmaxabs; } static double* _mp_memcopy_double(_cimg_math_parser& mp, const unsigned int ind, const ulongT *const p_ref, @@ -22254,9 +26903,11 @@ namespace cimg_library_suffixed { } static float* _mp_memcopy_float(_cimg_math_parser& mp, const ulongT *const p_ref, - const longT siz, const long inc) { + const longT siz, const long inc, const bool is_out) { const unsigned ind = (unsigned int)p_ref[1]; - const CImg &img = ind==~0U?mp.imgin:mp.listin[cimg::mod((int)mp.mem[ind],mp.listin.width())]; + const CImg &img = is_out? + (ind==~0U?mp.imgout:mp.imglist[cimg::mod((int)mp.mem[ind],mp.imglist.width())]): + (ind==~0U?mp.imgin:mp.imglist[cimg::mod((int)mp.mem[ind],mp.imglist.width())]); const bool is_relative = (bool)p_ref[2]; int ox, oy, oz, oc; longT off = 0; @@ -22291,7 +26942,7 @@ namespace cimg_library_suffixed { const float _opacity = (float)_mp_arg(7), opacity = (float)cimg::abs(_opacity), - omopacity = 1 - std::max(_opacity,0.0f); + omopacity = 1 - std::max(_opacity,0.f); if (siz>0) { const bool is_doubled = mp.opcode[8]<=1, @@ -22316,17 +26967,17 @@ namespace cimg_library_suffixed { } } 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); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s,false); 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); + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d,true); 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); + float *ptrd = _mp_memcopy_float(mp,&mp.opcode[8],siz,inc_d,true); + const float *ptrs = _mp_memcopy_float(mp,&mp.opcode[15],siz,inc_s,false); if (inc_d==1 && inc_s==1 && _opacity>=1) { if (ptrs + siz - 1ptrd + siz - 1) std::memcpy(ptrd,ptrs,siz*sizeof(float)); else std::memmove(ptrd,ptrs,siz*sizeof(float)); @@ -22349,38 +27000,96 @@ namespace cimg_library_suffixed { 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::inf(); + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k::inf(), abs_valminabs = cimg::type::inf(); + for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k values; + if (i_end==5) { // Only a single argument + if ((unsigned)mp.opcode[4]==1) return _mp_arg(3); // Real value + else values.assign(&_mp_arg(3),(unsigned int)mp.opcode[4],1,1,1,true); // Vector value + } else { + unsigned int siz = 0; + for (unsigned int i = 4; i1) std::memcpy(ptr,&_mp_arg(i),len*sizeof(double)); + else *ptr = _mp_arg(i); + ptr+=len; + } } - CImg vals(i_end - 3); - double *p = vals.data(); - for (unsigned int i = 3; i(ptrd,wS,wD,1,1,true) = CImg(ptrS,wS,hS,1,1,false). + project_matrix(CImg(ptrD,wD,hS,1,1,true),method,max_iter,max_residual); + return cimg::type::nan(); + } + + static double mp_mse(_cimg_math_parser& mp) { + const unsigned int + _siz = (unsigned int)mp.opcode[4], + siz = std::max(_siz,1U), + off = _siz?1:0; + return CImg(&_mp_arg(2) + off,1,siz,1,1,true). + MSE(CImg(&_mp_arg(3) + off,1,siz,1,1,true)); + } + static double mp_mul(_cimg_math_parser& mp) { return _mp_arg(2)*_mp_arg(3); } @@ -22393,65 +27102,25 @@ namespace cimg_library_suffixed { 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); + static double mp_o2c(_cimg_math_parser& mp) { + mp_check_list(mp,"o2c"); + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + const CImg &img = ind==~0U?mp.imgin:mp.imglist[ind]; + longT offset = (longT)_mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + if (!img) + ptrd[0] = ptrd[1] = ptrd[2] = ptrd[3] = cimg::type::nan(); + else { + *(ptrd++) = (double)(offset%img.width()); + offset/=img.width(); + *(ptrd++) = (double)(offset%img.height()); + offset/=img.height(); + *(ptrd++) = (double)(offset%img.depth()); + offset/=img.depth(); + *ptrd = (double)(offset%img.spectrum()); } - double res = 0; - for (unsigned int i = 3; ires) 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; i0?res:0.0; + return cimg::type::nan(); } static double mp_permutations(_cimg_math_parser& mp) { @@ -22461,25 +27130,31 @@ namespace cimg_library_suffixed { static double mp_polygon(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; unsigned int ind = (unsigned int)mp.opcode[3]; - if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.listin.width()); - CImg &img = ind==~0U?mp.imgout:mp.listout[ind]; - bool is_invalid_arguments = i_end<=4; + if (ind!=~0U) { + if (!mp.imglist.width()) return cimg::type::nan(); + ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width()); + } + CImg &img = ind==~0U?mp.imgout:mp.imglist[ind]; + bool is_invalid_arguments = i_end<=4, is_outlined = false; if (!is_invalid_arguments) { - const int nbv = (int)_mp_arg(4); - if (nbv<=0) is_invalid_arguments = true; + int nbv = (int)_mp_arg(4); + if (!nbv) is_invalid_arguments = true; else { + if (nbv<0) { nbv = -nbv; is_outlined = true; } CImg points(nbv,2,1,1,0); CImg color(img._spectrum,1,1,1,0); float opacity = 1; - unsigned int i = 5; + unsigned int i = 5, pattern=~0U; cimg_foroff(points,k) if (i expr(mp.opcode[2] - 4); + CImg _expr(mp.opcode[2] - 4); const ulongT *ptrs = mp.opcode._data + 4; - cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); - cimg::strellipsize(expr); + cimg_for(_expr,ptrd,char) *ptrd = (char)*(ptrs++); + cimg::strellipsize(_expr); cimg::mutex(6); if (print_char) - std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g = '%c'",expr._data,val,(int)val); + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %.17g = '%c'", + _expr._data,val,(int)val); else - std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %g",expr._data,val); + std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = %.17g", + _expr._data,val); std::fflush(cimg::output()); cimg::mutex(6,0); } @@ -22540,13 +27217,62 @@ namespace cimg_library_suffixed { 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; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k + *const p_body = ++mp.p_code, + *const p_end = p_body + mp.opcode[4]; + + if (nb_it>=1) { + const unsigned int _break_type = mp.break_type; + mp.break_type = 0; + + double it = 0; + if (ptrc) { // Version with loop variable (3 arguments) + while (it<=nb_itm1) { + *ptrc = it; + for (mp.p_code = p_body; 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; + ++it; + } + *ptrc = it; + } else // Version without loop variable (2 arguments) + while (it<=nb_itm1) { + for (mp.p_code = p_body; 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; + ++it; + } + mp.break_type = _break_type; + } + + mp.p_code = p_end - 1; + return *ptrs; } static double mp_rol(_cimg_math_parser& mp) { @@ -22560,7 +27286,7 @@ namespace cimg_library_suffixed { static double mp_rot2d(_cimg_math_parser& mp) { double *ptrd = &_mp_arg(1) + 1; const float - theta = (float)_mp_arg(2)*cimg::PI/180, + theta = (float)_mp_arg(2), ca = std::cos(theta), sa = std::sin(theta); *(ptrd++) = ca; @@ -22572,8 +27298,12 @@ namespace cimg_library_suffixed { 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(ptrd,3,3,1,1,true) = CImg::rotation_matrix(x,y,z,theta); + const float + x = (float)_mp_arg(2), + y = (float)_mp_arg(3), + z = (float)_mp_arg(4), + theta = (float)_mp_arg(5); + CImg(ptrd,3,3,1,1,true) = CImg::rotation_matrix(x,y,z,theta*180/cimg::PI); return cimg::type::nan(); } @@ -22581,6 +27311,36 @@ namespace cimg_library_suffixed { return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4)); } +#ifdef cimg_mp_func_run + static double mp_run(_cimg_math_parser& mp) { + const unsigned int nb_args = (unsigned int)(mp.opcode[2] - 3)/2; + CImgList _str; + CImg it; + for (unsigned int n = 0; n string + const double *ptr = &_mp_arg(3 + 2*n) + 1; + unsigned int l = 0; + while (l(ptr,l,1,1,1,true).move_to(_str); + } else { // Scalar argument -> number + it.assign(24); + cimg_snprintf(it,it._width,"%.17g",_mp_arg(3 + 2*n)); + CImg::string(it,false,true).move_to(_str); + } + } + CImg(1,1,1,1,0).move_to(_str); + CImg str = _str>'x'; +#if cimg_use_openmp==0 + const unsigned int n_thread = 0; +#else + const unsigned int n_thread = omp_get_thread_num(); +#endif + cimg_mp_func_run(str._data,n_thread && mp.is_noncritical_run); + return cimg::type::nan(); + } +#endif + static double mp_self_add(_cimg_math_parser& mp) { return _mp_arg(1)+=_mp_arg(2); } @@ -22619,7 +27379,7 @@ namespace cimg_library_suffixed { siz = (unsigned int)mp.opcode[2]; mp_func op = (mp_func)mp.opcode[3]; CImg l_opcode(1,3); - l_opcode[2] = mp.opcode[4]; // Scalar argument. + 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); } @@ -22663,6 +27423,22 @@ namespace cimg_library_suffixed { return _mp_arg(1)-=_mp_arg(2); } +#ifdef cimg_mp_func_set + static double mp_set(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(1); + double *ptrd = &_mp_arg(3) + 1; + const unsigned int + sizs = (unsigned int)mp.opcode[2], + sizd = (unsigned int)mp.opcode[4]; + CImg sd(sizd + 1); + cimg_for_inX(sd,0,sd.width() - 1,i) sd[i] = (char)ptrd[i]; + sd.back() = 0; + if (sizs) cimg_mp_func_set(ptrs + 1,sizs,sd._data); + else cimg_mp_func_set(ptrs,0,sd._data); + return *ptrs; + } +#endif + static double mp_set_ioff(_cimg_math_parser& mp) { CImg &img = mp.imgout; const longT @@ -22873,19 +27649,29 @@ namespace cimg_library_suffixed { k = (unsigned int)mp.opcode[4], l = (unsigned int)mp.opcode[5], m = (unsigned int)mp.opcode[6]; - CImg(ptrd,m,k,1,1,true) = CImg(ptr2,m,l,1,1,true).get_solve(CImg(ptr1,k,l,1,1,true)); + const bool use_LU = (bool)_mp_arg(7); + CImg(ptrd,m,k,1,1,true) = CImg(ptr2,m,l,1,1,false). + solve(CImg(ptr1,k,l,1,1,true),use_LU); return cimg::type::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 bool is_increasing = (bool)_mp_arg(4); 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(ptrd,chunk_siz,siz/chunk_siz,1,1,true) = CImg(ptrs,chunk_siz,siz/chunk_siz,1,1,true). - get_sort(is_increasing,chunk_siz>1?'y':0); + nb_elts = mp.opcode[5]==~0U?siz:(unsigned int)_mp_arg(5), + siz_elt = (unsigned int)_mp_arg(6); + const ulongT sn = siz_elt*nb_elts; + if (sn>siz || siz_elt<1) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'sort()': " + "Arguments 'nb_elts=%g' and 'siz_elt=%g' are invalid " + "for sorting a vector of size %u.", + mp.imgin.pixel_type(),_mp_arg(5),_mp_arg(6),siz); + CImg(ptrd,siz_elt,nb_elts,1,1,true) = CImg(ptrs,siz_elt,nb_elts,1,1,true). + get_sort(is_increasing,siz_elt>1?'y':0); + if (sn(ptrd + sn,siz - sn,1,1,1,true) = CImg(ptrs + sn,siz - sn,1,1,1,true); return cimg::type::nan(); } @@ -22898,24 +27684,25 @@ namespace cimg_library_suffixed { } static double mp_srand(_cimg_math_parser& mp) { - return cimg::srand((unsigned int)_mp_arg(2)); + mp.rng = (cimg_uint64)_mp_arg(2); + return cimg::type::nan(); } static double mp_srand0(_cimg_math_parser& mp) { - cimg::unused(mp); - return cimg::srand(); + cimg::srand(&mp.rng); + +#if cimg_use_openmp!=0 + mp.rng+=omp_get_thread_num(); +#endif + return cimg::type::nan(); } static double mp_std(_cimg_math_parser& mp) { - const unsigned int i_end = (unsigned int)mp.opcode[2]; - CImg vals(i_end - 3); - double *p = vals.data(); - for (unsigned int i = 3; i::nan(); } - static double mp_stov(_cimg_math_parser& mp) { +#ifdef cimg_mp_func_store + static double mp_store(_cimg_math_parser& mp) { + const double + *ptr1 = &_mp_arg(2), + *ptr2 = &_mp_arg(4) + 1; + const unsigned int + siz1 = (unsigned int)mp.opcode[3], + siz2 = (unsigned int)mp.opcode[5]; + const int + w = (int)_mp_arg(6), + h = (int)_mp_arg(7), + d = (int)_mp_arg(8), + s = (int)_mp_arg(9); + + const bool is_compressed = (bool)_mp_arg(10); + if (w<0 || h<0 || d<0 || s<0) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'store()': " + "Specified image dimensions (%d,%d,%d,%d) are invalid.", + pixel_type(),w,h,d,s); + CImg ss(siz2 + 1); + cimg_for_inX(ss,0,ss.width() - 1,i) ss[i] = (char)ptr2[i]; + ss.back() = 0; + if (siz1) cimg_mp_func_store(ptr1 + 1,siz1, + (unsigned int)w,(unsigned int)h,(unsigned int)d,(unsigned int)s, + is_compressed,ss._data); + else cimg_mp_func_store(ptr1,1,(unsigned int)w,(unsigned int)h,(unsigned int)d,(unsigned int)s, + is_compressed,ss._data); + return cimg::type::nan(); + } +#endif + + static double mp_s2v(_cimg_math_parser& mp) { const double *ptrs = &_mp_arg(2); const ulongT siz = (ulongT)mp.opcode[3]; longT ind = (longT)_mp_arg(4); @@ -22933,35 +27751,89 @@ namespace cimg_library_suffixed { if (!siz) return *ptrs>='0' && *ptrs<='9'?*ptrs - '0':val; CImg ss(siz + 1 - ind); - char sep; - ptrs+=1 + ind; cimg_forX(ss,i) ss[i] = (char)*(ptrs++); ss.back() = 0; + ptrs+=1 + ind; + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; - int err = std::sscanf(ss,"%lf%c",&val,&sep); + const char *s = ss._data; + while (*s && *s<=32) ++s; + const bool is_negative = *s=='-'; + if (is_negative || *s=='+') ++s; + int err = 0; + char sep; + + if (*s=='0' && (s[1]=='x' || s[1]=='X') && s[2]>32) { // Hexadecimal number + unsigned int ival; + err = cimg_sscanf(s + 2,"%x%c",&ival,&sep); + if (err>0) val = (double)ival; + } else if (*s>32) { // Decimal number + err = cimg_sscanf(s,"%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::inf(); err = 1; } - else if (!cimg::strcasecmp(s,"nan")) { val = cimg::type::nan(); err = 1; } - if (err==1 && !is_positive) val = -val; - } + // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able + // to read those particular values. + if (!err && (*s=='i' || *s=='I' || *s=='n' || *s=='N')) { + if (!cimg::strncasecmp(s,"inf",3)) { val = cimg::type::inf(); err = 1 + (s[3]!=0); } + else if (!cimg::strncasecmp(s,"nan",3)) { val = cimg::type::nan(); err = 1 + (s[3]!=0); } + } #endif - if (is_strict && err!=1) return cimg::type::nan(); + } + if (err<=0 || (is_strict && err!=1)) return cimg::type::nan(); + if (is_negative) val = -val; return val; } + static double mp_string(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const unsigned int nb_args = (unsigned int)(mp.opcode[3] - 3)/2; + CImgList _str; + CImg it; + for (unsigned int n = 0; n string + const double *ptr = &_mp_arg(4 + 2*n) + 1; + unsigned int l = 0; + while (l(ptr,l,1,1,1,true).move_to(_str); + } else { // Scalar argument -> number + it.assign(24); + cimg_snprintf(it,it._width,"%.17g",_mp_arg(4 + 2*n)); + CImg::string(it,false,true).move_to(_str); + } + } + const CImg str = _str>'x'; + const unsigned int sizd = std::min(str._width,(unsigned int)mp.opcode[2]); + std::memset(ptrd,0,mp.opcode[2]*sizeof(double)); + for (unsigned int k = 0; k::nan(); + } + 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; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k(ptrs,k,k,1,1,true).trace(); } - static double mp_transp(_cimg_math_parser& mp) { + static double mp_transpose(_cimg_math_parser& mp) { double *ptrd = &_mp_arg(1) + 1; const double *ptrs = &_mp_arg(2) + 1; const unsigned int @@ -22989,7 +27861,25 @@ namespace cimg_library_suffixed { } static double mp_u(_cimg_math_parser& mp) { - return cimg::rand(_mp_arg(2),_mp_arg(3)); + return cimg::rand(_mp_arg(2),_mp_arg(3),&mp.rng); + } + + static double mp_u_ext(_cimg_math_parser& mp) { // Extended version with extremum control + const double eps = 1e-5; + const bool + include_min = (bool)_mp_arg(4), + include_max = (bool)_mp_arg(5); + double + m = _mp_arg(2), + M = _mp_arg(3); + if (m>M) cimg::swap(m,M); + if (!include_min) m = m>0?m*(1 + eps):m<0?m*(1 - eps):eps; + if (!include_max) M = M>0?M*(1 - eps):M<0?M*(1 + eps):-eps; + return cimg::rand(m,M,&mp.rng); + } + + static double mp_ui2f(_cimg_math_parser& mp) { + return (double)cimg::uint2float((unsigned int)_mp_arg(2)); } static double mp_uppercase(_cimg_math_parser& mp) { @@ -22998,10 +27888,17 @@ namespace cimg_library_suffixed { static double mp_var(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - CImg vals(i_end - 3); - double *p = vals.data(); - for (unsigned int i = 3; i1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; klength) + sublength = (longT)mp.opcode[5], + step = (longT)_mp_arg(6); + if (start<0 || start + step*(sublength-1)>=length) throw CImgArgumentException("[" cimg_appname "_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)); + "(length: %ld, start: %ld, sub-length: %ld, step: %ld).", + mp.imgin.pixel_type(),length,start,sublength,step); + ptrs+=start; + if (step==1) std::memcpy(ptrd,ptrs,sublength*sizeof(double)); + else for (longT k = 0; k::nan(); + } + + static double mp_vector_crop_ext(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + const unsigned int + w = (unsigned int)mp.opcode[3], + h = (unsigned int)mp.opcode[4], + d = (unsigned int)mp.opcode[5], + s = (unsigned int)mp.opcode[6], + dx = (unsigned int)mp.opcode[11], + dy = (unsigned int)mp.opcode[12], + dz = (unsigned int)mp.opcode[13], + dc = (unsigned int)mp.opcode[14], + boundary_conditions = (int)_mp_arg(15); + const int x = (int)_mp_arg(7), y = (int)_mp_arg(8), z = (int)_mp_arg(9), c = (int)_mp_arg(10); + CImg(ptrd,dx,dy,dz,dc,true) = CImg(ptrs,w,h,d,s,true). + get_crop(x,y,z,c,x + dx - 1,y + dy - 1,z + dz - 1,c + dc - 1,boundary_conditions); + return cimg::type::nan(); + } + + static double mp_vector_draw(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(7) + 1; + const unsigned int + sizD = (unsigned int)mp.opcode[2], + sizS = (unsigned int)mp.opcode[8]; + const int + w = (int)_mp_arg(3), h = (int)_mp_arg(4), d = (int)_mp_arg(5), s = (int)_mp_arg(6), + x = (int)_mp_arg(9), y = (int)_mp_arg(10), z = (int)_mp_arg(11), c = (int)_mp_arg(12); + int dx = (int)mp.opcode[13], dy = (int)mp.opcode[14], dz = (int)mp.opcode[15], dc = (int)mp.opcode[16]; + dx = (unsigned int)dx==~0U?w:(int)_mp_arg(13); + dy = (unsigned int)dy==~0U?h:(int)_mp_arg(14); + dz = (unsigned int)dz==~0U?d:(int)_mp_arg(15); + dc = (unsigned int)dc==~0U?s:(int)_mp_arg(16); + + if (w<=0 || h<=0 || d<=0 || s<=0) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'draw()': " + "Invalid specified target vector geometry (%d,%d,%d,%d).", + mp.imgin.pixel_type(),w,h,d,s); + if (sizD<(ulongT)w*h*d*s) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'draw()': " + "Target vector (%lu values) and its specified target geometry (%d,%d,%d,%d) " + "(%lu values) do not match.", + mp.imgin.pixel_type(),sizD,w,h,d,s,(ulongT)w*h*d*s); + if (dx<=0 || dy<=0 || dz<=0 || dc<=0) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'draw()': " + "Invalid specified sprite geometry (%d,%d,%d,%d).", + mp.imgin.pixel_type(),dx,dy,dz,dc); + if (sizS<(ulongT)dx*dy*dz*dc) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'draw()': " + "Sprite vector (%lu values) and its specified sprite geometry (%d,%d,%d,%d) " + "(%lu values) do not match.", + mp.imgin.pixel_type(),sizS,dx,dy,dz,dc,(ulongT)dx*dy*dz*dc); + + CImg D(ptrd,w,h,d,s,true); + const CImg S(ptrs,dx,dy,dz,dc,true); + const float opacity = (float)_mp_arg(17); + + if (mp.opcode[18]!=~0U) { // Opacity mask specified + const ulongT sizM = mp.opcode[19]; + if (sizM<(ulongT)dx*dy*dz) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'draw()': " + "Mask vector (%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 M(&_mp_arg(18) + 1,dx,dy,dz,(unsigned int)(sizM/(dx*dy*dz)),true); + D.draw_image(x,y,z,c,S,M,opacity,(float)_mp_arg(20)); + } else D.draw_image(x,y,z,c,S,opacity); + return cimg::type::nan(); } @@ -23100,6 +28071,17 @@ namespace cimg_library_suffixed { return cimg::lowercase(_mp_arg(2))==cimg::lowercase(_mp_arg(4)); } + static double mp_vector_lerp(_cimg_math_parser& mp) { + unsigned int siz = (unsigned int)mp.opcode[2]; + double *ptrd = &_mp_arg(1) + 1; + const double + *ptrs1 = &_mp_arg(3) + 1, + *ptrs2 = &_mp_arg(4) + 1, + t = _mp_arg(5); + for (unsigned int k = 0; k::nan(); + } + static double mp_vector_off(_cimg_math_parser& mp) { const unsigned int ptr = (unsigned int)mp.opcode[2] + 1, @@ -23108,77 +28090,51 @@ namespace cimg_library_suffixed { return off>=0 && off<(int)siz?mp.mem[ptr + off]:cimg::type::nan(); } - static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector) + 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; + nb_args = (unsigned int)mp.opcode[2], + siz_vector = (unsigned int)mp.opcode[3], + ptrs = (unsigned int)mp.opcode[6] + 1; double *ptrd = &_mp_arg(1) + 1; - mp_func op = (mp_func)mp.opcode[3]; - CImg l_opcode(4); - l_opcode[2] = mp.opcode[4]; // Scalar argument1 + mp_func op = (mp_func)mp.opcode[4]; + CImg l_opcode(mp.opcode._data + 3,nb_args + 2); + l_opcode[0] = mp.opcode[1]; l_opcode.swap(mp.opcode); ulongT &argument2 = mp.opcode[3]; - while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); } + while (siz_vector-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); } l_opcode.swap(mp.opcode); return cimg::type::nan(); } - static double mp_vector_map_v(_cimg_math_parser& mp) { // Operator(vector) + 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; + nb_args = (unsigned int)mp.opcode[2], + siz_vector = (unsigned int)mp.opcode[3], + ptrs = (unsigned int)mp.opcode[5] + 1; double *ptrd = &_mp_arg(1) + 1; - mp_func op = (mp_func)mp.opcode[3]; - CImg l_opcode(1,3); + mp_func op = (mp_func)mp.opcode[4]; + CImg l_opcode(mp.opcode._data + 3,nb_args + 2); + l_opcode[0] = l_opcode[1]; l_opcode.swap(mp.opcode); ulongT &argument = mp.opcode[2]; - while (siz-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); } + while (siz_vector-->0) { argument = ptrs++; *(ptrd++) = (*op)(mp); } l_opcode.swap(mp.opcode); return cimg::type::nan(); } - static double mp_vector_map_vs(_cimg_math_parser& mp) { // Operator(vector,scalar) + static double mp_vector_map_vv(_cimg_math_parser& mp) { // Operator(vector,vector,[...]) unsigned int - siz = (unsigned int)mp.opcode[2], - ptrs = (unsigned int)mp.opcode[4] + 1; + nb_args = (unsigned int)mp.opcode[2], + siz_vector = (unsigned int)mp.opcode[3], + ptrs1 = (unsigned int)mp.opcode[5] + 1, + ptrs2 = (unsigned int)mp.opcode[6] + 1; double *ptrd = &_mp_arg(1) + 1; - mp_func op = (mp_func)mp.opcode[3]; - CImg 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::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 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::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 l_opcode(1,4); + mp_func op = (mp_func)mp.opcode[4]; + CImg l_opcode(mp.opcode._data + 3,nb_args + 2); + l_opcode[0] = l_opcode[1]; l_opcode.swap(mp.opcode); ulongT &argument1 = mp.opcode[2], &argument2 = mp.opcode[3]; - while (siz-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); } + while (siz_vector-->0) { argument1 = ptrs1++; argument2 = ptrs2++; *(ptrd++) = (*op)(mp); } l_opcode.swap(mp.opcode); return cimg::type::nan(); } @@ -23187,27 +28143,96 @@ namespace cimg_library_suffixed { return !mp_vector_eq(mp); } + static double _mp_vector_norm0(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)(_mp_arg(i)?1:0); + return res; + } + + static double _mp_vector_norm1(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)cimg::abs(_mp_arg(i)); + return res; + } + + static double _mp_vector_norm2(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)cimg::sqr(_mp_arg(i)); + return (double)std::sqrt(res); + } + + static double _mp_vector_norminf(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) { + const double val = (double)cimg::abs(_mp_arg(i)); + if (val>res) res = val; + } + return res; + } + + static double _mp_vector_normp(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + const double p = _mp_arg(3); + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)std::pow(cimg::abs(_mp_arg(i)),p); + res = (double)std::pow(res,1.0/p); + return res; + } + + static double mp_vector_normp(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[3]; + const double p = _mp_arg(4); + if (siz>0) { // Vector-valued argument + const double *ptrs = &_mp_arg(2) + 1; + double res = 0; + if (p==2) { // L2 + for (unsigned int i = 0; i::is_inf(p)) { // L-inf + for (unsigned int i = 0; ires) res = val; + } + } else { // L-p + for (unsigned int i = 0; i0?res:0; + } + // Scalar-valued argument. + const double val = _mp_arg(2); + return p?cimg::abs(val):(val!=0); + } + static double mp_vector_print(_cimg_math_parser& mp) { const bool print_string = (bool)mp.opcode[4]; cimg_pragma_openmp(critical(mp_vector_print)) { - CImg expr(mp.opcode[2] - 5); + CImg _expr(mp.opcode[2] - 5); const ulongT *ptrs = mp.opcode._data + 5; - cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); - cimg::strellipsize(expr); + 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_appname "_math_parser] %s = [ ",expr._data); + std::fprintf(cimg::output(),"\n[" cimg_appname "_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?",":""); + } else std::fprintf(cimg::output(),"%.17g%s",mp.mem[ptr++],siz?",":""); ++count; } if (print_string) { @@ -23242,6 +28267,38 @@ namespace cimg_library_suffixed { return cimg::type::nan(); } + static double mp_vector_resize_ext(_cimg_math_parser& mp) { + double *const ptrd = &_mp_arg(1) + 1; + const unsigned int + siz = (unsigned int)mp.opcode[2], + ow = (unsigned int)mp.opcode[4], + oh = (unsigned int)mp.opcode[5], + od = (unsigned int)mp.opcode[6], + os = (unsigned int)mp.opcode[7], + nw = (unsigned int)mp.opcode[8], + nh = (unsigned int)mp.opcode[9], + nd = (unsigned int)mp.opcode[10], + ns = (unsigned int)mp.opcode[11]; + const int + interpolation = (int)_mp_arg(12), + boundary_conditions = (int)_mp_arg(13); + const float + ax = (float)_mp_arg(14), + ay = (float)_mp_arg(15), + az = (float)_mp_arg(16), + ac = (float)_mp_arg(17); + if (siz) { // Resize vector + const double *const ptrs = &_mp_arg(3) + 1; + CImg(ptrd,nw,nh,nd,ns,true) = CImg(ptrs,ow,oh,od,os,true). + get_resize(nw,nh,nd,ns,interpolation,boundary_conditions,ax,ay,az,ac); + } else { // Resize scalar + const double value = _mp_arg(3); + CImg(ptrd,nw,nh,nd,ns,true) = CImg(1,1,1,1,value). + resize(nw,nh,nd,ns,interpolation,boundary_conditions,ax,ay,az,ac); + } + return cimg::type::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; @@ -23255,36 +28312,142 @@ namespace cimg_library_suffixed { 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); + if (off>=0 && off<(int)siz) mp.mem[ptr + off] = _mp_arg(1); + return _mp_arg(1); } - static double mp_vtos(_cimg_math_parser& mp) { + static double mp_vector_unitnorm(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[3]; + const double p = _mp_arg(4); + if (siz>0) { // Vector-valued argument + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + if (ptrd!=ptrs) std::memcpy(ptrd,ptrs,siz*sizeof(double)); + CImg vec(ptrd,siz,1,1,1,true); + const double mag = vec.magnitude(p); + if (mag>0) vec/=mag; + return cimg::type::nan(); + } + // Scalar-valued argument. + const double val = _mp_arg(2); + return val?(_mp_arg(2)?1:val):0; + } + +#define _cimg_mp_vfunc(func) \ + const longT sizd = (longT)mp.opcode[2];\ + const unsigned int nbargs = (unsigned int)(mp.opcode[3] - 4)/2; \ + double *const ptrd = &_mp_arg(1) + (sizd?1:0); \ + cimg_pragma_openmp(parallel cimg_openmp_if_size(sizd,256)) \ + { CImg vec(nbargs); double res; \ + cimg_pragma_openmp(for) for (longT k = sizd?sizd - 1:0; k>=0; --k) { \ + cimg_forX(vec,n) vec[n] = *(&_mp_arg(4 + 2*n) + (k+1)*(mp.opcode[4 + 2*n + 1]?1:0)); \ + func; ptrd[k] = res; \ + }} \ + return sizd?cimg::type::nan():*ptrd; + + static double _mp_vargkth(CImg& vec) { + const double val = (+vec).get_shared_points(1,vec.width() - 1). + kth_smallest((ulongT)cimg::cut((longT)*vec - 1,(longT)0,(longT)vec.width() - 2)); + cimg_for_inX(vec,1,vec.width()-1,ind) if (vec[ind]==val) return ind - 1.; + return 1.; + } + + static double mp_vargkth(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = _mp_vargkth(vec)); + } + + static double mp_vargmax(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = (double)(&vec.max() - vec.data())); + } + + static double mp_vargmaxabs(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = (double)(&vec.maxabs() - vec.data())); + } + + static double mp_vargmin(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = (double)(&vec.min() - vec.data())); + } + + static double mp_vargminabs(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = (double)(&vec.minabs() - vec.data())); + } + + static double mp_vavg(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.mean()); + } + + static double mp_vkth(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.get_shared_points(1,vec.width() - 1). + kth_smallest((ulongT)cimg::cut((longT)*vec - 1,(longT)0,(longT)vec.width() - 2))); + } + + static double mp_vmax(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.max()); + } + + static double mp_vmaxabs(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.maxabs()); + } + + static double mp_vmedian(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.median()); + } + + static double mp_vmin(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.min()); + } + + static double mp_vminabs(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.minabs()); + } + + static double mp_vprod(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.product()); + } + + static double mp_vstd(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = std::sqrt(vec.get_stats()[3])); + } + + static double mp_vsum(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.sum()); + } + + static double mp_vvar(_cimg_math_parser& mp) { + _cimg_mp_vfunc(res = vec.get_stats()[3]); + } + + static double mp_v2s(_cimg_math_parser& mp) { double *ptrd = &_mp_arg(1) + 1; const unsigned int sizd = (unsigned int)mp.opcode[2], sizs = (unsigned int)mp.opcode[4]; + std::memset(ptrd,0,sizd*sizeof(double)); const int nb_digits = (int)_mp_arg(5); CImg format(8); switch (nb_digits) { case -1 : std::strcpy(format,"%g"); break; case 0 : std::strcpy(format,"%.17g"); break; - default : cimg_snprintf(format,format._width,"%%.%dg",nb_digits); + default : + if (nb_digits>=-1) cimg_snprintf(format,format._width,"%%.%dg",nb_digits); + else cimg_snprintf(format,format._width,"%%.%dld",-nb_digits); } CImg str; if (sizs) { // Vector expression const double *ptrs = &_mp_arg(3) + 1; - CImg(ptrs,sizs,1,1,1,true).value_string(',',sizd + 1,format).move_to(str); + if (nb_digits>=-1) CImg(ptrs,sizs,1,1,1,true).value_string(',',sizd + 1,format).move_to(str); + else CImg(ptrs,sizs,1,1,1).value_string(',',sizd + 1,format).move_to(str); } else { // Scalar expression str.assign(sizd + 1); - cimg_snprintf(str,sizd + 1,format,_mp_arg(3)); + if (nb_digits>=-1) cimg_snprintf(str,sizd + 1,format,_mp_arg(3)); + else cimg_snprintf(str,sizd + 1,format,(long)_mp_arg(3)); } const unsigned int l = std::min(sizd,(unsigned int)std::strlen(str) + 1); CImg(ptrd,l,1,1,1,true) = str.get_shared_points(0,l - 1); return cimg::type::nan(); - } + } - static double mp_whiledo(_cimg_math_parser& mp) { + static double mp_while(_cimg_math_parser& mp) { const ulongT mem_body = mp.opcode[1], mem_cond = mp.opcode[2]; @@ -23371,7 +28534,49 @@ namespace cimg_library_suffixed { 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 + switch (interpolation) { + case 2 : // Cubic interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), + mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2), + cx = mx::nan(); } @@ -23479,7 +28663,49 @@ namespace cimg_library_suffixed { 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 + switch (interpolation) { + case 2 : // Cubic interpolation + switch (boundary_conditions) { + case 3 : { // Mirror + const float + w2 = 2.f*img.width(), h2 = 2.f*img.height(), d2 = 2.f*img.depth(), + mx = cimg::mod((float)x,w2), my = cimg::mod((float)y,h2), mz = cimg::mod((float)z,d2), + cx = mx::nan(); } @@ -23537,11 +28742,10 @@ namespace cimg_library_suffixed { }; // struct _cimg_math_parser {} -#define _cimg_create_pointwise_functions(name,func,openmp_size) \ +#define _cimg_create_pointwise_functions(name,func,min_size) \ CImg& name() { \ if (is_empty()) return *this; \ - cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=openmp_size)) \ - cimg_rof(*this,ptrd,T) *ptrd = (T)func((double)*ptrd); \ + cimg_openmp_for(*this,func((typename cimg::superset::type)*ptr),min_size); \ return *this; \ } \ CImg get_##name() const { \ @@ -23587,6 +28791,17 @@ namespace cimg_library_suffixed { **/ _cimg_create_pointwise_functions(exp,std::exp,4096) + //! Compute the error function of each pixel value. + /** + Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by its error function. + \note + - The \inplace of this method statically casts the computed values to the pixel type \c T. + - The \newinstance returns a \c CImg image, if the pixel type \c T is \e not float-valued. + **/ +#if cimg_use_cpp11==1 + _cimg_create_pointwise_functions(erf,std::erf,4096) +#endif + //! 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 @@ -23752,9 +28967,9 @@ namespace cimg_library_suffixed { \par Example \code const CImg - 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(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 **/ @@ -23879,56 +29094,26 @@ namespace cimg_library_suffixed { \par Example \code const CImg - 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("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& 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==-4) { cimg_openmp_for(*this,1/(Tfloat)cimg::pow4(*ptr),32768); return *this; } + if (p==-3) { cimg_openmp_for(*this,1/(Tfloat)cimg::pow3(*ptr),32768); return *this; } + if (p==-2) { cimg_openmp_for(*this,1/(Tfloat)cimg::sqr(*ptr),32768); return *this; } + if (p==-1) { cimg_openmp_for(*this,1/(Tfloat)*ptr,32768); return *this; } + if (p==-0.5) { cimg_openmp_for(*this,1/std::sqrt((Tfloat)*ptr),8192); 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); + if (p==3) { cimg_openmp_for(*this,cimg::pow3(*ptr),262144); return *this; } + if (p==4) { cimg_openmp_for(*this,cimg::pow4(*ptr),131072); return *this; } + cimg_openmp_for(*this,std::pow((Tfloat)*ptr,(Tfloat)p),1024); return *this; } @@ -23942,7 +29127,7 @@ namespace cimg_library_suffixed { Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition. **/ CImg& pow(const char *const expression) { - return pow((+*this)._fill(expression,true,true,0,0,"pow",this)); + return pow((+*this)._fill(expression,true,3,0,"pow",this,0)); } //! Raise each pixel value to a power, specified from an expression \newinstance. @@ -23980,8 +29165,7 @@ namespace cimg_library_suffixed { **/ CImg& 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); + cimg_openmp_for(*this,cimg::rol(*ptr,n),32768); return *this; } @@ -23995,7 +29179,7 @@ namespace cimg_library_suffixed { Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift. **/ CImg& rol(const char *const expression) { - return rol((+*this)._fill(expression,true,true,0,0,"rol",this)); + return rol((+*this)._fill(expression,true,3,0,"rol",this,0)); } //! Compute the bitwise left rotation of each pixel value \newinstance. @@ -24033,8 +29217,7 @@ namespace cimg_library_suffixed { **/ CImg& 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); + cimg_openmp_for(*this,cimg::ror(*ptr,n),32768); return *this; } @@ -24048,7 +29231,7 @@ namespace cimg_library_suffixed { Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift. **/ CImg& ror(const char *const expression) { - return ror((+*this)._fill(expression,true,true,0,0,"ror",this)); + return ror((+*this)._fill(expression,true,3,0,"ror",this,0)); } //! Compute the bitwise right rotation of each pixel value \newinstance. @@ -24086,16 +29269,15 @@ namespace cimg_library_suffixed { \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& min(const T& val) { + CImg& min(const T& value) { 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); + cimg_openmp_for(*this,std::min(*ptr,value),65536); return *this; } //! Pointwise min operator between instance image and a value \newinstance. - CImg get_min(const T& val) const { - return (+*this).min(val); + CImg get_min(const T& value) const { + return (+*this).min(value); } //! Pointwise min operator between two images. @@ -24131,7 +29313,7 @@ namespace cimg_library_suffixed { \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. **/ CImg& min(const char *const expression) { - return min((+*this)._fill(expression,true,true,0,0,"min",this)); + return min((+*this)._fill(expression,true,3,0,"min",this,0)); } //! Pointwise min operator between an image and an expression \newinstance. @@ -24145,16 +29327,15 @@ namespace cimg_library_suffixed { \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& max(const T& val) { + CImg& max(const T& value) { 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); + cimg_openmp_for(*this,std::max(*ptr,value),65536); return *this; } //! Pointwise max operator between instance image and a value \newinstance. - CImg get_max(const T& val) const { - return (+*this).max(val); + CImg get_max(const T& value) const { + return (+*this).max(value); } //! Pointwise max operator between two images. @@ -24190,7 +29371,7 @@ namespace cimg_library_suffixed { \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. **/ CImg& max(const char *const expression) { - return max((+*this)._fill(expression,true,true,0,0,"max",this)); + return max((+*this)._fill(expression,true,3,0,"max",this,0)); } //! Pointwise max operator between an image and an expression \newinstance. @@ -24198,6 +29379,124 @@ namespace cimg_library_suffixed { return CImg(*this,false).max(expression); } + //! Pointwise minabs operator between instance image and a value. + /** + \param val Value used as the reference argument of the minabs operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{minabs}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& minabs(const T& value) { + if (is_empty()) return *this; + const T absvalue = cimg::abs(value); + cimg_openmp_for(*this,cimg::minabs(*ptr,value,absvalue),65536); + return *this; + } + + //! Pointwise minabs operator between instance image and a value \newinstance. + CImg get_minabs(const T& value) const { + return (+*this).minabs(value); + } + + //! Pointwise minabs operator between two images. + /** + \param img Image used as the reference argument of the minabs operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{minabs}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& minabs(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return minabs(+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 + CImg<_cimg_Tt> get_minabs(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).minabs(img); + } + + //! Pointwise minabs 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{minabs}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& minabs(const char *const expression) { + return minabs((+*this)._fill(expression,true,3,0,"minabs",this,0)); + } + + //! Pointwise minabs operator between an image and an expression \newinstance. + CImg get_minabs(const char *const expression) const { + return CImg(*this,false).minabs(expression); + } + + //! Pointwise maxabs operator between instance image and a value. + /** + \param val Value used as the reference argument of the maxabs operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{maxabs}(I_{(x,y,z,c)},\mathrm{val})\f$. + **/ + CImg& maxabs(const T& value) { + if (is_empty()) return *this; + const T absvalue = cimg::abs(value); + cimg_openmp_for(*this,cimg::maxabs(*ptr,value,absvalue),65536); + return *this; + } + + //! Pointwise maxabs operator between instance image and a value \newinstance. + CImg get_maxabs(const T& value) const { + return (+*this).maxabs(value); + } + + //! Pointwise maxabs operator between two images. + /** + \param img Image used as the reference argument of the maxabs operator. + \note Replace each pixel value \f$I_{(x,y,z,c)}\f$ of the image instance by + \f$\mathrm{maxabs}(I_{(x,y,z,c)},\mathrm{img}_{(x,y,z,c)})\f$. + **/ + template + CImg& maxabs(const CImg& img) { + const ulongT siz = size(), isiz = img.size(); + if (siz && isiz) { + if (is_overlapped(img)) return maxabs(+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 + CImg<_cimg_Tt> get_maxabs(const CImg& img) const { + return CImg<_cimg_Tt>(*this,false).maxabs(img); + } + + //! Pointwise maxabs 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{maxabs}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. + **/ + CImg& maxabs(const char *const expression) { + return maxabs((+*this)._fill(expression,true,3,0,"maxabs",this,0)); + } + + //! Pointwise maxabs operator between an image and an expression \newinstance. + CImg get_maxabs(const char *const expression) const { + return CImg(*this,false).maxabs(expression); + } + //! Return a reference to the minimum pixel value. /** **/ @@ -24224,6 +29523,38 @@ namespace cimg_library_suffixed { return *ptr_min; } + //! Return a reference to the minimum pixel value in absolute value. + /** + **/ + T& minabs() { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "minabs(): Empty instance.", + cimg_instance); + T *ptr_minabs = _data; + T minabs_value = *ptr_minabs; + cimg_for(*this,ptrs,T) { + const T ma = cimg::abs(*ptrs); + if (mamaxabs_value) { maxabs_value = ma; ptr_maxabs = ptrs; } + } + return *ptr_maxabs; + } + + //! Return a reference to the maximum pixel value in absolute value \const. + const T& maxabs() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "maxabs(): Empty instance.", + cimg_instance); + const T *ptr_maxabs = _data; + T maxabs_value = *ptr_maxabs; + cimg_for(*this,ptrs,T) { + const T ma = cimg::abs(*ptrs); + if (ma>maxabs_value) { maxabs_value = ma; ptr_maxabs = ptrs; } + } + return *ptr_maxabs; + } + //! Return a reference to the minimum pixel value as well as the maximum pixel value. /** \param[out] max_val Maximum pixel value. @@ -24330,13 +29693,14 @@ namespace cimg_library_suffixed { //! Return the kth smallest pixel value. /** - \param k Rank of the search smallest element. + \param k Rank of the smallest element searched. **/ T kth_smallest(const ulongT k) const { if (is_empty()) throw CImgInstanceException(_cimg_instance "kth_smallest(): Empty instance.", cimg_instance); + if (k>=size()) return max(); CImg arr(*this,false); ulongT l = 0, ir = size() - 1; for ( ; ; ) { @@ -24493,7 +29857,7 @@ namespace cimg_library_suffixed { /** \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 + recommended 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. @@ -24506,11 +29870,12 @@ namespace cimg_library_suffixed { const ulongT siz = size(); if (!siz || !_data) return 0; - if (variance_method>1) { // Compute a scaled version of the Laplacian. + if (variance_method>1) { // Compute a scaled version of the Laplacian CImg tmp(*this,false); 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)) + const double cste = 1./std::sqrt(20.); // Depends on how the Laplacian is computed + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*262144 && + _spectrum>=2)) cimg_forC(*this,c) { CImg_3x3(I,T); cimg_for3x3(*this,x,y,0,c,I,T) { @@ -24519,8 +29884,9 @@ namespace cimg_library_suffixed { } } } 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)) + const double cste = 1./std::sqrt(42.); // Depends on how the Laplacian is computed + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*262144 && + _spectrum>=2)) cimg_forC(*this,c) { CImg_3x3x3(I,T); cimg_for3x3x3(*this,x,y,z,c,I,T) { @@ -24536,7 +29902,7 @@ namespace cimg_library_suffixed { // 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); + const double cste = 1./std::sqrt(20.); 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 + @@ -24544,7 +29910,7 @@ namespace cimg_library_suffixed { S+=val; S2+=val*val; } } else { - const double cste = 1.0/std::sqrt(42.0); + const double cste = 1./std::sqrt(42.); CImg_3x3x3(I,T); cimg_forC(*this,c) cimg_for3x3x3(*this,x,y,z,c,I,T) { const double val = cste * @@ -24599,37 +29965,131 @@ namespace cimg_library_suffixed { \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[out] list_outputs A pointer to a list of output images attached to the specified math formula. + \param list_images A list of 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 *const list_inputs=0, CImgList *const list_outputs=0) { - return _eval(this,expression,x,y,z,c,list_inputs,list_outputs); + CImgList *const list_images=0) { + return _eval(this,expression,x,y,z,c,list_images); } //! 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 *const list_inputs=0, CImgList *const list_outputs=0) const { - return _eval(0,expression,x,y,z,c,list_inputs,list_outputs); + CImgList *const list_images=0) const { + return _eval(0,expression,x,y,z,c,list_images); + } + + // Fast function to pre-evaluate common expressions. + // (return 'true' in case of success, and set value of 'res'). + template + bool __eval(const char *const expression, t &res) const { + +#define __eval_op(op) if (__eval_get(++ptr,val2) && !*ptr) { res = (t)(op); return true; } else return false; + + double val1, val2; + if (!expression || !*expression || *expression==';' || *expression=='[') return false; + if (!expression[1]) switch (*expression) { + case 'w' : res = (t)_width; return true; + case 'h' : res = (t)_height; return true; + case 'd' : res = (t)_depth; return true; + case 's' : res = (t)_spectrum; return true; + case 'r' : res = (t)_is_shared; return true; + default : if (*expression>='0' && *expression<='9') { res = (t)(*expression - '0'); return true; } + } + if (*expression=='w' && expression[1]=='h') { + if (!expression[2]) { res = (t)(_width*_height); return true; } + if (expression[2]=='d') { + if (!expression[3]) { res = (t)(_width*_height*_depth); return true; } + if (expression[3]=='s' && !expression[4]) { res = (t)(_width*_height*_depth*_spectrum); return true; } + } + if (expression[2]=='s' && !expression[3]) { res = (t)(_width*_height*_spectrum); return true; } + } + const char *ptr = expression; + while (*ptr && cimg::is_blank(*ptr)) ++ptr; + if (*ptr=='\'' && *(++ptr)) { // Detect 'stringA' op 'stringB' (op='==' or '!=') + const char *ptr2 = std::strchr(ptr,'\''); + if (ptr2) { + const char *ptr3 = ptr2 + 1; + while (*ptr3 && cimg::is_blank(*ptr3)) ++ptr3; + const char *ptr4 = ptr3; + if ((*ptr3=='!' || *ptr3=='=') && *(++ptr4)=='=') { + ++ptr4; + while (*ptr4 && cimg::is_blank(*ptr4)) ++ptr4; + if (*ptr4=='\'' && *(++ptr4)) { + const char *const ptr5 = std::strchr(ptr4,'\''); + if (ptr5) { + const char *ptr6 = ptr5 + 1; + while (*ptr6 && cimg::is_blank(*ptr6)) ++ptr6; + if (!*ptr6) { + CImg str1(ptr,ptr2 - ptr,1,1,1,true), str2(ptr4,ptr5 - ptr4,1,1,1,true); + if (*ptr3=='!') res = (t)!(str1==str2); else res = (t)(str1==str2); + return true; + } + } + } + } + } + return false; + } + if (__eval_get(ptr,val1)) { // Detect 'value1' op 'value2' + switch (*ptr) { + case 0 : res = (t)val1; return true; + case '+' : __eval_op(val1 + val2); + case '-' : __eval_op(val1 - val2); + case '*' : __eval_op(val1 * val2); + case '/' : __eval_op(val1 / val2); + case '%' : __eval_op(cimg::mod(val1,val2)); + case '&' : if (ptr[1]=='&') { ++ptr; __eval_op(val1 && val2); } else { __eval_op((long)val1 & (long)val2); } + case '|' : if (ptr[1]=='|') { ++ptr; __eval_op(val1 || val2); } else { __eval_op((long)val1 | (long)val2); } + case '>' : if (ptr[1]=='=') { ++ptr; __eval_op(val1>=val2); } else { __eval_op(val1>val2); } + case '<' : if (ptr[1]=='=') { ++ptr; __eval_op(val1<=val2); } else { __eval_op(val1 *const img_output, const char *const expression, const double x, const double y, const double z, const double c, - const CImgList *const list_inputs, CImgList *const list_outputs) const { + CImgList *const list_images) const { if (!expression || !*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=='<' || + double _val = 0; + if (__eval(expression,_val)) return _val; + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || *expression=='+' || *expression=='*' || *expression==':'),"eval", - *this,img_output,list_inputs,list_outputs,false); + *this,img_output,list_images,false); + mp.begin_t(); const double val = mp(x,y,z,c); + mp.end_t(); mp.end(); return val; } @@ -24643,41 +30103,37 @@ namespace cimg_library_suffixed { \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[out] list_outputs A pointer to a list of output images attached to the specified math formula. + \param list_images A list of input images attached to the specified math formula. **/ template void eval(CImg &output, const char *const expression, const double x=0, const double y=0, const double z=0, const double c=0, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { - _eval(output,this,expression,x,y,z,c,list_inputs,list_outputs); + CImgList *const list_images=0) { + _eval(output,this,expression,x,y,z,c,list_images); } //! Evaluate math formula \const. template void eval(CImg& output, const char *const expression, const double x=0, const double y=0, const double z=0, const double c=0, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { - _eval(output,0,expression,x,y,z,c,list_inputs,list_outputs); + CImgList *const list_images=0) const { + _eval(output,0,expression,x,y,z,c,list_images); } template void _eval(CImg& output, CImg *const img_output, const char *const expression, const double x, const double y, const double z, const double c, - const CImgList *const list_inputs, CImgList *const list_outputs) const { - if (!expression || !*expression) { output.assign(1); *output = 0; } - if (!expression[1]) switch (*expression) { // Single-char optimization. - case 'w' : output.assign(1); *output = (t)_width; break; - case 'h' : output.assign(1); *output = (t)_height; break; - case 'd' : output.assign(1); *output = (t)_depth; break; - case 's' : output.assign(1); *output = (t)_spectrum; break; - case 'r' : output.assign(1); *output = (t)_is_shared; break; - } - _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || + CImgList *const list_images) const { + if (!expression || !*expression) { output.assign(1); *output = 0; return; } + double _val = 0; + if (__eval(expression,_val)) { output.assign(1); *output = _val; return; } + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || *expression=='+' || *expression=='*' || *expression==':'),"eval", - *this,img_output,list_inputs,list_outputs,false); + *this,img_output,list_images,false); output.assign(1,std::max(1U,mp.result_dim)); + mp.begin_t(); mp(x,y,z,c,output._data); + mp.end_t(); mp.end(); } @@ -24685,56 +30141,63 @@ namespace cimg_library_suffixed { /** \param expression Math formula, as a C-string. \param xyzc Set of values (x,y,z,c) used for the evaluation. - \param list_inputs A list of input images attached to the specified math formula. - \param[out] list_outputs A pointer to a list of output images attached to the specified math formula. + \param list_images A list of input images attached to the specified math formula. **/ template CImg eval(const char *const expression, const CImg& xyzc, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { - return _eval(this,expression,xyzc,list_inputs,list_outputs); + CImgList *const list_images=0) { + return _eval(this,expression,xyzc,list_images); } //! Evaluate math formula on a set of variables \const. template CImg eval(const char *const expression, const CImg& xyzc, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { - return _eval(0,expression,xyzc,list_inputs,list_outputs); + CImgList *const list_images=0) const { + return _eval(0,expression,xyzc,list_images); } template CImg _eval(CImg *const output, const char *const expression, const CImg& xyzc, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { + CImgList *const list_images=0) const { CImg res(1,xyzc.size()/4); if (!expression || !*expression) return res.fill(0); - _cimg_math_parser mp(expression,"eval",*this,output,list_inputs,list_outputs,false); -#ifdef cimg_use_openmp + _cimg_math_parser mp(expression,"eval",*this,output,list_images,false); + +#if cimg_use_openmp!=0 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; + *const _mp = omp_get_thread_num()?new _cimg_math_parser(mp):&mp, + &lmp = *_mp; + cimg_pragma_openmp(barrier) + lmp.begin_t(); cimg_pragma_openmp(for) - for (unsigned int i = 0; i[min, max, mean, variance, xmin, ymin, zmin, cmin, xmax, ymax, zmax, cmax, sum, product]. @@ -24747,7 +30210,7 @@ namespace cimg_library_suffixed { longT offm = 0, offM = 0; T m = *_data, M = m; - cimg_pragma_openmp(parallel reduction(+:S,S2) reduction(*:P) cimg_openmp_if(siz>=131072)) { + cimg_pragma_openmp(parallel reduction(+:S,S2) reduction(*:P) cimg_openmp_if_size(siz,131072)) { longT loffm = 0, loffM = 0; T lm = *_data, lM = lm; cimg_pragma_openmp(for) @@ -24797,29 +30260,37 @@ namespace cimg_library_suffixed { //! Compute norm of the image, viewed as a matrix. /** - \param magnitude_type Norm type. Can be: - - \c -1: Linf-norm + \param magnitude_type Can be: - \c 0: L0-norm - \c 1: L1-norm - \c 2: L2-norm + - \c p>2 : Lp-norm + - \c ~0U: Linf-norm **/ - double magnitude(const int magnitude_type=2) const { + double magnitude(const float magnitude_type=2) const { if (is_empty()) throw CImgInstanceException(_cimg_instance "magnitude(): Empty instance.", cimg_instance); + const ulongT siz = size(); 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); + if (magnitude_type==2) { // L2 + cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) + for (longT off = 0; off<(longT)siz; ++off) res+=(double)cimg::sqr(_data[off]); res = (double)std::sqrt(res); - } + } else if (magnitude_type==1) { // L1 + cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) + for (longT off = 0; off<(longT)siz; ++off) res+=(double)cimg::abs(_data[off]); + } else if (!magnitude_type) { // L0 + cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) + for (longT off = 0; off<(longT)siz; ++off) res+=(double)(_data[off]?1:0); + } else if (cimg::type::is_inf(magnitude_type)) { // L-inf + cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } + } else { // L-p + cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) + for (longT off = 0; off<(longT)siz; ++off) + res+=(double)std::pow((double)cimg::abs(_data[off]),(double)magnitude_type); + res = (double)std::pow(res,1.0/magnitude_type); } return res; } @@ -24874,18 +30345,10 @@ namespace cimg_library_suffixed { **/ template double dot(const CImg& 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& vector() { - return unroll('y'); - } - - //! Unroll pixel values along axis \c y \newinstance. - CImg get_vector() const { - return get_unroll('y'); - } - - //! Resize image to become a scalar square matrix. - /** - **/ - CImg& 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 get_matrix() const { - return (+*this).matrix(); - } - - //! Resize image to become a symmetric tensor. - /** - **/ - CImg& tensor() { - return get_tensor().move_to(*this); - } - - //! Resize image to become a symmetric tensor \newinstance. - CImg get_tensor() const { - CImg 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. @@ -25134,7 +30514,7 @@ namespace cimg_library_suffixed { //! Transpose the image, viewed as a matrix. /** - \note Equivalent to \code permute_axes("yxzc"); \endcode + \note Equivalent to \code permute_axes("yxzc"); \endcode. **/ CImg& transpose() { if (_width==1) { _width = _height; _height = 1; return *this; } @@ -25151,7 +30531,7 @@ namespace cimg_library_suffixed { return get_permute_axes("yxzc"); } - //! Compute the cross product between two \c 1x3 images, viewed as 3d vectors. + //! 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. @@ -25160,7 +30540,7 @@ namespace cimg_library_suffixed { CImg& cross(const CImg& 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.", + "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); @@ -25171,7 +30551,7 @@ namespace cimg_library_suffixed { return *this; } - //! Compute the cross product between two \c 1x3 images, viewed as 3d vectors \newinstance. + //! Compute the cross product between two \c 1x3 images, viewed as 3D vectors \newinstance. template CImg<_cimg_Tt> get_cross(const CImg& img) const { return CImg<_cimg_Tt>(*this).cross(img); @@ -25179,46 +30559,33 @@ namespace cimg_library_suffixed { //! Invert the instance image, viewed as a matrix. /** + If the instance matrix is not square, the Moore-Penrose pseudo-inverse is computed instead. \param use_LU Choose the inverting algorithm. Can be: - - \c true: LU-based matrix inversion. - - \c false: SVD-based matrix inversion. + - \c true: LU solver (faster but sometimes less precise). + - \c false: SVD solver (more precise but slower). + \param lambda is used only in the Moore-Penrose pseudoinverse for estimating A^t.(A^t.A + lambda.Id)^-1. **/ - CImg& invert(const bool use_LU=true) { - if (_width!=_height || _depth!=1 || _spectrum!=1) + CImg& invert(const bool use_LU=false, const float lambda=0) { + if (_depth!=1 || _spectrum!=1) throw CImgInstanceException(_cimg_instance - "invert(): Instance is not a square matrix.", + "invert(): Instance is not a 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) { + if (lambda<0) + throw CImgArgumentException(_cimg_instance + "invert(): Specified lambda (%g) should be >=0.", + cimg_instance); + + if (_width!=_height) return get_invert(use_LU,lambda).move_to(*this); // Non-square matrix: Pseudoinverse + + // Square matrix. + const double dete = _width>3?-1.:det(); + if (dete!=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) { + } else if (dete!=0. && _width==3) { const double a = _data[0], d = _data[1], g = _data[2], b = _data[3], e = _data[4], h = _data[5], @@ -25227,49 +30594,99 @@ namespace cimg_library_suffixed { _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 A(*this,false), indx, col(1,_width); + +#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 + if (use_LU) { // LU solver + CImg A(*this,false), indx; bool d; A._LU(indx,d); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width*_height,16*16)) cimg_forX(*this,j) { - col.fill(0); + CImg col(1,_width,1,1,0); col(j) = 1; col._solve(A,indx); cimg_forX(*this,i) (*this)(j,i) = (T)col(i); } - } else { // SVD-based inverse computation - CImg 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; - } - } + } else _get_invert_svd(false).move_to(*this); // SVD solver #endif + } return *this; } //! Invert the instance image, viewed as a matrix \newinstance. - CImg get_invert(const bool use_LU=true) const { - return CImg(*this,false).invert(use_LU); + CImg get_invert(const bool use_LU=false, const float lambda=0) const { + if (_depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "invert(): Instance is not a matrix.", + cimg_instance); + if (lambda<0) + throw CImgArgumentException(_cimg_instance + "invert(): Specified lambda (%g) should be >=0.", + cimg_instance); + + if (_width==_height) return CImg(*this,false).invert(use_LU,lambda); // Square matrix + + // Non-square matrix: Pseudoinverse + if (use_LU) { + if (_width<_height) { // under-solved system -> (A^t.A)^-1.A^t + CImg AtA(width(),width()); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width*_height,128*128)) + cimg_forY(AtA,i) + for (int j = 0; j<=i; ++j) { + double res = 0; + cimg_forY(*this,k) res+=(*this)(i,k)*(*this)(j,k); + AtA(j,i) = AtA(i,j) = (Tfloat)res; + } + if (lambda!=0) cimg_forY(AtA,i) AtA(i,i)+=lambda; + AtA.invert(true); + return AtA*get_transpose(); + } else { // over-resolved linear system -> A^t.(A.A^t)^-1 + CImg AAt(height(),height()); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width*_height,128*128)) + cimg_forY(AAt,i) + for (int j = 0; j<=i; ++j) { + double res = 0; + cimg_forX(*this,k) res+=(*this)(k,i)*(*this)(k,j); + AAt(j,i) = AAt(i,j) = (Tfloat)res; + } + if (lambda!=0) cimg_forY(AAt,i) AAt(i,i)+=lambda; + AAt.invert(true); + return get_transpose()*AAt; + } + } + return _get_invert_svd(lambda); } - //! Compute the Moore-Penrose pseudo-inverse of the instance image, viewed as a matrix. - /** - **/ - CImg& pseudoinvert() { - return get_pseudoinvert().move_to(*this); - } - - //! Compute the Moore-Penrose pseudo-inverse of the instance image, viewed as a matrix \newinstance. - CImg get_pseudoinvert() const { + // SVD solver, both used for inverse and pseudoinverse. + CImg _get_invert_svd(const float lambda) const { CImg 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(); + SVD(U,S,V,false); + const Tfloat epsilon = (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; + const Tfloat s = S(x), invs = lambda?1/(lambda + s):s>epsilon?1/s:0; + cimg_forY(V,y) V(x,y)*=invs; } return V*U.transpose(); } @@ -25277,10 +30694,13 @@ namespace cimg_library_suffixed { //! Solve a system of linear equations. /** \param A Matrix of the linear system. - \note Solve \c AX=B where \c B=*this. + \param use_LU In case of non square system (least-square solution), + choose between SVD (\c false) or LU (\c true) solver. + LU solver is faster for large matrices, but numerically less stable. + \note Solve \c AX = B where \c B=*this. **/ template - CImg& solve(const CImg& A) { + CImg& solve(const CImg& A, const bool use_LU=false) { 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 " @@ -25288,12 +30708,37 @@ namespace cimg_library_suffixed { 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 res(_width,A._width); - cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); - return res.move_to(*this); - } + + if (A.size()==1) return (*this)/=A[0]; + if (A._width==2 && A._height==2 && _height==2) { // 2x2 linear system + const double a = (double)A[0], b = (double)A[1], c = (double)A[2], d = (double)A[3], + fa = std::fabs(a), fb = std::fabs(b), fc = std::fabs(c), fd = std::fabs(d), + det = a*d - b*c, fM = cimg::max(fa,fb,fc,fd); + if (fM==fa) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256)) + cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), y = (a*v - c*u)/det; + (*this)(k,0) = (T)((u - b*y)/a); (*this)(k,1) = (T)y; + } else if (fM==fc) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256)) + cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), y = (a*v - c*u)/det; + (*this)(k,0) = (T)((v - d*y)/c); (*this)(k,1) = (T)y; + } else if (fM==fb) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256)) + cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), x = (d*u - b*v)/det; + (*this)(k,0) = (T)x; (*this)(k,1) = (T)((u - a*x)/b); + } else + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256)) + cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), x = (d*u - b*v)/det; + (*this)(k,0) = (T)x; (*this)(k,1) = (T)((v - c*x)/d); + } + return *this; + } + + if (A._width==A._height) { // Square linear system #ifdef cimg_use_lapack char TRANS = 'N'; int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N]; @@ -25302,63 +30747,61 @@ namespace cimg_library_suffixed { *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); + cimg_forX(*this,i) { + cimg_forY(*this,j) lapB[j] = (Ttfloat)((*this)(i,j)); + cimg::getrf(N,lapA,IPIV,INFO); if (INFO) cimg::warn(_cimg_instance - "solve(): LAPACK library function dgetrs_() returned error code %d.", + "solve(): LAPACK library function dgetrf_() returned error code %d.", cimg_instance, INFO); + else { + 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,j) (*this)(i,j) = (T)(lapB[j]); else cimg_forY(*this,j) (*this)(i,j) = (T)0; } - if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0); delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK; #else CImg lu(A,false); CImg indx; bool d; lu._LU(indx,d); - _solve(lu,indx); + CImg res(_width,A._width); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width*_height,16)) + cimg_forX(*this,i) res.draw_image(i,get_column(i)._solve(lu,indx)); + res.move_to(*this); #endif - } else { // Least-square solution for non-square systems. + } else { // Least-square solution for non-square systems + #ifdef cimg_use_lapack - if (_width!=1) { - CImg 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'; + 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]; + 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); + 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)); + assign(NRHS, N); + if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l]; + else (A.get_invert(use_LU)*(*this)).move_to(*this); delete[] lapA; delete[] lapB; delete[] WORK; #else - assign(A.get_pseudoinvert()*(*this)); + (A.get_invert(use_LU)*(*this)).move_to(*this); #endif } return *this; @@ -25366,19 +30809,20 @@ namespace cimg_library_suffixed { //! Solve a system of linear equations \newinstance. template - CImg<_cimg_Ttfloat> get_solve(const CImg& A) const { - return CImg<_cimg_Ttfloat>(*this,false).solve(A); + CImg<_cimg_Ttfloat> get_solve(const CImg& A, const bool use_LU=false) const { + typedef _cimg_Ttfloat Ttfloat; + return CImg(*this,false).solve(A,use_LU); } template CImg& _solve(const CImg& A, const CImg& indx) { typedef _cimg_Ttfloat Ttfloat; - const int N = (int)size(); + const int N = height(); int ii = -1; Ttfloat sum; for (int i = 0; i=0) for (int j = ii; j<=i - 1; ++j) sum-=A(j,i)*(*this)(j); else if (sum!=0) ii = i; @@ -25448,11 +30892,9 @@ namespace cimg_library_suffixed { 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); - + if (f<0) cimg::warn(_cimg_instance + "eigen(): Complex eigenvalues found.", + cimg_instance); f = std::sqrt(f); const double l1 = 0.5*(e - f), @@ -25492,71 +30934,78 @@ namespace cimg_library_suffixed { **/ template const CImg& symmetric_eigen(CImg& val, CImg& 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); + if (is_empty()) { val.assign(); vec.assign(); return *this; } + if (_width!=_height || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "eigen(): Instance is not a square matrix.", + cimg_instance); + val.assign(1,_width); + vec.assign(_width,_width); - 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 V(_width,_width); - Tfloat M = 0, m = (Tfloat)min_max(M), maxabs = cimg::max((Tfloat)1,cimg::abs(m),cimg::abs(M)); - (CImg(*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 permutations; // sort eigenvalues in decreasing order - CImg 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 + if (_width==1) { val[0] = cimg::abs((*this)[0]); vec[0] = 1; return *this; } + if (_width==2) { + const double + a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], + e = a + d, f = std::sqrt(std::max(e*e - 4*(a*d - b*c),0.0)), + l1 = 0.5*(e - f), l2 = 0.5*(e + f), + n = std::sqrt(cimg::sqr(l2 - a) + b*b); + val[0] = (t)l2; + val[1] = (t)l1; + if (n>0) { vec[0] = (t)(b/n); vec[2] = (t)((l2 - a)/n); } else { vec[0] = 1; vec[2] = 0; } + vec[1] = -vec[2]; + vec[3] = vec[0]; + return *this; } + +#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); + 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 + CImg V(_width,_width); + Tfloat M = 0, m = (Tfloat)min_max(M), maxabs = cimg::max((Tfloat)1,cimg::abs(m),cimg::abs(M)); + (CImg(*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 permutations; // Sort eigenvalues in decreasing order + CImg 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; } @@ -25719,7 +31168,7 @@ namespace cimg_library_suffixed { \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). + const CImg<> A; // Input matrix (assumed to contain some values) CImg<> U,S,V; A.SVD(U,S,V) \endcode @@ -25727,26 +31176,41 @@ namespace cimg_library_suffixed { template const CImg& SVD(CImg& U, CImg& S, CImg& V, const bool sorting=true, const unsigned int max_iteration=40, const float lambda=0) const { + typedef _cimg_Ttfloat Ttfloat; + const Ttfloat epsilon = (Ttfloat)1e-25; + if (is_empty()) { U.assign(); S.assign(); V.assign(); } + else if (_depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "SVD(): Instance has invalid dimensions (depth or channels different from 1).", + cimg_instance); else { U = *this; - if (lambda!=0) { - const unsigned int delta = std::min(U._width,U._height); - for (unsigned int i = 0; i rv1(_width); - t anorm = 0, c, f, g = 0, h, s, scale = 0; - int l = 0, nm = 0; + Ttfloat anorm = 0, c, f, g = 0, h, s, scale = 0; + int l = 0; cimg_forX(U,i) { - l = i + 1; rv1[i] = scale*g; g = s = scale = 0; + l = i + 1; + rv1[i] = scale*g; + g = s = scale = 0; if (i=0?-1:1)*std::sqrt(s)); h=f*g-s; U(i,i) = f-g; + for (int k = i; k=0?-1:1)*std::sqrt(s)); + h = f*g - s; + U(i,i) = f - g; for (int j = l; j=0?-1:1)*std::sqrt(s)); h = f*g-s; U(l,i) = f-g; - for (int k = l; k=0?-1:1)*std::sqrt(s)); + h = f*g - s; + U(l,i) = f - g; + for (int k = l; k=0; --i) { - if (i=0; --i) { - l = i + 1; g = S[i]; + l = i + 1; + g = S[i]; for (int j = l; j=0; --k) { + int nm = 0; for (unsigned int its = 0; its=1; --l) { @@ -25815,12 +31290,23 @@ namespace cimg_library_suffixed { if ((cimg::abs(S[nm]) + anorm)==anorm) break; } if (flag) { - c = 0; s = 1; + c = 0; + s = 1; for (int i = l; i<=k; ++i) { - f = s*rv1[i]; rv1[i] = c*rv1[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; } + 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; + } } } @@ -25828,25 +31314,48 @@ namespace cimg_library_suffixed { 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); + g = rv1[nm]; + h = rv1[k]; + f = ((y - z)*(y + z) + (g - h)*(g + h))/std::max(epsilon,(Ttfloat)2*h*y); + g = cimg::_hypot(f,(Ttfloat)1); + f = ((x - z)*(x + z) + h*((y/(f + (f>=0?g:-g))) - h))/std::max(epsilon,(Ttfloat)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; } + g = rv1[i]; + h = s*g; + g = c*g; + t y1 = S[i], z1 = cimg::_hypot(f,h); + rv1[j] = z1; + c = f/std::max(epsilon,(Ttfloat)z1); + s = h/std::max(epsilon,(Ttfloat)z1); + f = x*c + g*s; + g = g*c - x*s; + h = y1*s; + y1*=c; + cimg_forX(U,jj) { + const t x2 = V(j,jj), z2 = V(i,jj); + V(j,jj) = x2*c + z2*s; + V(i,jj) = z2*c - x2*s; + } + z1 = cimg::_hypot(f,h); + S[j] = z1; + if (z1) { + z1 = 1/std::max(epsilon,(Ttfloat)z1); + c = f*z1; + s = h*z1; + } + f = c*g + s*y1; + x = c*y1 - s*g; + cimg_forY(U,jj) { + const t y2 = U(j,jj), z2 = U(i,jj); + U(j,jj) = y2*c + z2*s; + U(i,jj) = z2*c - y2*s; + } } - rv1[l] = 0; rv1[k]=f; S[k]=x; + rv1[l] = 0; + rv1[k] = f; + S[k] = x; } } @@ -25887,32 +31396,37 @@ namespace cimg_library_suffixed { CImg vv(N); indx.assign(N); d = true; + + bool return0 = false; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=512)) 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; + if (vmax==0) return0 = true; else vv[i] = 1/vmax; } + if (return0) { indx.fill(0); return fill(0); } + cimg_forX(*this,j) { for (int i = 0; i=vmax) { vmax=tmp; imax=i; } + if (tmp>=vmax) { vmax = tmp; imax = i; } } if (j!=imax) { cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j)); - d =!d; + d = !d; vv[imax] = vv[j]; } indx[j] = (t)imax; @@ -25922,17 +31436,161 @@ namespace cimg_library_suffixed { for (int i = j + 1; i=3 = orthogonal matching pursuit where an orthogonal projection step is performed + every 'method-2' iterations. + \param max_iter Sets the max number of iterations processed for each signal. + If set to '0' (default), 'max_iter' is set to the number of dictionary columns. + (only meaningful for matching pursuit and its variants). + \param max_residual Gives a stopping criterion on signal reconstruction accuracy. + (only meaningful for matching pursuit and its variants). + \return A matrix W whose columns correspond to the sparse weights of associated to each input matrix column. + Thus, the matrix product D*W is an approximation of the input matrix. + **/ + template + CImg& project_matrix(const CImg& dictionary, const unsigned int method=0, + const unsigned int max_iter=0, const double max_residual=1e-6) { + return get_project_matrix(dictionary,method,max_iter,max_residual).move_to(*this); + } + + template + CImg get_project_matrix(const CImg& dictionary, const unsigned int method=0, + const unsigned int max_iter=0, const double max_residual=1e-6) const { + if (_depth!=1 || _spectrum!=1) + throw CImgInstanceException(_cimg_instance + "project_matrix(): Instance image is not a matrix.", + cimg_instance); + if (dictionary._height!=_height || dictionary._depth!=1 || dictionary._spectrum!=1) + throw CImgArgumentException(_cimg_instance + "project_matrix(): Specified dictionary (%u,%u,%u,%u) has an invalid size.", + cimg_instance, + dictionary._width,dictionary._height,dictionary._depth,dictionary._spectrum); + + if (!method) return get_solve(dictionary); + CImg W(_width,dictionary._width,1,1,0); + + // Compute dictionary norm and normalize it. + CImg D(dictionary,false), Dnorm(D._width); + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=2 && _width*_height>=32)) + cimg_forX(Dnorm,d) { + Tfloat norm = 0; + cimg_forY(D,y) norm+=cimg::sqr(D(d,y)); + Dnorm[d] = std::max((Tfloat)1e-8,std::sqrt(norm)); + } + cimg_forXY(D,d,y) D(d,y)/=Dnorm[d]; + + // Matching pursuit. + const unsigned int proj_step = method<3?1:method - 2; + bool is_orthoproj = false; + + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=2 && _width*_height>=32)) + cimg_forX(*this,x) { + CImg S = get_column(x); + const CImg S0 = method<2?CImg():S; + Tfloat residual = S.magnitude(2)/S._height; + const unsigned int nmax = max_iter?max_iter:D._width; + + for (unsigned int n = 0; nmax_residual; ++n) { + + // Find best matching column in D. + int dmax = 0; + Tfloat absdotmax = 0, dotmax = 0; + cimg_pragma_openmp(parallel for cimg_openmp_if(D._width>=2 && D._width*D._height>=32)) + cimg_forX(D,d) { + Tfloat _dot = 0; + cimg_forY(D,y) _dot+=S[y]*D(d,y); + Tfloat absdot = cimg::abs(_dot); + cimg_pragma_openmp(critical(get_project_matrix)) { + if (absdot>absdotmax) { + absdotmax = absdot; + dotmax = _dot; + dmax = d; + } + } + } + + if (!n || method<3 || n%proj_step) { + // Matching Pursuit: Subtract component to signal. + W(x,dmax)+=dotmax; + residual = 0; + cimg_forY(S,y) { + S[y]-=dotmax*D(dmax,y); + residual+=cimg::sqr(S[y]); + } + residual = std::sqrt(residual)/S._height; + is_orthoproj = false; + + } else { + // Orthogonal Matching Pursuit: Orthogonal projection step. + W(x,dmax) = 1; // Used as a marker only. + unsigned int nbW = 0; + cimg_forY(W,d) if (W(x,d)) ++nbW; + CImg sD(nbW,D._height); + CImg inds(nbW); + int sd = 0; + cimg_forY(W,d) if (W(x,d)) { + cimg_forY(sD,y) sD(sd,y) = D(d,y); + inds[sd++] = d; + } + S0.get_solve(sD).move_to(sD); // sD is now a one-column vector of weights + + // Recompute residual signal. + S = S0; + cimg_forY(sD,k) { + const Tfloat weight = sD[k]; + const unsigned int ind = inds[k]; + W(x,ind) = weight; + cimg_forY(S,y) S[y]-=weight*D(ind,y); + } + residual = S.magnitude(2)/S._height; + is_orthoproj = true; + } + } + + // Perform last orthoprojection step if needed. + if (method>=2 && !is_orthoproj) { + unsigned int nbW = 0; + cimg_forY(W,d) if (W(x,d)) ++nbW; + if (nbW) { // Avoid degenerated case where 0 coefs are used + CImg sD(nbW,D._height); + CImg inds(nbW); + int sd = 0; + cimg_forY(W,d) if (W(x,d)) { + cimg_forY(sD,y) sD(sd,y) = D(d,y); + inds[sd++] = d; + } + S0.get_solve(sD).move_to(sD); + cimg_forY(sD,k) W(x,inds[k]) = sD[k]; + } + } + } + + // Normalize resulting coefficients according to initial (non-normalized) dictionary. + cimg_forXY(W,x,y) W(x,y)/=Dnorm[y]; + return W; + } + //! 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 + \param starting_node Index of the starting node. + \param ending_node Index of the ending node (set to ~0U to ignore ending node). + \param previous_node Array that gives the previous node index in the path to the starting node (optional parameter). \return Array of distances of each node to the starting node. **/ @@ -25941,7 +31599,7 @@ namespace cimg_library_suffixed { const unsigned int starting_node, const unsigned int ending_node, CImg& previous_node) { if (starting_node>=nb_nodes) - throw CImgArgumentException("CImg<%s>::dijkstra(): Specified indice of starting node %u is higher " + throw CImgArgumentException("CImg<%s>::dijkstra(): Specified index of starting node %u is higher " "than number of nodes %u.", pixel_type(),starting_node,nb_nodes); CImg dist(1,nb_nodes,1,1,cimg::type::max()); @@ -25999,9 +31657,9 @@ namespace cimg_library_suffixed { //! 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 + \param starting_node Index of the starting node. + \param ending_node Index of the ending node. + \param previous_node Array that gives the previous node index 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. @@ -26035,7 +31693,7 @@ namespace cimg_library_suffixed { return get_dijkstra(starting_node,ending_node,foo); } - //! Return an image containing the ascii codes of the specified string. + //! Return an image containing the character codes of 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. @@ -26046,6 +31704,162 @@ namespace cimg_library_suffixed { return CImg(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 row_vector(const T& a0) { + return vector(a0); + } + + //! Return a \c 2x1 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + **/ + static CImg row_vector(const T& a0, const T& a1) { + CImg r(2,1); + r[0] = a0; r[1] = a1; + return r; + } + + //! Return a \c 3x1 image containing specified values. + /** + \param a0 First vector value. + \param a1 Second vector value. + \param a2 Third vector value. + **/ + static CImg row_vector(const T& a0, const T& a1, const T& a2) { + CImg r(3,1); + r[0] = a0; r[1] = a1; r[2] = a2; + return r; + } + + //! Return a \c 4x1 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 row_vector(const T& a0, const T& a1, const T& a2, const T& a3) { + CImg r(4,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; + return r; + } + + //! Return a \c 5x1 image containing specified values. + static CImg row_vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { + CImg r(5,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; + return r; + } + + //! Return a \c 6x1 image containing specified values. + static CImg row_vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { + CImg r(6,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; + return r; + } + + //! Return a \c 7x1 image containing specified values. + static CImg row_vector(const T& a0, const T& a1, const T& a2, const T& a3, + const T& a4, const T& a5, const T& a6) { + CImg r(7,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; + return r; + } + + //! Return a \c 8x1 image containing specified values. + static CImg row_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 r(8,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; + return r; + } + + //! Return a \c 9x1 image containing specified values. + static CImg row_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 r(9,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; + return r; + } + + //! Return a \c 10x1 image containing specified values. + static CImg row_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 r(10,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + return r; + } + + //! Return a \c 11x1 image containing specified values. + static CImg row_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 r(11,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; + return r; + } + + //! Return a \c 12x1 image containing specified values. + static CImg row_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 r(12,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; + return r; + } + + //! Return a \c 13x1 image containing specified values. + static CImg row_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 r(13,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; + return r; + } + + //! Return a \c 14x1 image containing specified values. + static CImg row_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 r(14,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; + return r; + } + + //! Return a \c 15x1 image containing specified values. + static CImg row_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 r(15,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; + return r; + } + + //! Return a \c 16x1 image containing specified values. + static CImg row_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 r(16,1); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; r[15] = a15; + return r; + } + //! Return a \c 1x1 image containing specified value. /** \param a0 First vector value. @@ -26062,8 +31876,8 @@ namespace cimg_library_suffixed { \param a1 Second vector value. **/ static CImg vector(const T& a0, const T& a1) { - CImg r(1,2); T *ptr = r._data; - *(ptr++) = a0; *(ptr++) = a1; + CImg r(1,2); + r[0] = a0; r[1] = a1; return r; } @@ -26074,8 +31888,8 @@ namespace cimg_library_suffixed { \param a2 Third vector value. **/ static CImg vector(const T& a0, const T& a1, const T& a2) { - CImg r(1,3); T *ptr = r._data; - *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; + CImg r(1,3); + r[0] = a0; r[1] = a1; r[2] = a2; return r; } @@ -26087,51 +31901,47 @@ namespace cimg_library_suffixed { \param a3 Fourth vector value. **/ static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3) { - CImg r(1,4); T *ptr = r._data; - *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; + CImg r(1,4); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; return r; } //! Return a \c 1x5 image containing specified values. static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) { - CImg r(1,5); T *ptr = r._data; - *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; + CImg r(1,5); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; return r; } //! Return a \c 1x6 image containing specified values. static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) { - CImg r(1,6); T *ptr = r._data; - *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; + CImg r(1,6); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; return r; } //! Return a \c 1x7 image containing specified values. static CImg vector(const T& a0, const T& a1, const T& a2, const T& a3, - const T& a4, const T& a5, const T& a6) { - CImg r(1,7); T *ptr = r._data; - *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; - *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; + const T& a4, const T& a5, const T& a6) { + CImg r(1,7); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; return r; } //! Return a \c 1x8 image containing specified values. static CImg 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 r(1,8); T *ptr = r._data; - *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; - *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; + const T& a4, const T& a5, const T& a6, const T& a7) { + CImg r(1,8); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; return r; } //! Return a \c 1x9 image containing specified values. static CImg 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 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; + const T& a4, const T& a5, const T& a6, const T& a7, + const T& a8) { + CImg r(1,9); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; return r; } @@ -26139,10 +31949,8 @@ namespace cimg_library_suffixed { static CImg 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 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; + CImg r(1,10); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; return r; } @@ -26150,10 +31958,9 @@ namespace cimg_library_suffixed { static CImg 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 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; + CImg r(1,11); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; return r; } @@ -26161,10 +31968,9 @@ namespace cimg_library_suffixed { static CImg 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 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; + CImg r(1,12); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; return r; } @@ -26172,12 +31978,10 @@ namespace cimg_library_suffixed { static CImg 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 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; + const T& a12) { + CImg r(1,13); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; return r; } @@ -26185,12 +31989,10 @@ namespace cimg_library_suffixed { static CImg 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 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; + const T& a12, const T& a13) { + CImg r(1,14); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; return r; } @@ -26198,12 +32000,10 @@ namespace cimg_library_suffixed { static CImg 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 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; + const T& a12, const T& a13, const T& a14) { + CImg r(1,15); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; return r; } @@ -26211,12 +32011,74 @@ namespace cimg_library_suffixed { static CImg 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 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; + const T& a12, const T& a13, const T& a14, const T& a15) { + CImg r(1,16); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; r[15] = a15; + return r; + } + + //! Return a \c 1x17 image containing specified values. + static CImg 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, + const T& a16) { + CImg r(1,17); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; r[15] = a15; r[16] = a16; + return r; + } + + //! Return a \c 1x18 image containing specified values. + static CImg 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, + const T& a16, const T& a17) { + CImg r(1,18); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; r[15] = a15; r[16] = a16; r[17] = a17; + return r; + } + + //! Return a \c 1x19 image containing specified values. + static CImg 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, + const T& a16, const T& a17, const T& a18) { + CImg r(1,19); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; r[15] = a15; r[16] = a16; r[17] = a17; + r[18] = a18; + return r; + } + + //! Return a \c 1x20 image containing specified values. + static CImg 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, + const T& a16, const T& a17, const T& a18, const T& a19) { + CImg r(1,20); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; r[15] = a15; r[16] = a16; r[17] = a17; + r[18] = a18; r[19] = a19; + return r; + } + + //! Return a \c 1x21 image containing specified values. + static CImg 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, + const T& a16, const T& a17, const T& a18, const T& a19, + const T& a20) { + CImg r(1,21); + r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9; + r[10] = a10; r[11] = a11; r[12] = a12; r[13] = a13; r[14] = a14; r[15] = a15; r[16] = a16; r[17] = a17; + r[18] = a18; r[19] = a19; r[20] = a20; return r; } @@ -26237,7 +32099,7 @@ namespace cimg_library_suffixed { \param a3 Fourth matrix value. **/ static CImg matrix(const T& a0, const T& a1, - const T& a2, const T& a3) { + const T& a2, const T& a3) { CImg r(2,2); T *ptr = r._data; *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; @@ -26254,11 +32116,11 @@ namespace cimg_library_suffixed { \param a5 Sixth matrix value. \param a6 Seventh matrix value. \param a7 Eighth matrix value. - \param a8 Nineth matrix value. + \param a8 Ninth matrix value. **/ static CImg 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& a3, const T& a4, const T& a5, + const T& a6, const T& a7, const T& a8) { CImg r(3,3); T *ptr = r._data; *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; @@ -26268,9 +32130,9 @@ namespace cimg_library_suffixed { //! Return a 4x4 matrix containing specified coefficients. static CImg 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& 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 r(4,4); T *ptr = r._data; *(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; @@ -26435,8 +32297,8 @@ namespace cimg_library_suffixed { for (ptrd = _data; ptrd& fill(const char *const expression, const bool repeat_values, const bool allow_formula=true, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0) { - return _fill(expression,repeat_values,allow_formula,list_inputs,list_outputs,"fill",0); + CImgList *const list_images=0) { + return _fill(expression,repeat_values,allow_formula?3:1,list_images,"fill",0,0); } - CImg& _fill(const char *const expression, const bool repeat_values, bool allow_formula, - const CImgList *const list_inputs, CImgList *const list_outputs, - const char *const calling_function, const CImg *provides_copy) { + // bits of 'mode' can enable/disable these properties: + // . 1 = Allow list of values. + // . 2 = Allow formula. + // . 4 = Evaluate but does not fill image values. + CImg& _fill(const char *const expression, const bool repeat_values, const unsigned int mode, + CImgList *const list_images, const char *const calling_function, + const CImg *provides_copy, CImg *const result_end) { if (is_empty() || !expression || !*expression) return *this; - const unsigned int omode = cimg::exception_mode(); + const unsigned int excmode = cimg::exception_mode(); cimg::exception_mode(0); - CImg is_error; - bool is_value_sequence = false; + CImg is_error_expr; + bool is_done = false, is_value_sequence = false; cimg_abort_init; + if (result_end) result_end->assign(); - if (allow_formula) { - - // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser. + // Detect value sequence. + if (mode&1) { 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); + if (err==1) { if (mode&4) return *this; return fill((T)value); } else is_value_sequence = true; } + } - // Try to fill values according to a formula. - _cimg_abort_init_omp; - if (!is_value_sequence) try { - CImg 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,false); // Needs input copy + // Try to fill values according to a formula. + if (mode&2 && !is_value_sequence) { + _cimg_abort_init_openmp; + try { + CImg base = provides_copy?provides_copy->get_shared():get_shared(); + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || *expression=='+' || + *expression=='*' || *expression==':'), + calling_function,base,this,list_images,true); + if (!provides_copy && expression && + *expression!='>' && *expression!='<' && *expression!=':' && + mp.need_input_copy) + base.assign().assign(*this,false); // 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 res(1,mp.result_dim); - cimg_rofYZ(*this,y,z) { - cimg_abort_test; - cimg_rofX(*this,x) { + // Determine M2, smallest image dimension (used as axis for the most inner loop in parallelized iterations). + // M1 is the total number of parallelized iterations. + unsigned int M1 = 0, M2 = 0; + cimg::unused(M1,M2); + if (mp.result_dim) { + M2 = cimg::min(_width,_height,_depth); + M1 = M2==_width?_height*_depth:M2==_height?_width*_depth:_width*_height; + } else { + M2 = cimg::min(_width,_height,_depth,_spectrum); + M1 = M2==_width?_height*_depth*_spectrum:M2==_height?_width*_depth*_spectrum: + M2==_depth?_width*_height*_spectrum:_width*_height*_depth; + } + + bool is_parallelizable = false; + cimg_openmp_if(*expression=='*' || *expression==':' || (mp.is_parallelizable && + (M2>=2 || M1>=4096) && M1*M2>=32)) + is_parallelizable = true; + + 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 res(1,mp.result_dim); + mp.begin_t(); + cimg_rofYZ(*this,y,z) { + cimg_abort_test; + if (mode&4) cimg_rofX(*this,x) mp(x,y,z,0); + else 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 res(1,mp.result_dim); - cimg_forYZ(*this,y,z) { - cimg_abort_test; - cimg_forX(*this,x) { + } + mp.end_t(); + + } else if (*expression=='>' || *expression=='+' || !is_parallelizable) { + CImg res(1,mp.result_dim); + mp.begin_t(); + cimg_forYZ(*this,y,z) { + cimg_abort_test; + if (mode&4) cimg_forX(*this,x) mp(x,y,z,0); + else 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_omp { - cimg_abort_test; - CImg 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_omp _cimg_abort_catch_fill_omp - } -#endif } + mp.end_t(); - } 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) + } else { + +#if cimg_use_openmp!=0 + cimg_pragma_openmp(parallel) { _cimg_math_parser - _mp = omp_get_thread_num()?mp:_cimg_math_parser(), - &lmp = omp_get_thread_num()?_mp:mp; + *const _mp = omp_get_thread_num()?new _cimg_math_parser(mp):&mp, + &lmp = *_mp; lmp.is_fill = true; - cimg_pragma_openmp(for collapse(3)) - cimg_forYZC(*this,y,z,c) _cimg_abort_try_omp { - cimg_abort_test; - T *ptrd = data(0,y,z,c); - cimg_forX(*this,x) *ptrd++ = (T)lmp(x,y,z,c); - } _cimg_abort_catch_omp _cimg_abort_catch_fill_omp + cimg_pragma_openmp(barrier) + lmp.begin_t(); + +#define _cimg_fill_openmp_vector(_YZ,_y,_z,_X,_x,_sx,_sy,_sz,_off) \ + cimg_pragma_openmp(for cimg_openmp_collapse(2)) \ + cimg_for##_YZ(*this,_y,_z) _cimg_abort_try_openmp { \ + cimg_abort_test; \ + if (mode&4) cimg_for##_X(*this,_x) lmp(x,y,z,0); \ + else { \ + CImg res(1,lmp.result_dim); \ + T *__ptrd = data(_sx,_sy,_sz,0); \ + const ulongT off = (ulongT)_off; \ + cimg_for##_X(*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; } \ + __ptrd+=off; \ + } \ + } \ + } _cimg_abort_catch_openmp _cimg_abort_catch_fill_openmp + + if (M2==_width) { _cimg_fill_openmp_vector(YZ,y,z,X,x,0,y,z,1) } + else if (M2==_height) { _cimg_fill_openmp_vector(XZ,x,z,Y,y,x,0,z,_width) } + else { _cimg_fill_openmp_vector(XY,x,y,Z,z,x,y,0,_width*_height) } + + lmp.end_t(); + cimg_pragma_openmp(barrier) cimg_pragma_openmp(critical) { lmp.merge(mp); } + if (&lmp!=&mp) delete &lmp; } #endif - } } - mp.end(); - } catch (CImgException& e) { CImg::string(e._message).move_to(is_error); } + + } else { // Scalar-valued expression + T *ptrd = *expression=='<'?end() - 1:_data; + if (*expression=='<') { + mp.begin_t(); + if (mode&4) cimg_rofYZC(*this,y,z,c) { cimg_abort_test; cimg_rofX(*this,x) mp(x,y,z,c); } + else cimg_rofYZC(*this,y,z,c) { cimg_abort_test; cimg_rofX(*this,x) *(ptrd--) = (T)mp(x,y,z,c); } + mp.end_t(); + + } else if (*expression=='>' || *expression=='+' || !is_parallelizable) { + mp.begin_t(); + if (mode&4) cimg_forYZC(*this,y,z,c) { cimg_abort_test; cimg_forX(*this,x) mp(x,y,z,c); } + else cimg_forYZC(*this,y,z,c) { cimg_abort_test; cimg_forX(*this,x) *(ptrd++) = (T)mp(x,y,z,c); } + mp.end_t(); + + } else { + +#if cimg_use_openmp!=0 + cimg_pragma_openmp(parallel) + { + _cimg_math_parser + *const _mp = omp_get_thread_num()?new _cimg_math_parser(mp):&mp, + &lmp = *_mp; + lmp.is_fill = true; + cimg_pragma_openmp(barrier) + lmp.begin_t(); + +#define _cimg_fill_openmp_scalar(_YZC,_y,_z,_c,_X,_x,_sx,_sy,_sz,_sc,_off) \ + cimg_pragma_openmp(for cimg_openmp_collapse(3)) \ + cimg_for##_YZC(*this,_y,_z,_c) _cimg_abort_try_openmp { \ + cimg_abort_test; \ + if (mode&4) cimg_for##_X(*this,_x) lmp(x,y,z,c); \ + else { \ + T *_ptrd = data(_sx,_sy,_sz,_sc); \ + const ulongT off = (ulongT)_off; \ + cimg_for##_X(*this,_x) { *_ptrd = (T)lmp(x,y,z,c); _ptrd+=off; } \ + } \ + } _cimg_abort_catch_openmp _cimg_abort_catch_fill_openmp + + if (M2==_width) { _cimg_fill_openmp_scalar(YZC,y,z,c,X,x,0,y,z,c,1) } + else if (M2==_height) { _cimg_fill_openmp_scalar(XZC,x,z,c,Y,y,x,0,z,c,_width) } + else if (M2==_depth) { _cimg_fill_openmp_scalar(XYC,x,y,c,Z,z,x,y,0,c,_width*_height) } + else { _cimg_fill_openmp_scalar(XYZ,x,y,z,C,c,x,y,z,0,_width*_height*_depth) } + + lmp.end_t(); + cimg_pragma_openmp(barrier) cimg_pragma_openmp(critical) { lmp.merge(mp); } + if (&lmp!=&mp) delete &lmp; + } +#endif + } + } + mp.end(); + + if (result_end && mp.result_end) // Transfer result of the end() block if requested. + result_end->assign(mp.result_end + (mp.result_end_dim?1:0),std::max(1U,mp.result_end_dim)); + + is_done = true; + } catch (CImgException& e) { CImg::string(e._message).move_to(is_error_expr); } } // Try to fill values according to a value sequence. - if (!allow_formula || is_value_sequence || is_error) { - CImg item(256); - char sep = 0; - const char *nexpression = expression; - ulongT nb = 0; - const ulongT siz = size(); - T *ptrd = _data; - for (double val = 0; *nexpression && nb0 && 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 get_fill(const char *const expression, const bool repeat_values, const bool allow_formula=true, - const CImgList *const list_inputs=0, CImgList *const list_outputs=0) const { - return (+*this).fill(expression,repeat_values,allow_formula,list_inputs,list_outputs); + CImgList *const list_images=0) const { + return (+*this).fill(expression,repeat_values,allow_formula?1:0,list_images); + } + + //! Fill sequentially pixel values according to a value sequence, given as a string. + /** + \param values C-string describing a sequence of values. + \param repeat_values Tells if this sequence must be repeated when filling. + **/ + CImg& fill_from_values(const char *const values, const bool repeat_values) { + if (_fill_from_values(values,repeat_values)) + throw CImgArgumentException(_cimg_instance + "Invalid sequence of filling values '%s'.", + cimg_instance,values); + return *this; + } + + //! Fill sequentially pixel values according to a value sequence, given as a string \newinstance. + CImg get_fill_from_values(const char *const values, const bool repeat_values) const { + return (+*this).fill_from_values(values,repeat_values); + } + + // Fill image according to a value sequence, given as a string. + // Return 'true' if an error occured, 'false' otherwise. + bool _fill_from_values(const char *const values, const bool repeat_values) { + CImg item(256); + const char *nvalues = values; + const ulongT siz = size(); + T *ptrd = _data; + ulongT nb = 0; + char sep = 0; + for (double val = 0; *nvalues && nb0 && cimg_sscanf(item,"%lf",&val)==1 && (sep==',' || sep==';' || err==1)) { + nvalues+=std::strlen(item) + (err>1); + *(ptrd++) = (T)val; + } else break; + } + if (nb CImg& discard(const CImg& values, const char axis=0) { @@ -27127,8 +33092,8 @@ namespace cimg_library_suffixed { template CImg get_discard(const CImg& values, const char axis=0) const { - CImg res; if (!values) return +*this; + CImg res; if (is_empty()) return res; const ulongT vsiz = values.size(); const char _axis = cimg::lowercase(axis); @@ -27182,16 +33147,24 @@ namespace cimg_library_suffixed { 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=vsiz) { j = 0; i0 = (int)i + 1; }} + } + if ((ulongT)i0& rand(const T& val_min, const T& val_max) { const float delta = (float)val_max - (float)val_min + (cimg::type::is_float()?0:1); - if (cimg::type::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)); + if (cimg::type::is_float()) cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),524288)) { + cimg_uint64 rng = (cimg::_rand(),cimg::rng()); + +#if cimg_use_openmp!=0 + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) _data[off] = (T)(val_min + delta*cimg::rand(1,&rng)); + cimg::srand(rng); + } else cimg_pragma_openmp(parallel cimg_openmp_if_size(size(),524288)) { + cimg_uint64 rng = (cimg::_rand(),cimg::rng()); + +#if cimg_use_openmp!=0 + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_rofoff(*this,off) _data[off] = std::min(val_max,(T)(val_min + delta*cimg::rand(1,&rng))); + cimg::srand(rng); + } return *this; } @@ -27282,9 +33274,7 @@ namespace cimg_library_suffixed { - \c 1: Forward. **/ CImg& 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); + if (y>0) cimg_openmp_for(*this,cimg::round(*ptr,y,rounding_type),8192); return *this; } @@ -27317,22 +33307,40 @@ namespace cimg_library_suffixed { 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); + if (nsigma<0) nsigma = (Tfloat)(-nsigma*(M-m)/100.); 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 (valvmax) val = vmax; + if (valvmax) val = vmax; - if (valvmax) val = vmax; + if (val::is_float()) { --m; ++M; } else { m = (Tfloat)cimg::type::min(); M = (Tfloat)cimg::type::max(); } } - cimg_rof(*this,ptrd,T) if (cimg::rand(100)vmax) val = vmax; - if (valvmax) val = vmax; + if (val img("reference.jpg"), res = img.get_normalize(160,220); @@ -27385,19 +33423,24 @@ namespace cimg_library_suffixed { \endcode \image html ref_normalize2.jpg **/ - CImg& normalize(const T& min_value, const T& max_value) { + CImg& normalize(const T& min_value, const T& max_value, + const float constant_case_ratio=0) { if (is_empty()) return *this; const T a = min_value get_normalize(const T& min_value, const T& max_value) const { - return CImg(*this,false).normalize((Tfloat)min_value,(Tfloat)max_value); + CImg get_normalize(const T& min_value, const T& max_value, + const float ratio_if_constant_image=0) const { + return CImg(*this,false).normalize((Tfloat)min_value,(Tfloat)max_value,ratio_if_constant_image); } //! Normalize multi-valued pixels of the image instance, with respect to their L2-norm. @@ -27411,7 +33454,8 @@ namespace cimg_library_suffixed { **/ CImg& normalize() { const ulongT whd = (ulongT)_width*_height*_depth; - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth>=16)) cimg_forYZ(*this,y,z) { T *ptrd = data(0,y,z,0); cimg_forX(*this,x) { @@ -27454,8 +33498,9 @@ namespace cimg_library_suffixed { const ulongT whd = (ulongT)_width*_height*_depth; CImg 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)) + case -1 : { // Linf-norm + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth>=16)) cimg_forYZ(*this,y,z) { const ulongT off = (ulongT)offset(0,y,z); const T *ptrs = _data + off; @@ -27468,8 +33513,9 @@ namespace cimg_library_suffixed { } } } break; - case 0 : { // L0-norm. - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) + case 0 : { // L0-norm + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth>=16)) cimg_forYZ(*this,y,z) { const ulongT off = (ulongT)offset(0,y,z); const T *ptrs = _data + off; @@ -27482,8 +33528,9 @@ namespace cimg_library_suffixed { } } } break; - case 1 : { // L1-norm. - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) + case 1 : { // L1-norm + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth>=16)) cimg_forYZ(*this,y,z) { const ulongT off = (ulongT)offset(0,y,z); const T *ptrs = _data + off; @@ -27496,8 +33543,9 @@ namespace cimg_library_suffixed { } } } break; - case 2 : { // L2-norm. - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) + case 2 : { // L2-norm + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth>=16)) cimg_forYZ(*this,y,z) { const ulongT off = (ulongT)offset(0,y,z); const T *ptrs = _data + off; @@ -27510,8 +33558,9 @@ namespace cimg_library_suffixed { } } } break; - default : { // Linf-norm. - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=512 && _height*_depth>=16)) + default : { // Linf-norm + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth>=16)) cimg_forYZ(*this,y,z) { const ulongT off = (ulongT)offset(0,y,z); const T *ptrs = _data + off; @@ -27542,8 +33591,7 @@ namespace cimg_library_suffixed { CImg& cut(const T& min_value, const T& max_value) { if (is_empty()) return *this; const T a = min_value=32768)) - cimg_rof(*this,ptrd,T) *ptrd = (*ptrdb)?b:*ptrd); + cimg_openmp_for(*this,cimg::cut(*ptr,a,b),32768); return *this; } @@ -27573,15 +33621,15 @@ namespace cimg_library_suffixed { 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); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32768)) + cimg_rofoff(*this,off) { + const unsigned int val = (unsigned int)((_data[off] - m)*nb_levels/range); + _data[off] = (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); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32768)) + cimg_rofoff(*this,off) { + const unsigned int val = (unsigned int)((_data[off] - m)*nb_levels/range); + _data[off] = (T)std::min(val,nb_levels - 1); } } return *this; @@ -27592,6 +33640,33 @@ namespace cimg_library_suffixed { return (+*this).quantize(n,keep_range); } + //! Return the Otsu threshold. + /** + \param nb_levels Number of histogram levels used for the estimation. + **/ + T otsu(const unsigned int nb_levels=256) const { + T m,M = max_min(m); + CImg hist = get_histogram(nb_levels,m,M); + ulongT sum = 0, sumB = 0, wB = 0; + double best_variance = 0; + unsigned int best_t = 0; + cimg_forX(hist,t) sum+=t*hist[t]; + cimg_forX(hist,t) { + wB+=hist[t]; + if (wB) { + const ulongT wF = size() - wB; + if (!wF) break; + sumB+=t*hist[t]; + const double + mB = (double)sumB/wB, + mF = (double)(sum - sumB)/wF, + variance = wB*wF*cimg::sqr(mB - mF); + if (variance>best_variance) { best_variance = variance; best_t = t; } + } + } + return m + best_t*(M - m)/(hist.width() - 1); + } + //! Threshold pixel values. /** \param value Threshold value @@ -27608,24 +33683,24 @@ namespace cimg_library_suffixed { 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; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32768)) + cimg_rofoff(*this,off) { + const T v = _data[off]; + _data[off] = 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; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),65536)) + cimg_rofoff(*this,off) _data[off] = _data[off]>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; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32768)) + cimg_rofoff(*this,off) { + const T v = _data[off]; + _data[off] = 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; + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),65536)) + cimg_rofoff(*this,off) _data[off] = _data[off]>=value?(T)1:(T)0; } return *this; } @@ -27643,9 +33718,9 @@ namespace cimg_library_suffixed { \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 + - The histogram H of an image I is the 1D function where H(x) counts the number of occurrences 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. + - The resulting histogram is always defined in 1D. Histograms of multi-valued images are not multi-dimensional. \par Example \code const CImg img = CImg("reference.jpg").histogram(256); @@ -27706,10 +33781,10 @@ namespace cimg_library_suffixed { 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); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),1048576)) + cimg_rofoff(*this,off) { + const int pos = (int)((_data[off] - vmin)*(nb_levels - 1.)/(vmax - vmin)); + if (pos>=0 && pos<(int)nb_levels) _data[off] = (T)(vmin + (vmax - vmin)*hist[pos]/cumul); } return *this; } @@ -27731,7 +33806,7 @@ namespace cimg_library_suffixed { return (+*this).equalize(nblevels); } - //! Index multi-valued pixels regarding to a specified colormap. + //! Index multi-valued pixels regarding to a specified palette. /** \param colormap Multi-valued colormap used as the basis for multi-valued pixel indexing. \param dithering Level of dithering (0=disable, 1=standard level). @@ -27768,8 +33843,8 @@ namespace cimg_library_suffixed { whd = (ulongT)_width*_height*_depth, pwhd = (ulongT)colormap._width*colormap._height*colormap._depth; CImg res(_width,_height,_depth,map_indexes?_spectrum:1); - tuint *ptrd = res._data; - if (dithering>0) { // Dithered versions. + if (dithering>0) { // Dithered versions + tuint *ptrd = res._data; 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; } @@ -27777,7 +33852,7 @@ namespace cimg_library_suffixed { 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. + case 1 : { // Optimized for scalars cimg_forYZ(*this,y,z) { if (y=64 && _height*_depth>=16 && pwhd>=16)) + case 1 : { // Optimized for scalars + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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=64 && _height*_depth>=16 && pwhd>=16)) + case 2 : { // Optimized for 2D vectors + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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=64 && _height*_depth>=16 && pwhd>=16)) + case 3 : { // Optimized for 3D vectors (colors) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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, @@ -27976,8 +34054,9 @@ namespace cimg_library_suffixed { } } } break; - default : // Generic version. - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=64 && _height*_depth>=16 && pwhd>=16)) + default : // Generic version + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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 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); + palette1(3,1,1,3, 0,128,255, 0,128,255, 0,128,255), + palette2(3,1,1,3, 255,0,0, 0,255,0, 0,0,255), + res = img.get_index(palette1,0).map(palette2); (img,res).display(); \endcode \image html ref_map.jpg **/ template - CImg& map(const CImg& colormap, const unsigned int boundary_conditions=0) { - return get_map(colormap,boundary_conditions).move_to(*this); + CImg& map(const CImg& palette, const unsigned int boundary_conditions=0) { + return get_map(palette,boundary_conditions).move_to(*this); } - //! Map predefined colormap on the scalar (indexed) image instance \newinstance. + //! Map predefined palette on the scalar (indexed) image instance \newinstance. template - CImg get_map(const CImg& 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); - + CImg get_map(const CImg& palette, const unsigned int boundary_conditions=0) const { const ulongT - whd = (ulongT)_width*_height*_depth, - cwhd = (ulongT)colormap._width*colormap._height*colormap._depth, + whd = (ulongT)_width*_height*_depth, siz = size(), + cwhd = (ulongT)palette._width*palette._height*palette._depth, cwhd2 = 2*cwhd; - CImg res(_width,_height,_depth,colormap._spectrum==1?_spectrum:colormap._spectrum); - switch (colormap._spectrum) { + CImg res(_width,_height,_depth,_spectrum*palette._spectrum); + switch (palette._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& label(const bool is_high_connectivity=false, const Tfloat tolerance=0) { - return get_label(is_high_connectivity,tolerance).move_to(*this); + CImg& label(const bool is_high_connectivity=false, const Tfloat tolerance=0, + const bool is_L2_norm=true) { + if (is_empty()) return *this; + return get_label(is_high_connectivity,tolerance,is_L2_norm).move_to(*this); } //! Label connected components \newinstance. - CImg get_label(const bool is_high_connectivity=false, - const Tfloat tolerance=0) const { + CImg get_label(const bool is_high_connectivity=false, const Tfloat tolerance=0, + const bool is_L2_norm=true) const { if (is_empty()) return CImg(); // Create neighborhood tables. @@ -28193,7 +34328,7 @@ namespace cimg_library_suffixed { dx[nb] = 1; dy[nb] = 1; dz[nb++] = 0; dx[nb] = 1; dy[nb] = -1; dz[nb++] = 0; } - if (_depth>1) { // 3d version. + 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; @@ -28207,23 +34342,27 @@ namespace cimg_library_suffixed { dx[nb] = 1; dy[nb] = 1; dz[nb++] = 1; } } - return _label(nb,dx,dy,dz,tolerance); + return _label(nb,dx,dy,dz,tolerance,is_L2_norm); } //! 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. + \param is_L2_norm If true, tolerance is compared against L2 difference, otherwise L1 is used. **/ template - CImg& label(const CImg& connectivity_mask, const Tfloat tolerance=0) { - return get_label(connectivity_mask,tolerance).move_to(*this); + CImg& label(const CImg& connectivity_mask, const Tfloat tolerance=0, + const bool is_L2_norm=true) { + if (is_empty()) return *this; + return get_label(connectivity_mask,tolerance,is_L2_norm).move_to(*this); } //! Label connected components \newinstance. template - CImg get_label(const CImg& connectivity_mask, - const Tfloat tolerance=0) const { + CImg get_label(const CImg& connectivity_mask, const Tfloat tolerance=0, + const bool is_L2_norm=true) const { + if (is_empty()) return CImg(); int nb = 0; cimg_for(connectivity_mask,ptr,t) if (*ptr) ++nb; CImg dx(nb,1,1,1,0), dy(nb,1,1,1,0), dz(nb,1,1,1,0); @@ -28232,67 +34371,110 @@ namespace cimg_library_suffixed { connectivity_mask(x,y,z)) { dx[nb] = x; dy[nb] = y; dz[nb++] = z; } - return _label(nb,dx,dy,dz,tolerance); + return _label(nb,dx,dy,dz,tolerance,is_L2_norm); } CImg _label(const unsigned int nb, const int *const dx, const int *const dy, const int *const dz, - const Tfloat tolerance) const { - CImg res(_width,_height,_depth,_spectrum); - cimg_forC(*this,c) { - CImg _res = res.get_shared_channel(c); + const Tfloat tolerance, const bool is_L2_norm) const { + CImg res(_width,_height,_depth); + const Tfloat _tolerance = _spectrum>1 && is_L2_norm?cimg::sqr(tolerance):tolerance; - // Init label numbers. - ulongT *ptr = _res.data(); - cimg_foroff(_res,p) *(ptr++) = p; + // Init label numbers. + ulongT *ptr = res.data(); + cimg_foroff(res,p) *(ptr++) = p; - // For each neighbour-direction, label. - for (unsigned int n = 0; n& default_LUT256() { - static CImg colormap; + static CImg palette; cimg::mutex(8); - if (!colormap) { - colormap.assign(1,256,1,3); + if (!palette) { + palette.assign(1,256,1,3); for (unsigned int index = 0, r = 16; r<256; r+=32) for (unsigned int g = 16; g<256; g+=32) for (unsigned int b = 32; b<256; b+=64) { - colormap(0,index,0) = (Tuchar)r; - colormap(0,index,1) = (Tuchar)g; - colormap(0,index++,2) = (Tuchar)b; + palette(0,index,0) = (Tuchar)r; + palette(0,index,1) = (Tuchar)g; + palette(0,index++,2) = (Tuchar)b; } } cimg::mutex(8,0); - return colormap; + return palette; } - //! Return colormap \e "HSV", containing 256 colors entries in RGB. + //! Return palette \e "HSV", containing 256 colors entries in RGB. /** - \return The following \c 256x1x1x3 colormap is returned: + \return The following \c 256x1x1x3 palette is returned: \image html ref_colormap_hsv.jpg **/ static const CImg& HSV_LUT256() { - static CImg colormap; + static CImg palette; cimg::mutex(8); - if (!colormap) { + if (!palette) { CImg tmp(1,256,1,3,1); tmp.get_shared_channel(0).sequence(0,359); - colormap = tmp.HSVtoRGB(); + palette = tmp.HSVtoRGB(); } cimg::mutex(8,0); - return colormap; + return palette; } - //! Return colormap \e "lines", containing 256 colors entries in RGB. + //! Return palette \e "lines", containing 256 colors entries in RGB. /** - \return The following \c 256x1x1x3 colormap is returned: + \return The following \c 256x1x1x3 palette is returned: \image html ref_colormap_lines.jpg **/ static const CImg& 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 colormap(pal,1,256,1,3,false); - return colormap; + 0,255,255,0,0,28,125,125,235,210,186,182,36,0,125,255, + 53,32,255,210,89,186,65,45,125,210,210,97,130,194,0,125, + 206,53,190,89,255,146,20,190,154,73,255,36,130,215,0,138, + 101,210,61,194,206,0,77,45,255,154,174,0,190,239,89,125, + 16,36,158,223,117,0,97,69,223,255,40,239,0,0,255,0, + 97,170,93,255,138,40,117,210,0,170,53,158,186,255,0,121, + 227,121,186,40,20,190,89,255,77,57,130,142,255,73,186,85, + 210,8,32,166,243,130,210,40,255,45,61,142,223,49,121,255, + 20,162,158,73,89,255,53,138,210,190,57,235,36,73,255,49, + 210,0,210,85,57,97,255,121,85,174,40,255,162,178,0,121, + 166,125,53,146,166,255,97,121,65,89,235,231,12,170,36,190, + 85,255,166,97,198,77,20,146,109,166,255,28,40,202,121,81, + 247,0,210,255,49,0,65,255,36,166,93,77,255,85,251,0, + 170,178,0,182,255,0,162,16,154,142,162,223,223,0,0,81, + 215,4,215,162,215,125,77,206,121,36,125,231,101,16,255,121, + 0,57,190,215,65,125,89,142,255,101,73,53,146,223,125,125, + 0,255,0,255,0,206,93,138,49,255,0,202,154,85,45,219, + 251,53,0,255,40,130,219,158,16,117,186,130,202,49,65,239, + 89,202,49,28,247,134,150,0,255,117,202,4,215,81,186,57, + 202,89,73,210,40,93,45,251,206,28,223,142,40,134,162,125, + 32,247,97,170,0,255,57,134,73,247,162,0,251,40,142,142, + 8,166,206,81,154,194,93,89,125,243,28,109,227,0,190,65, + 194,186,0,255,53,45,109,186,186,0,255,130,49,170,69,210, + 154,0,109,227,45,255,125,105,81,81,255,0,219,134,170,85, + 146,28,170,89,223,97,8,210,255,158,49,40,125,174,174,125, + 0,227,166,28,219,130,0,93,239,0,85,255,81,178,125,49, + 89,255,53,206,73,113,146,255,0,150,36,219,162,0,210,125, + 69,134,255,85,40,89,235,49,215,121,0,206,36,223,174,69, + 40,182,178,130,69,45,255,210,85,77,215,0,231,146,0,194, + 125,174,0,255,40,89,121,206,57,0,206,170,231,150,81,0, + 125,255,4,174,4,190,121,255,4,166,109,130,49,239,170,93, + 16,174,210,0,255,16,105,158,93,255,0,125,0,255,158,85, + 0,255,0,0,255,170,166,61,121,28,198,215,45,243,61,97, + 255,53,81,130,109,255,8,117,235,121,40,178,174,0,182,49, + 162,121,255,69,206,0,219,125,0,101,255,239,121,32,210,130, + 36,231,32,125,81,142,215,158,4,178,255,0,40,251,125,125, + 219,89,130,0,166,255,24,65,194,125,255,125,77,125,93,125, + 202,24,138,174,178,32,255,85,194,40,85,36,174,174,125,210, + 85,255,53,16,93,206,40,130,170,202,93,255,0,24,117,255, + 97,113,105,81,255,186,194,57,69,206,57,53,223,190,4,255, + 85,97,130,255,85,0,125,223,85,219,0,215,146,77,40,239, + 89,36,142,154,227,0,255,85,162,0,162,0,235,178,45,166, + 0,247,255,20,69,210,89,142,53,255,40,146,166,255,69,0, + 174,154,142,130,162,0,215,255,0,89,40,255,166,61,146,69, + 162,40,255,32,121,255,117,178,0,186,206,0,57,215,215,81, + 158,77,166,210,77,89,210,0,24,202,150,186,0,255,20,97, + 57,170,235,251,16,73,142,251,93,0,202,0,255,121,219,4, + 73,219,8,162,206,16,219,93,117,0,255,8,130,174,223,45 }; + static const CImg palette(pal,1,256,1,3,false); + return palette; } - //! Return colormap \e "hot", containing 256 colors entries in RGB. + //! Return palette \e "hot", containing 256 colors entries in RGB. /** - \return The following \c 256x1x1x3 colormap is returned: + \return The following \c 256x1x1x3 palette is returned: \image html ref_colormap_hot.jpg **/ static const CImg& hot_LUT256() { - static CImg colormap; + static CImg palette; 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); + if (!palette) { + palette.assign(1,4,1,3,(T)0); + palette[1] = palette[2] = palette[3] = palette[6] = palette[7] = palette[11] = 255; + palette.resize(1,256,1,3,3); } cimg::mutex(8,0); - return colormap; + return palette; } - //! Return colormap \e "cool", containing 256 colors entries in RGB. + //! Return palette \e "cool", containing 256 colors entries in RGB. /** - \return The following \c 256x1x1x3 colormap is returned: + \return The following \c 256x1x1x3 palette is returned: \image html ref_colormap_cool.jpg **/ static const CImg& cool_LUT256() { - static CImg colormap; + static CImg palette; 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); + if (!palette) palette.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 palette; } - //! Return colormap \e "jet", containing 256 colors entries in RGB. + //! Return palette \e "jet", containing 256 colors entries in RGB. /** - \return The following \c 256x1x1x3 colormap is returned: + \return The following \c 256x1x1x3 palette is returned: \image html ref_colormap_jet.jpg **/ static const CImg& jet_LUT256() { - static CImg colormap; + static CImg palette; 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); + if (!palette) { + palette.assign(1,4,1,3,(T)0); + palette[2] = palette[3] = palette[5] = palette[6] = palette[8] = palette[9] = 255; + palette.resize(1,256,1,3,3); } cimg::mutex(8,0); - return colormap; + return palette; } - //! Return colormap \e "flag", containing 256 colors entries in RGB. + //! Return palette \e "flag", containing 256 colors entries in RGB. /** \return The following \c 256x1x1x3 colormap is returned: - \image html ref_colormap_flag.jpg + \image html ref_palette_flag.jpg **/ static const CImg& flag_LUT256() { - static CImg colormap; + static CImg palette; 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); + if (!palette) { + palette.assign(1,4,1,3,(T)0); + palette[0] = palette[1] = palette[5] = palette[9] = palette[10] = 255; + palette.resize(1,256,1,3,0,2); } cimg::mutex(8,0); - return colormap; + return palette; } - //! Return colormap \e "cube", containing 256 colors entries in RGB. + //! Return palette \e "cube", containing 256 colors entries in RGB. /** - \return The following \c 256x1x1x3 colormap is returned: + \return The following \c 256x1x1x3 palette is returned: \image html ref_colormap_cube.jpg **/ static const CImg& cube_LUT256() { - static CImg colormap; + static CImg palette; 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); + if (!palette) { + palette.assign(1,8,1,3,(T)0); + palette[1] = palette[3] = palette[5] = palette[7] = + palette[10] = palette[11] = palette[12] = palette[13] = + palette[20] = palette[21] = palette[22] = palette[23] = 255; + palette.resize(1,256,1,3,3); } cimg::mutex(8,0); - return colormap; + return palette; } //! Convert pixel values from sRGB to RGB color spaces. CImg& sRGBtoRGB() { if (is_empty()) return *this; - cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32)) - cimg_rof(*this,ptr,T) { + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32)) + cimg_rofoff(*this,off) { const Tfloat - sval = (Tfloat)*ptr/255, + sval = (Tfloat)_data[off]/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); + _data[off] = (T)cimg::cut(val*255,0,255); } return *this; } @@ -28499,12 +34705,12 @@ namespace cimg_library_suffixed { //! Convert pixel values from RGB to sRGB color spaces. CImg& RGBtosRGB() { if (is_empty()) return *this; - cimg_pragma_openmp(parallel for cimg_openmp_if(size()>=32)) - cimg_rof(*this,ptr,T) { + cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32)) + cimg_rofoff(*this,off) { const Tfloat - val = (Tfloat)*ptr/255, + val = (Tfloat)_data[off]/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); + _data[off] = (T)cimg::cut(sval*255,0,255); } return *this; } @@ -28522,24 +34728,23 @@ namespace cimg_library_suffixed { 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; N0) 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); + M = cimg::max(R,G,B), + C = M - m, + sum = R + G + B, + H = 60*(C==0?0:M==R?cimg::mod((G - B)/C,(Tfloat)6):M==G?(B - R)/C + 2:(R - G)/C + 4), + S = sum<=0?0:1 - 3*m/sum, + I = sum/(3*255); + p1[N] = (T)H; + p2[N] = (T)S; + p3[N] = (T)I; } return *this; } @@ -28557,33 +34762,29 @@ namespace cimg_library_suffixed { 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=256)) - for (ulongT N = 0; N=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); + C = M - m, + H = 60*(C==0?0:M==R?cimg::mod((G - B)/C,(Tfloat)6):M==G?(B - R)/C + 2:(R - G)/C + 4), + L = 0.5f*(m + M)/255, + S = L==1 || L==0?0:C/(1 - cimg::abs(2*L - 1))/255; + p1[N] = (T)H; + p2[N] = (T)S; + p3[N] = (T)L; } return *this; } @@ -28641,28 +34835,28 @@ namespace cimg_library_suffixed { 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; N1?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); + C = (1 - cimg::abs(2*L - 1))*S, + X = C*(1 - cimg::abs(cimg::mod(H,(Tfloat)2) - 1)), + m = L - C/2; + Tfloat R, G, B; + switch ((int)H) { + case 0 : R = C; G = X; B = 0; break; + case 1 : R = X; G = C; B = 0; break; + case 2 : R = 0; G = C; B = X; break; + case 3 : R = 0; G = X; B = C; break; + case 4 : R = X; G = 0; B = C; break; + default : R = C; G = 0; B = X; + } + p1[N] = (T)((R + m)*255); + p2[N] = (T)((G + m)*255); + p3[N] = (T)((B + m)*255); } return *this; } @@ -28680,28 +34874,20 @@ namespace cimg_library_suffixed { 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=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); + M = cimg::max(R,G,B), + C = M - cimg::min(R,G,B), + H = 60*(C==0?0:M==R?cimg::mod((G-B)/C,(Tfloat)6):M==G?(B - R)/C + 2:(R - G)/C + 4), + S = M<=0?0:C/M; + p1[N] = (T)H; + p2[N] = (T)S; + p3[N] = (T)(M/255); } return *this; } @@ -28719,35 +34905,28 @@ namespace cimg_library_suffixed { 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=512)) - for (ulongT N = 0; N=512)) - for (ulongT N = 0; N=16384)) - for (ulongT N = 0; N=16384)) - for (ulongT N = 0; N=2048)) - for (ulongT N = 0; N=2048)) - for (ulongT N = 0; N 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=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; } + 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), @@ -28983,15 +35162,15 @@ namespace cimg_library_suffixed { CImg 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=2048)) - for (ulongT N = 0; N=2048)) - for (ulongT N = 0; N white = CImg(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 white = CImg(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=4096)) - for (ulongT N = 0; N=4096)) - for (ulongT N = 0; N=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),1024*1024)) cimg_forXYZC(res,x,y,z,c) { const int mx = cimg::mod(x - xc,w2), my = cimg::mod(y - yc,h2), @@ -29375,7 +35554,7 @@ namespace cimg_library_suffixed { 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)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),1024*1024)) 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) @@ -29435,13 +35614,14 @@ namespace cimg_library_suffixed { wh = (ulongT)_width*_height, whd = (ulongT)_width*_height*_depth, sxy = (ulongT)sx*sy, - sxyz = (ulongT)sx*sy*sz; + sxyz = (ulongT)sx*sy*sz, + one = (ulongT)1; 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); + curr = (x + one)*_width/sx; *(poff_x++) = curr - old; } } @@ -29450,7 +35630,7 @@ namespace cimg_library_suffixed { ulongT *poff_y = off_y._data, curr = 0; cimg_forY(res,y) { const ulongT old = curr; - curr = (ulongT)((y + 1.0)*_height/sy); + curr = (y + one)*_height/sy; *(poff_y++) = _width*(curr - old); } *poff_y = 0; @@ -29460,7 +35640,7 @@ namespace cimg_library_suffixed { ulongT *poff_z = off_z._data, curr = 0; cimg_forZ(res,z) { const ulongT old = curr; - curr = (ulongT)((z + 1.0)*_depth/sz); + curr = (z + one)*_depth/sz; *(poff_z++) = wh*(curr - old); } *poff_z = 0; @@ -29470,7 +35650,7 @@ namespace cimg_library_suffixed { ulongT *poff_c = off_c._data, curr = 0; cimg_forC(res,c) { const ulongT old = curr; - curr = (ulongT)((c + 1.0)*_spectrum/sc); + curr = (c + one)*_spectrum/sc; *(poff_c++) = whd*(curr - old); } *poff_c = 0; @@ -29496,12 +35676,12 @@ namespace cimg_library_suffixed { } ++z; ulongT dz = *(poff_z++); - for ( ; !dz && z 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 (sx>_width) get_resize(sx,_height,_depth,_spectrum,1).move_to(res); + else { + CImg tmp(sx,_height,_depth,_spectrum,0); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(sx>=256 && _height*_depth*_spectrum>=256)) + cimg_forYZC(tmp,y,z,v) { + 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; + tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d; + if (!b) { tmp(t++,y,z,v)/=_width; b = _width; } + if (!c) { ++s; c = sx; } + } } - if (!c) { ++s; c = sx; } + tmp.move_to(res); } - tmp.move_to(res); instance_first = false; } + if (sy!=_height) { - CImg 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 (sy>_height) get_resize(sx,sy,_depth,_spectrum,1).move_to(res); + else { + CImg tmp(sx,sy,_depth,_spectrum,0); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(sy>=256 && _width*_depth*_spectrum>=256)) + cimg_forXZC(tmp,x,z,v) { + 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) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d; + else tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d; + if (!b) { tmp(x,t++,z,v)/=_height; b = _height; } + if (!c) { ++s; c = sy; } + } } - if (!c) { ++s; c = sy; } + tmp.move_to(res); } - tmp.move_to(res); instance_first = false; } + if (sz!=_depth) { - CImg 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 (sz>_depth) get_resize(sx,sy,sz,_spectrum,1).move_to(res); + else { + CImg tmp(sx,sy,sz,_spectrum,0); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(sz>=256 && _width*_height*_spectrum>=256)) + cimg_forXYC(tmp,x,y,v) { + 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) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d; + else tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d; + if (!b) { tmp(x,y,t++,v)/=_depth; b = _depth; } + if (!c) { ++s; c = sz; } + } } - if (!c) { ++s; c = sz; } + tmp.move_to(res); } - tmp.move_to(res); instance_first = false; } + if (sc!=_spectrum) { - CImg 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 (sc>_spectrum) get_resize(sx,sy,sz,sc,1).move_to(res); + else { + CImg tmp(sx,sy,sz,sc,0); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(sc>=256 && _width*_height*_depth>=256)) + cimg_forXYZ(tmp,x,y,z) { + 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) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d; + else tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d; + if (!b) { tmp(x,y,z,t++)/=_spectrum; b = _spectrum; } + if (!c) { ++s; c = sc; } + } } - if (!c) { ++s; c = sc; } + tmp.move_to(res); } - tmp.move_to(res); instance_first = false; } + } break; // Linear interpolation. @@ -29597,19 +35787,22 @@ namespace cimg_library_suffixed { 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): + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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; + { + 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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resx._width>=256 && resx._height*resx._depth*resx._spectrum>=256)) 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); @@ -29630,19 +35823,22 @@ namespace cimg_library_suffixed { 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): + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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); + { + 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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resy._height>=256 && resy._width*resy._depth*resy._spectrum>=256)) 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); @@ -29666,20 +35862,23 @@ namespace cimg_library_suffixed { 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): + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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); + { + 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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resz._depth>=256 && resz._width*resz._height*resz._spectrum>=256)) 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); @@ -29703,20 +35902,23 @@ namespace cimg_library_suffixed { 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): + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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); + { + 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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resc._spectrum>=256 && resc._width*resc._height*resc._depth>=256)) 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); @@ -29817,19 +36019,22 @@ namespace cimg_library_suffixed { 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): + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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; + { + 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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resx._width>=256 && resx._height*resx._depth*resx._spectrum>=256)) 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); @@ -29857,19 +36062,22 @@ namespace cimg_library_suffixed { 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): + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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); + { + 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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resy._height>=256 && resy._width*resy._depth*resy._spectrum>=256)) 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); @@ -29899,20 +36107,23 @@ namespace cimg_library_suffixed { 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): + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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); + { + 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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resz._depth>=256 && resz._width*resz._height*resz._spectrum>=256)) 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); @@ -29942,20 +36153,23 @@ namespace cimg_library_suffixed { 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): + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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); + { + 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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resc._spectrum>=256 && resc._width*resc._height*resc._depth>=256)) 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); @@ -29997,19 +36211,22 @@ namespace cimg_library_suffixed { 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): + const double fx = (!boundary_conditions && sx>_width)?(sx>1?(_width - 1.)/(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; + { + 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.,curr + fx); + *(poff++) = (unsigned int)curr - (unsigned int)old; + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resx.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resx._width>=256 && resx._height*resx._depth*resx._spectrum>=256)) 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); @@ -30043,19 +36260,22 @@ namespace cimg_library_suffixed { 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): + const double fy = (!boundary_conditions && sy>_height)?(sy>1?(_height - 1.)/(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); + { + 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.,curr + fy); + *(poff++) = sx*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resy.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resy._height>=256 && resy._width*resy._depth*resy._spectrum>=256)) 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; @@ -30091,20 +36311,23 @@ namespace cimg_library_suffixed { 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): + const double fz = (!boundary_conditions && sz>_depth)?(sz>1?(_depth - 1.)/(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); + { + 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.,curr + fz); + *(poff++) = sxy*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resz.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resz._depth>=256 && resz._width*resz._height*resz._spectrum>=256)) 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; @@ -30140,20 +36363,23 @@ namespace cimg_library_suffixed { 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): + const double fc = (!boundary_conditions && sc>_spectrum)?(sc>1?(_spectrum - 1.)/(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); + { + 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.,curr + fc); + *(poff++) = sxyz*((unsigned int)curr - (unsigned int)old); + } } - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(resc.size()>=65536)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(resc._spectrum>=256 && resc._width*resc._height*resc._depth>=256)) 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; @@ -30187,7 +36413,7 @@ namespace cimg_library_suffixed { return resc._is_shared?(resz._is_shared?(resy._is_shared?(resx._is_shared?(+(*this)):resx):resy):resz):resc; } break; - // Unknow interpolation. + // Unknown interpolation. // default : throw CImgArgumentException(_cimg_instance @@ -30205,6 +36431,7 @@ namespace cimg_library_suffixed { \param src Reference image used for dimensions. \param interpolation_type Interpolation method. \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \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). @@ -30234,6 +36461,7 @@ namespace cimg_library_suffixed { \param disp Reference display window used for dimensions. \param interpolation_type Interpolation method. \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \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). @@ -30300,7 +36528,7 @@ namespace cimg_library_suffixed { _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)), \ + (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)), \ @@ -30354,7 +36582,7 @@ namespace cimg_library_suffixed { _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)), \ + (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)), \ @@ -30492,7 +36720,8 @@ namespace cimg_library_suffixed { \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 }. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. **/ CImg& 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) { @@ -30703,237 +36932,300 @@ namespace cimg_library_suffixed { //! Permute axes order. /** - \param order Axes permutations, as a C-string of 4 characters. + \param axes_order Axes permutations, as a C-string of 4 characters. This function permutes image content regarding the specified axes permutation. **/ - CImg& permute_axes(const char *const order) { - return get_permute_axes(order).move_to(*this); + CImg& permute_axes(const char *const axes_order) { + if (is_empty() || !axes_order) return *this; + const unsigned uicase = _permute_axes_uicase(axes_order); + if (_permute_axes_is_optim(uicase)) { // Data layout allow to do nothing but set the new dimensions + CImg res(*this,true); + for (unsigned int i = 0; i<4; ++i) { + const unsigned int + axis = (uicase>>(4*(3 - i)))&15, + value = !axis?_width:axis==1?_height:axis==2?_depth:_spectrum; + if (!i) res._width = value; else if (i==1) res._height = value; + else if (i==2) res._depth = value; else res._spectrum = value; + } + _width = res._width; _height = res._height; _depth = res._depth; _spectrum = res._spectrum; + return *this; + } + return get_permute_axes(axes_order).move_to(*this); } //! Permute axes order \newinstance. - CImg get_permute_axes(const char *const order) const { + CImg get_permute_axes(const char *const axes_order) const { const T foo = (T)0; - return _permute_axes(order,foo); + return _permute_axes(axes_order,foo); + } + + unsigned int _permute_axes_uicase(const char *const axes_order) const { // Convert axes to integer case number + unsigned char s_axes[4] = { 0,1,2,3 }, n_axes[4] = { }; + bool is_error = false; + if (axes_order) for (unsigned int l = 0; axes_order[l]; ++l) { + int c = cimg::lowercase(axes_order[l]); + if (l>=4 || (c!='x' && c!='y' && c!='z' && c!='c')) { is_error = true; break; } + else { ++n_axes[c%=4]; s_axes[l] = (unsigned char)c; } + } + is_error|=(*n_axes>1) || (n_axes[1]>1) || (n_axes[2]>1) || (n_axes[3]>1); + if (is_error) + throw CImgArgumentException(_cimg_instance + "permute_axes(): Invalid specified axes order '%s'.", + cimg_instance, + axes_order); + return (s_axes[0]<<12) | (s_axes[1]<<8) | (s_axes[2]<<4) | (s_axes[3]); + } + + bool _permute_axes_is_optim(const unsigned int uicase) const { // Determine cases where nothing has to be done + const unsigned int co = ((_width>1)<<3)|((_height>1)<<2)|((_depth>1)<<1)|(_spectrum>1); + if (co<=2 || uicase==0x0123) return true; + switch (uicase) { + case (0x0132) : if ((co>=4 && co<=6) || (co>=8 && co<=10) || (co>=12 && co<=14)) return true; break; + case (0x0213) : if ((co>=3 && co<=5) || (co>=8 && co<=13)) return true; break; + case (0x0231) : if (co==3 || co==4 || (co>=8 && co<=12)) return true; break; + case (0x0312) : if (co==4 || co==6 || co==8 || co==9 || co==10 || co==12 || co==14) return true; break; + case (0x0321) : if (co==4 || (co>=8 && co<=10) || co==12) return true; break; + case (0x1023) : if (co>=3 && co<=11) return true; break; + case (0x1032) : if ((co>=4 && co<=6) || (co>=8 && co<=10)) return true; break; + case (0x1203) : if (co>=3 && co<=9) return true; break; + case (0x1230) : if (co>=3 && co<=8) return true; break; + case (0x1302) : if ((co>=4 && co<=6) || co==8 || co==10) return true; break; + case (0x1320) : if ((co>=4 && co<=6) || co==8) return true; break; + case (0x2013) : if ((co>=3 && co<=5) || co==8 || co==9 || co==12 || co==13) return true; break; + case (0x2031) : if (co==3 || co==4 || co==8 || co==9 || co==12) return true; break; + case (0x2103) : if ((co>=3 && co<=5) || co==8 || co==9) return true; break; + case (0x2130) : if ((co>=3 && co<=5) || co==8) return true; break; + case (0x2301) : if (co==3 || co==4 || co==8 || co==12) return true; break; + case (0x2310) : if (co==3 || co==4 || co==8) return true; break; + case (0x3012) : if (co==4 || co==6 || co==8 || co==10 || co==12 || co==14) return true; break; + case (0x3021) : if (co==4 || co==8 || co==10 || co==12) return true; break; + case (0x3102) : if (co==4 || co==6 || co==8 || co==10) return true; break; + case (0x3120) : if (co==4 || co==6 || co==8) return true; break; + case (0x3201) : if (co==4 || co==8 || co==12) return true; break; + case (0x3210) : if (co==4 || co==8) return true; break; + } + return false; } template - CImg _permute_axes(const char *const order, const t&) const { - if (is_empty() || !order) return CImg(*this,false); + CImg _permute_axes(const char *const axes_order, const t&) const { + if (is_empty() || !axes_order) return CImg(*this,false); CImg 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; + const unsigned uicase = _permute_axes_uicase(axes_order); + + if (_permute_axes_is_optim(uicase)) { // Data layout allow to do nothing but set the new dimensions + res.assign(*this,false); + for (unsigned int i = 0; i<4; ++i) { + const unsigned int + axis = (uicase>>(4*(3 - i)))&15, + value = !axis?_width:axis==1?_height:axis==2?_depth:_spectrum; + if (!i) res._width = value; else if (i==1) res._height = value; + else if (i==2) res._depth = value; else res._spectrum = value; } + return res; + } + + const T* ptrs = _data; + ulongT wh, whd; + + switch (uicase) { + 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; } @@ -30947,7 +37239,7 @@ namespace cimg_library_suffixed { 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; + case 'c' : _spectrum = siz; _width = _height = _depth = 1; break; } return *this; } @@ -30962,13 +37254,13 @@ namespace cimg_library_suffixed { \param angle Rotation angle, in degrees. \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic }. \param boundary_conditions Boundary conditions. - Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \note The size of the image is modified. **/ CImg& 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; + const float nangle = cimg::mod(angle,360.f); + if (nangle==0.f) return *this; return get_rotate(nangle,interpolation,boundary_conditions).move_to(*this); } @@ -30977,8 +37269,8 @@ namespace cimg_library_suffixed { const unsigned int boundary_conditions=0) const { if (is_empty()) return *this; CImg 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 float nangle = cimg::mod(angle,360.f); + if (boundary_conditions!=1 && cimg::mod(nangle,90.f)==0) { // Optimized version for orthogonal angles const int wm1 = width() - 1, hm1 = height() - 1; const int iangle = (int)nangle/90; switch (iangle) { @@ -31002,7 +37294,7 @@ namespace cimg_library_suffixed { } } else { // Generic angle const float - rad = (float)(nangle*cimg::PI/180.0), + rad = (float)(nangle*cimg::PI/180.), 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), @@ -31036,13 +37328,13 @@ namespace cimg_library_suffixed { return res; } - // [internal] Perform 2d rotation with arbitrary angle. + // [internal] Perform 2D rotation with arbitrary angle. void _rotate(CImg& 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), + rad = (float)(angle*cimg::PI/180.), ca = (float)std::cos(rad), sa = (float)std::sin(rad); switch (boundary_conditions) { @@ -31050,18 +37342,18 @@ namespace cimg_library_suffixed { 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)) + const float ww = 2.f*width(), hh = 2.f*height(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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) = _cubic_cut_atXY(mx=2048)) + const float ww = 2.f*width(), hh = 2.f*height(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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), @@ -31071,7 +37363,7 @@ namespace cimg_library_suffixed { } 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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), @@ -31085,23 +37377,21 @@ namespace cimg_library_suffixed { case 2 : // Periodic switch (interpolation) { case 2 : { // Cubic interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZC(res,x,y,z,c) { const float xc = x - rw2, yc = y - rh2; - res(x,y,z,c) = _cubic_cut_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) = _cubic_atXY_pc(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c); } } break; case 1 : { // Linear interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + res(x,y,z,c) = (T)_linear_atXY_p(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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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()), @@ -31113,21 +37403,21 @@ namespace cimg_library_suffixed { case 1 : // Neumann switch (interpolation) { case 2 : { // Cubic interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZC(res,x,y,z,c) { const float xc = x - rw2, yc = y - rh2; - res(x,y,z,c) = _cubic_cut_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c); + res(x,y,z,c) = _cubic_atXY_c(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c); } } break; case 1 : { // Linear interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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), @@ -31139,21 +37429,21 @@ namespace cimg_library_suffixed { default : // Dirichlet switch (interpolation) { case 2 : { // Cubic interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZC(res,x,y,z,c) { const float xc = x - rw2, yc = y - rh2; - res(x,y,z,c) = cubic_cut_atXY(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c,(T)0); + res(x,y,z,c) = cubic_atXY_c(w2 + xc*ca + yc*sa,h2 - xc*sa + yc*ca,z,c,(T)0); } } break; case 1 : { // Linear interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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), @@ -31166,19 +37456,19 @@ namespace cimg_library_suffixed { //! 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 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 { 0=nearest | 1=linear | 2=cubic }. \param boundary_conditions Boundary conditions. - Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \note Most of the time, size of the image is modified. **/ CImg 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; + const float nangle = cimg::mod(angle,360.f); + if (nangle==0.f) return *this; return get_rotate(u,v,w,nangle,interpolation,boundary_conditions).move_to(*this); } @@ -31193,9 +37483,9 @@ namespace cimg_library_suffixed { CImg R = CImg::rotation_matrix(u,v,w,angle); const CImg X = R*CImg(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); + 0.f,w1,w1,0.f,0.f,w1,w1,0.f, + 0.f,0.f,h1,h1,0.f,0.f,h1,h1, + 0.f,0.f,0.f,0.f,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), @@ -31213,22 +37503,23 @@ namespace cimg_library_suffixed { //! 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 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 cx X-coordinate of the rotation center. \param cy Y-coordinate of the rotation center. \param cz Z-coordinate of the rotation center. \param interpolation Type of interpolation. Can be { 0=nearest | 1=linear | 2=cubic | 3=mirror }. - \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann | 2=periodic }. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic }. \note Most of the time, size of the image is modified. **/ CImg 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; + const float nangle = cimg::mod(angle,360.f); + if (nangle==0.f) return *this; return get_rotate(u,v,w,nangle,cx,cy,cz,interpolation,boundary_conditions).move_to(*this); } @@ -31243,7 +37534,7 @@ namespace cimg_library_suffixed { return res; } - // [internal] Perform 3d rotation with arbitrary axis and angle. + // [internal] Perform 3D rotation with arbitrary axis and angle. void _rotate(CImg& res, const CImg& R, const unsigned int interpolation, const unsigned int boundary_conditions, const float w2, const float h2, const float d2, @@ -31252,22 +37543,22 @@ namespace cimg_library_suffixed { 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)) + const float ww = 2.f*width(), hh = 2.f*height(), dd = 2.f*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(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) = _cubic_cut_atXYZ(X=2048)) + const float ww = 2.f*width(), hh = 2.f*height(), dd = 2.f*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZ(res,x,y,z) { const float xc = x - rw2, yc = y - rh2, zc = z - rd2, @@ -31281,7 +37572,7 @@ namespace cimg_library_suffixed { } 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_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZ(res,x,y,z) { const float xc = x - rw2, yc = y - rh2, zc = z - rd2; const int @@ -31298,29 +37589,29 @@ namespace cimg_library_suffixed { case 2 : // Periodic switch (interpolation) { case 2 : { // Cubic interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(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) = _cubic_cut_atXYZ(X,Y,Z,c); + 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) = _cubic_atXYZ_pc(X,Y,Z,c); } } break; case 1 : { // Linear interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(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); + 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) = (T)_linear_atXYZ_p(X,Y,Z,c); } } break; default : { // Nearest-neighbor interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZ(res,x,y,z) { const float xc = x - rw2, yc = y - rh2, zc = z - rd2; const int @@ -31335,18 +37626,18 @@ namespace cimg_library_suffixed { case 1 : // Neumann switch (interpolation) { case 2 : { // Cubic interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(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) = _cubic_cut_atXYZ(X,Y,Z,c); + cimg_forC(res,c) res(x,y,z,c) = _cubic_atXYZ_c(X,Y,Z,c); } } break; case 1 : { // Linear interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZ(res,x,y,z) { const float xc = x - rw2, yc = y - rh2, zc = z - rd2, @@ -31357,7 +37648,7 @@ namespace cimg_library_suffixed { } } break; default : { // Nearest-neighbor interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZ(res,x,y,z) { const float xc = x - rw2, yc = y - rh2, zc = z - rd2; const int @@ -31372,18 +37663,18 @@ namespace cimg_library_suffixed { default : // Dirichlet switch (interpolation) { case 2 : { // Cubic interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(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) = cubic_cut_atXYZ(X,Y,Z,c,(T)0); + cimg_forC(res,c) res(x,y,z,c) = cubic_atXYZ_c(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_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZ(res,x,y,z) { const float xc = x - rw2, yc = y - rh2, zc = z - rd2, @@ -31394,7 +37685,7 @@ namespace cimg_library_suffixed { } } break; default : { // Nearest-neighbor interpolation - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(res.size()>=2048)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(res.size(),2048)) cimg_forXYZ(res,x,y,z) { const float xc = x - rw2, yc = y - rh2, zc = z - rd2; const int @@ -31416,37 +37707,37 @@ namespace cimg_library_suffixed { \param boundary_conditions Boundary conditions { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. **/ template - CImg& warp(const CImg& warp, const unsigned int mode=0, + CImg& warp(const CImg& p_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); + return get_warp(p_warp,mode,interpolation,boundary_conditions).move_to(*this); } //! Warp image content by a warping field \newinstance template - CImg get_warp(const CImg& warp, const unsigned int mode=0, + CImg get_warp(const CImg& p_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)) + if (is_empty() || !p_warp) return *this; + if (mode && !is_sameXYZ(p_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); + p_warp._width,p_warp._height,p_warp._depth,p_warp._spectrum,p_warp._data); - CImg res(warp._width,warp._height,warp._depth,_spectrum); + CImg res(p_warp._width,p_warp._height,p_warp._depth,_spectrum); - if (warp._spectrum==1) { // 1d warping + if (p_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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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); + const t *ptrs0 = p_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=1) // Linear interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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); + const t *ptrs0 = p_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=4096)) + const float w2 = 2.f*width(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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++) = _cubic_cut_atX(mx=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atX(cimg::mod(x - (float)*(ptrs0++),(float)_width),y,z,c); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atX_pc(x - (float)*(ptrs0++),y,z,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atX(x - (float)*(ptrs0++),y,z,c); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atX_c(x - (float)*(ptrs0++),y,z,c); } break; default : // Dirichlet - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = cubic_cut_atX(x - (float)*(ptrs0++),y,z,c,(T)0); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = cubic_atX_c(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)) + const float w2 = 2.f*width(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = (T)_linear_atX_p(x - (float)*(ptrs0++),y,z,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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); } } @@ -31541,9 +37832,9 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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=4096)) + const float w2 = 2.f*width(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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++) = _cubic_cut_atX(mx=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atX(cimg::mod((float)*(ptrs0++),(float)_width),0,0,c); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atX_pc((float)*(ptrs0++),0,0,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atX((float)*(ptrs0++),0,0,c); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atX_c((float)*(ptrs0++),0,0,c); } break; default : // Dirichlet - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = cubic_cut_atX((float)*(ptrs0++),0,0,c,(T)0); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = cubic_atX_c((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)) + const float w2 = 2.f*width(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = (T)_linear_atX_p((float)*(ptrs0++),0,0,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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); } } @@ -31642,9 +37933,9 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_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=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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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=0 && Y=1) // Linear interpolation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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=0 && Y=4096)) + const float w2 = 2.f*width(), h2 = 2.f*height(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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++) = _cubic_cut_atXY(mx=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atXY(cimg::mod(x - (float)*(ptrs0++),(float)_width), - cimg::mod(y - (float)*(ptrs1++),(float)_height),z,c); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atXY_pc(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atXY_c(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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = cubic_cut_atXY(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c,(T)0); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = cubic_atXY_c(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)) + const float w2 = 2.f*width(), h2 = 2.f*height(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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), @@ -31758,24 +38048,23 @@ namespace cimg_library_suffixed { } } break; case 2 : // Periodic - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY_p(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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); } } @@ -31783,9 +38072,9 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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), @@ -31796,68 +38085,69 @@ namespace cimg_library_suffixed { } 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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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++)),width()), + cimg::mod(y - (int)cimg::round(*(ptrs1++)),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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _atXY(x - (int)cimg::round(*(ptrs0++)), + y - (int)cimg::round(*(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = atXY(x - (int)cimg::round(*(ptrs0++)), + y - (int)cimg::round(*(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)) + const float w2 = 2.f*width(), h2 = 2.f*height(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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++) = _cubic_cut_atXY(mx=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atXY(cimg::mod((float)*(ptrs0++),(float)_width), - cimg::mod((float)*(ptrs1++),(float)_height),0,c); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atXY_pc((float)*(ptrs0++),(float)*(ptrs1++),0,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = _cubic_cut_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _cubic_atXY_c((float)*(ptrs0++),(float)*(ptrs1++),0,c); } break; default : // Dirichlet - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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++) = cubic_cut_atXY((float)*(ptrs0++),(float)*(ptrs1++),0,c,(T)0); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = cubic_atXY_c((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)) + const float w2 = 2.f*width(), h2 = 2.f*height(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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), @@ -31867,24 +38157,23 @@ namespace cimg_library_suffixed { } } break; case 2 : // Periodic - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = (T)_linear_atXY_p((float)*(ptrs0++),(float)*(ptrs1++),0,c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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); } } @@ -31892,9 +38181,9 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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), @@ -31905,39 +38194,41 @@ namespace cimg_library_suffixed { } 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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_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++)),width()), + cimg::mod((int)cimg::round(*(ptrs1++)),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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = _atXY((int)cimg::round(*(ptrs0++)), + (int)cimg::round(*(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1); T *ptrd = res.data(0,y,z,c); + cimg_forX(res,x) *(ptrd++) = atXY((int)cimg::round(*(ptrs0++)), + (int)cimg::round(*(ptrs1++)),0,c,(T)0); } } } - } else { // 3d warping + } 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); const T *ptrs = data(0,y,z,c); cimg_forX(res,x) { const int @@ -31950,15 +38241,15 @@ namespace cimg_library_suffixed { } 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); const T *ptrs = data(0,y,z,c); cimg_forX(res,x) { const int @@ -31972,57 +38263,56 @@ namespace cimg_library_suffixed { 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)) + const float w2 = 2.f*width(), h2 = 2.f*height(), d2 = 2.f*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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++) = _cubic_cut_atXYZ(mx=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); - cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXYZ(cimg::mod(x - (float)*(ptrs0++),(float)_width), - cimg::mod(y - (float)*(ptrs1++),(float)_height), - cimg::mod(z - (float)*(ptrs2++),(float)_depth),c); + cimg_forX(res,x) + *(ptrd++) = _cubic_atXYZ_pc(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); cimg_forX(res,x) - *(ptrd++) = _cubic_cut_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c); + *(ptrd++) = _cubic_atXYZ_c(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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); cimg_forX(res,x) - *(ptrd++) = cubic_cut_atXYZ(x - (float)*(ptrs0++),y - (float)*(ptrs1++),z - (float)*(ptrs2++),c,(T)0); + *(ptrd++) = cubic_atXYZ_c(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)) + const float w2 = 2.f*width(), h2 = 2.f*height(), d2 = 2.f*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); cimg_forX(res,x) { const float @@ -32036,28 +38326,27 @@ namespace cimg_library_suffixed { } } break; case 2 : // Periodic - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = (T)_linear_atXYZ_p(x - (float)*(ptrs0++),y - (float)*(ptrs1++), + z - (float)*(ptrs2++),c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); @@ -32067,9 +38356,9 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); cimg_forX(res,x) { const int @@ -32084,81 +38373,83 @@ namespace cimg_library_suffixed { } 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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod(x - (int)cimg::round(*(ptrs0++)),width()), + cimg::mod(y - (int)cimg::round(*(ptrs1++)),height()), + cimg::mod(z - (int)cimg::round(*(ptrs2++)),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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = _atXYZ(x - (int)cimg::round(*(ptrs0++)), + y - (int)cimg::round(*(ptrs1++)), + z - (int)cimg::round(*(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = atXYZ(x - (int)cimg::round(*(ptrs0++)), + y - (int)cimg::round(*(ptrs1++)), + z - (int)cimg::round(*(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)) + const float w2 = 2.f*width(), h2 = 2.f*height(), d2 = 2.f*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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++) = _cubic_cut_atXYZ(mx=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); - cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXYZ(cimg::mod((float)*(ptrs0++),(float)_width), - cimg::mod((float)*(ptrs1++),(float)_height), - cimg::mod((float)*(ptrs2++),(float)_depth),c); + cimg_forX(res,x) *(ptrd++) = _cubic_atXYZ_pc((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); - cimg_forX(res,x) *(ptrd++) = _cubic_cut_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); + cimg_forX(res,x) *(ptrd++) = _cubic_atXYZ_c((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++),c); } break; default : // Dirichlet - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=4096)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); - cimg_forX(res,x) *(ptrd++) = cubic_cut_atXYZ((float)*(ptrs0++),(float)*(ptrs1++),(float)*(ptrs2++), - c,(T)0); + cimg_forX(res,x) *(ptrd++) = cubic_atXYZ_c((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)) + const float w2 = 2.f*width(), h2 = 2.f*height(), d2 = 2.f*depth(); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); cimg_forX(res,x) { const float @@ -32172,27 +38463,26 @@ namespace cimg_library_suffixed { } } break; case 2 :// Periodic - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = (T)_linear_atXYZ_p((float)*(ptrs0++),(float)*(ptrs1++), + (float)*(ptrs2++),c); } break; case 1 : // Neumann - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(res.size()>=1048576)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); @@ -32202,9 +38492,9 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(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 *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_warp.data(0,y,z,2); T *ptrd = res.data(0,y,z,c); cimg_forX(res,x) { const int @@ -32219,25 +38509,29 @@ namespace cimg_library_suffixed { } 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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = (*this)(cimg::mod((int)cimg::round(*(ptrs0++)),width()), + cimg::mod((int)cimg::round(*(ptrs1++)),height()), + cimg::mod((int)cimg::round(*(ptrs2++)),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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = _atXYZ((int)cimg::round(*(ptrs0++)), + (int)cimg::round(*(ptrs1++)), + (int)cimg::round(*(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); + const t *ptrs0 = p_warp.data(0,y,z,0), *ptrs1 = p_warp.data(0,y,z,1), *ptrs2 = p_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); + cimg_forX(res,x) *(ptrd++) = atXYZ((int)cimg::round(*(ptrs0++)), + (int)cimg::round(*(ptrs1++)), + (int)cimg::round(*(ptrs2++)),c,(T)0); } } } @@ -32245,7 +38539,7 @@ namespace cimg_library_suffixed { return res; } - //! Generate a 2d representation of a 3d image, with XY,XZ and YZ views. + //! 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. @@ -32267,7 +38561,7 @@ namespace cimg_library_suffixed { draw_image(0,img_xy._height,img_xz); } - //! Construct a 2d representation of a 3d image, with XY,XZ and YZ views \inplace. + //! Construct a 2D representation of a 3D image, with XY,XZ and YZ views \inplace. CImg& 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); @@ -32304,12 +38598,18 @@ namespace cimg_library_suffixed { ny0 = y0=0 && nx1=0 && ny1=0 && nz1=0 && nc1 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) { + 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*16 && + _height*_depth*_spectrum>=4)) cimg_forXYZC(res,x,y,z,c) { const int mx = cimg::mod(nx0 + x,w2), @@ -32323,14 +38623,16 @@ namespace cimg_library_suffixed { } } break; case 2 : { // Periodic - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=16 && _height*_depth*_spectrum>=4)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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 @@ -32384,24 +38686,24 @@ namespace cimg_library_suffixed { for (const char *s = axes; *s; ++s) { const char axis = cimg::lowercase(*s); const CImg coords = _autocrop(value,axis); - if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels. + 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; + 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; + 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; + 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); - } + 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; @@ -32419,7 +38721,7 @@ namespace cimg_library_suffixed { **/ CImg& autocrop(const T *const color=0, const char *const axes="zyx") { if (is_empty()) return *this; - if (!color) { // Guess color. + if (!color) { // Guess color const CImg col1 = get_vector_at(0,0,0); const unsigned int w = _width, h = _height, d = _depth, s = _spectrum; autocrop(col1,axes); @@ -32433,32 +38735,32 @@ namespace cimg_library_suffixed { const char axis = cimg::lowercase(*s); switch (axis) { case 'x' : { - int x0 = width(), x1 = -1; - cimg_forC(*this,c) { - const CImg 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); } - } + int x0 = width(), x1 = -1; + cimg_forC(*this,c) { + const CImg 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; + } break; case 'y' : { - int y0 = height(), y1 = -1; - cimg_forC(*this,c) { - const CImg 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); } - } + int y0 = height(), y1 = -1; + cimg_forC(*this,c) { + const CImg 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; + } break; default : { - int z0 = depth(), z1 = -1; - cimg_forC(*this,c) { - const CImg 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); - } + int z0 = depth(), z1 = -1; + cimg_forC(*this,c) { + const CImg 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; @@ -32469,16 +38771,6 @@ namespace cimg_library_suffixed { return (+*this).autocrop(color,axes); } - //! Autocrop image region, regarding the specified background color \overloading. - template CImg& autocrop(const CImg& color, const char *const axes="zyx") { - return get_autocrop(color,axes).move_to(*this); - } - - //! Autocrop image region, regarding the specified background color \newinstance. - template CImg get_autocrop(const CImg& color, const char *const axes="zyx") const { - return get_autocrop(color._data,axes); - } - CImg _autocrop(const T& value, const char axis) const { CImg res; switch (cimg::lowercase(axis)) { @@ -32486,41 +38778,41 @@ namespace cimg_library_suffixed { 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) { + 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::vector(x0,x1); + res = CImg::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) { + 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::vector(y0,y1); + res = CImg::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) { + 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::vector(z0,z1); + res = CImg::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) { + 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::vector(c0,c1); + res = CImg::vector(c0,c1); } } return res; @@ -32634,37 +38926,37 @@ namespace cimg_library_suffixed { return get_channels(c0,c1).move_to(*this); } - //! Return stream line of a 2d or 3d vector field. + //! Return stream line of a 2D or 3D vector field. CImg 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.", + "streamline(): Instance is not a 2D or 3D vector field.", cimg_instance); if (_spectrum==2) { if (is_oriented_only) { typename CImg::_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); + 0,0,0,_width - 1.f,_height - 1.f,0.f); } else { typename CImg::_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); + 0,0,0,_width - 1.f,_height - 1.f,0.f); } } if (is_oriented_only) { typename CImg::_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); + 0,0,0,_width - 1.f,_height - 1.f,_depth - 1.f); } typename CImg::_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); + 0,0,0,_width - 1.f,_height - 1.f,_depth - 1.f); } - //! Return stream line of a 3d vector field. + //! Return stream line of a 3D vector field. /** \param func Vector field function. \param x X-coordinate of the starting point of the streamline. @@ -32712,7 +39004,7 @@ namespace cimg_library_suffixed { X = x, Y = y, Z = z; switch (interpolation_type) { - case 0 : { // Nearest integer interpolation. + case 0 : { // Nearest integer interpolation cimg_forX(coordinates,l) { *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; const int @@ -32728,7 +39020,7 @@ namespace cimg_library_suffixed { if (is_bounded && (Xx1 || Yy1 || Zz1)) break; } } break; - case 1 : { // First-order interpolation. + case 1 : { // First-order interpolation cimg_forX(coordinates,l) { *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; float @@ -32740,7 +39032,7 @@ namespace cimg_library_suffixed { if (is_bounded && (Xx1 || Yy1 || Zz1)) break; } } break; - case 2 : { // Second order interpolation. + case 2 : { // Second order interpolation cimg_forX(coordinates,l) { *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; float @@ -32757,8 +39049,8 @@ namespace cimg_library_suffixed { if (is_bounded && (Xx1 || Yy1 || Zz1)) break; } } break; - default : { // Fourth order interpolation. - cimg_forX(coordinates,x) { + default : { // Fourth order interpolation + cimg_forX(coordinates,k) { *(ptr_x++) = X; *(ptr_y++) = Y; *(ptr_z++) = Z; float u0 = (float)(dl2*func(X,Y,Z,0)), @@ -32793,7 +39085,7 @@ namespace cimg_library_suffixed { return coordinates; } - //! Return stream line of a 3d vector field \overloading. + //! Return stream line of a 3D vector field \overloading. static CImg streamline(const char *const expression, const float x, const float y, const float z, const float L=256, const float dl=0.1f, @@ -32926,30 +39218,28 @@ namespace cimg_library_suffixed { **/ CImg 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); + const ulongT + beg = (ulongT)offset(x0,y0,z0,c0), + end = (ulongT)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(_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 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); + const ulongT + beg = (ulongT)offset(x0,y0,z0,c0), + end = (ulongT)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(_data + beg,x1 - x0 + 1,1,1,1,true); } @@ -32962,32 +39252,30 @@ namespace cimg_library_suffixed { **/ CImg 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); + const ulongT + beg = (ulongT)offset(0,y0,z0,c0), + end = (ulongT)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(_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 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); + const ulongT + beg = (ulongT)offset(0,y0,z0,c0), + end = (ulongT)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(_data + beg,_width,y1 - y0 + 1,1,1,true); } @@ -33013,31 +39301,29 @@ namespace cimg_library_suffixed { \param c0 C-coordinate. **/ CImg 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); + const ulongT + beg = (ulongT)offset(0,0,z0,c0), + end = (ulongT)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(_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 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); + const ulongT + beg = (ulongT)offset(0,0,z0,c0), + end = (ulongT)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(_data + beg,_width,_height,z1 - z0 + 1,1,true); } @@ -33061,31 +39347,29 @@ namespace cimg_library_suffixed { \param c1 C-coordinate of the ending channel. **/ CImg 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); + const ulongT + beg = (ulongT)offset(0,0,0,c0), + end = (ulongT)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(_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 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); + const ulongT + beg = (ulongT)offset(0,0,0,c0), + end = (ulongT)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(_data + beg,_width,_height,_depth,c1 - c0 + 1,true); } @@ -33115,26 +39399,27 @@ namespace cimg_library_suffixed { //! Split image into a list along specified axis. /** \param axis Splitting axis. Can be { 'x' | 'y' | 'z' | 'c' }. - \param nb Number of splitted parts. + \param nb Number of split 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. + - If \c nb==0, instance image is split into blocs of equal values along the specified axis. + - If \c nb<=0, instance image is split into blocs of -\c nb pixel wide. + - If \c nb>0, instance image is split into \c nb blocs. **/ CImgList get_split(const char axis, const int nb=-1) const { CImgList res; if (is_empty()) return res; const char _axis = cimg::lowercase(axis); - if (nb<0) { // Split by bloc size. + if (nb<0) { // Split by block 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=(cimg_openmp_sizefactor)*128 && + _height*_depth*_spectrum>=128)) + for (int p = 0; p<(int)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); @@ -33143,8 +39428,9 @@ namespace cimg_library_suffixed { 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=(cimg_openmp_sizefactor)*128 && + _width*_depth*_spectrum>=128)) + for (int p = 0; p<(int)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); @@ -33153,8 +39439,9 @@ namespace cimg_library_suffixed { 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=(cimg_openmp_sizefactor)*128 && + _width*_height*_spectrum>=128)) + for (int p = 0; p<(int)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); @@ -33163,14 +39450,15 @@ namespace cimg_library_suffixed { 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=(cimg_openmp_sizefactor)*128 && + _width*_height*_depth>=128)) + for (int p = 0; p<(int)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. + } 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 @@ -33212,7 +39500,7 @@ namespace cimg_library_suffixed { } } } - } else { // Split by egal values according to specified axis. + } else { // Split by equal values according to specified axis T current = *_data; switch (_axis) { case 'x' : { @@ -33257,16 +39545,18 @@ namespace cimg_library_suffixed { /** \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. + \param keep_values Tells if the splitting sequence must be kept in the split blocs. **/ template CImgList get_split(const CImg& values, const char axis=0, const bool keep_values=true) const { + typedef _cimg_Tt Tt; + CImgList res; if (is_empty()) return res; const ulongT vsiz = values.size(); const char _axis = cimg::lowercase(axis); if (!vsiz) return CImgList(*this); - if (vsiz==1) { // Split according to a single value. + if (vsiz==1) { // Split according to a single value const T value = (T)*values; switch (_axis) { case 'x' : { @@ -33310,22 +39600,25 @@ namespace cimg_library_suffixed { ulongT i0 = 0, i = 0; do { while (ii0) { if (keep_values) CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); i0 = i; } + if (i>i0) { + if (keep_values) CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); + i0 = i; + } while (ii0) { CImg(_data + i0,1,(unsigned int)(i - i0)).move_to(res); i0 = i; } } while (i=vsiz) j = 0; } - i-=j; + while (i<_width && (Tt)(*this)(i)==(Tt)values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=(unsigned int)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); @@ -33338,10 +39631,10 @@ namespace cimg_library_suffixed { case 'y' : { unsigned int i0 = 0, i1 = 0, i = 0; do { - if ((*this)(0,i)==*values) { + if ((Tt)(*this)(0,i)==(Tt)*values) { i1 = i; j = 0; - while (i<_height && (*this)(0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } - i-=j; + while (i<_height && (Tt)(*this)(0,i)==(Tt)values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=(unsigned int)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); @@ -33354,10 +39647,10 @@ namespace cimg_library_suffixed { case 'z' : { unsigned int i0 = 0, i1 = 0, i = 0; do { - if ((*this)(0,0,i)==*values) { + if ((Tt)(*this)(0,0,i)==(Tt)*values) { i1 = i; j = 0; - while (i<_depth && (*this)(0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } - i-=j; + while (i<_depth && (Tt)(*this)(0,0,i)==(Tt)values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=(unsigned int)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); @@ -33370,10 +39663,10 @@ namespace cimg_library_suffixed { case 'c' : { unsigned int i0 = 0, i1 = 0, i = 0; do { - if ((*this)(0,0,0,i)==*values) { + if ((Tt)(*this)(0,0,0,i)==(Tt)*values) { i1 = i; j = 0; - while (i<_spectrum && (*this)(0,0,0,i)==values[j]) { ++i; if (++j>=vsiz) j = 0; } - i-=j; + while (i<_spectrum && (Tt)(*this)(0,0,0,i)==(Tt)values[j]) { ++i; if (++j>=vsiz) j = 0; } + i-=(unsigned int)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); @@ -33384,13 +39677,13 @@ namespace cimg_library_suffixed { if (i0<_spectrum) get_channels(i0,spectrum() - 1).move_to(res); } break; default : { - ulongT i0 = 0, i1 = 0, i = 0; const ulongT siz = size(); + ulongT i0 = 0, i1 = 0, i = 0; do { - if ((*this)[i]==*values) { + if ((Tt)(*this)[i]==(Tt)*values) { i1 = i; j = 0; - while (i=vsiz) j = 0; } - i-=j; + while (i=vsiz) j = 0; } + i-=(unsigned int)j; if (i>i1) { if (i1>i0) CImg(_data + i0,1,(unsigned int)(i1 - i0)).move_to(res); if (keep_values) CImg(_data + i1,1,(unsigned int)(i - i1)).move_to(res); @@ -33450,410 +39743,576 @@ namespace cimg_library_suffixed { //! Correlate image by a kernel. /** \param kernel = the correlation kernel. - \param boundary_conditions boundary conditions can be (false=dirichlet, true=neumann) + \param boundary_conditions Boundary condition. Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \param is_normalized = enable local normalization. + \param channel_mode Channel processing mode. + Can be { 0=all | 1=one for one (default) | 2=partial sum | 3=full sum }. + \param xcenter X-coordinate of the kernel center (~0U>>1 means 'centered'). + \param ycenter Y-coordinate of the kernel center (~0U>>1 means 'centered'). + \param zcenter Z-coordinate of the kernel center (~0U>>1 means 'centered'). + \param xstart Starting X-coordinate of the instance image. + \param ystart Starting Y-coordinate of the instance image. + \param zstart Starting Z-coordinate of the instance image. + \param xend Ending X-coordinate of the instance image. + \param yend Ending Y-coordinate of the instance image. + \param zend Ending Z-coordinate of the instance image. + \param xstride Stride along the X-axis. + \param ystride Stride along the Y-axis. + \param zstride Stride along the Z-axis. + \param xdilation Dilation along the X-axis. + \param ydilation Dilation along the Y-axis. + \param zdilation Dilation along the Z-axis. + \param interpolation_type Can be { false=nearest | true=linear }. \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). + res(x,y,z) = sum_{i,j,k} (*this)(\alpha_x\;x + \beta_x\;(i - c_x),\alpha_y\;y + \beta_y\;(j - + c_y),\alpha_z\;z + \beta_z\;(k - c_z))*kernel(i,j,k). **/ template - CImg& correlate(const CImg& kernel, const bool boundary_conditions=true, - const bool is_normalized=false) { + CImg& correlate(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_normalized=false, const unsigned int channel_mode=1, + const int xcenter=(int)(~0U>>1), + const int ycenter=(int)(~0U>>1), + const int zcenter=(int)(~0U>>1), + const int xstart=0, + const int ystart=0, + const int zstart=0, + const int xend=(int)(~0U>>1), + const int yend=(int)(~0U>>1), + const int zend=(int)(~0U>>1), + const float xstride=1, const float ystride=1, const float zstride=1, + const float xdilation=1, const float ydilation=1, const float zdilation=1, + const bool interpolation_type=false) { if (is_empty() || !kernel) return *this; - return get_correlate(kernel,boundary_conditions,is_normalized).move_to(*this); + return get_correlate(kernel,boundary_conditions,is_normalized,channel_mode, + xcenter,ycenter,zcenter,xstart,ystart,zstart,xend,yend,zend, + xstride,ystride,zstride,xdilation,ydilation,zdilation, + interpolation_type).move_to(*this); } template - CImg<_cimg_Ttfloat> get_correlate(const CImg& kernel, const bool boundary_conditions=true, - const bool is_normalized=false) const { - return _correlate(kernel,boundary_conditions,is_normalized,false); + CImg<_cimg_Ttfloat> get_correlate(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_normalized=false, const unsigned int channel_mode=1, + const int xcenter=(int)(~0U>>1), + const int ycenter=(int)(~0U>>1), + const int zcenter=(int)(~0U>>1), + const int xstart=0, + const int ystart=0, + const int zstart=0, + const int xend=(int)(~0U>>1), + const int yend=(int)(~0U>>1), + const int zend=(int)(~0U>>1), + const float xstride=1, const float ystride=1, const float zstride=1, + const float xdilation=1, const float ydilation=1, const float zdilation=1, + const bool interpolation_type=false) const { + return _correlate(kernel,boundary_conditions,is_normalized,channel_mode, + xcenter,ycenter,zcenter,xstart,ystart,zstart,xend,yend,zend, + xstride,ystride,zstride,xdilation,ydilation,zdilation, + interpolation_type,false); } //! Correlate image by a kernel \newinstance. template - CImg<_cimg_Ttfloat> _correlate(const CImg& kernel, const bool boundary_conditions, - const bool is_normalized, const bool is_convolution) const { - if (is_empty() || !kernel) return *this; + CImg<_cimg_Ttfloat> _correlate(const CImg& kernel, const unsigned int boundary_conditions, + const bool is_normalized, const unsigned int channel_mode, + const int xcenter, const int ycenter, const int zcenter, + const int xstart, const int ystart, const int zstart, + const int xend, const int yend, const int zend, + const float xstride, const float ystride, const float zstride, + const float xdilation, const float ydilation, const float zdilation, + const bool interpolation_type, const bool is_convolve) const { typedef _cimg_Ttfloat Ttfloat; CImg res; - const ulongT - res_whd = (ulongT)_width*_height*_depth, - res_size = res_whd*std::max(_spectrum,kernel._spectrum); - const bool - is_inner_parallel = _width*_height*_depth>=32768, - is_outer_parallel = res_size>=32768; - _cimg_abort_init_omp; + _cimg_abort_init_openmp; cimg_abort_init; - if (kernel._width==kernel._height && - ((kernel._depth==1 && kernel._width<=6) || (kernel._depth==kernel._width && kernel._width<=3))) { + if (xstart>xend || ystart>yend || zstart>zend) + throw CImgArgumentException(_cimg_instance + "%s(): Invalid xyz-start/end arguments (start = (%d,%d,%d), end = (%d,%d,%d)).", + cimg_instance, + is_convolve?"convolve":"correlate", + xstart,ystart,zstart,xend,yend,zend); + if (xstride<=0 || ystride<=0 || zstride<=0) + throw CImgArgumentException(_cimg_instance + "%s(): Invalid stride arguments (%g,%g,%g).", + cimg_instance, + is_convolve?"convolve":"correlate", + xstride,ystride,zstride); - // Special optimization done for 2x2, 3x3, 4x4, 5x5, 6x6, 2x2x2 and 3x3x3 kernel. - if (!boundary_conditions && res_whd<=3000*3000) { // Dirichlet boundaries - // For relatively small images, adding a zero border then use optimized NxN convolution loops is faster. - res = (kernel._depth==1?get_crop(-1,-1,_width,_height):get_crop(-1,-1,-1,_width,_height,_depth)). - _correlate(kernel,true,is_normalized,is_convolution); - if (kernel._depth==1) res.crop(1,1,res._width - 2,res._height - 2); - else res.crop(1,1,1,res._width - 2,res._height - 2,res._depth - 2); + if (is_empty() || !kernel) return *this; + int + _xcenter = xcenter==(int)(~0U>>1)?kernel.width()/2 - 1 + (kernel.width()%2):xcenter, + _ycenter = ycenter==(int)(~0U>>1)?kernel.height()/2 - 1 + (kernel.height()%2):ycenter, + _zcenter = zcenter==(int)(~0U>>1)?kernel.depth()/2 - 1 + (kernel.depth()%2):zcenter; + float _xdilation = xdilation, _ydilation = ydilation, _zdilation = zdilation; - } else { // Neumann boundaries - res.assign(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); - cimg::unused(is_inner_parallel,is_outer_parallel); - CImg _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(); + CImg _kernel; + if (is_convolve) { // If convolution, go back to correlation + if (kernel.size()/kernel.spectrum()<=27) { + _kernel = CImg(kernel._data,kernel.size()/kernel._spectrum,1,1,kernel._spectrum,true). + get_mirror('x').resize(kernel,-1); + _xcenter = kernel.width() - 1 - _xcenter; + _ycenter = kernel.height() - 1 - _ycenter; + _zcenter = kernel.depth() - _zcenter - 1; + } else { _kernel = kernel.get_shared(); _xdilation*=-1; _ydilation*=-1; _zdilation*=-1; } + } else _kernel = kernel.get_shared(); - switch (_kernel._depth) { - case 3 : { - cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) - cimg_forC(res,c) { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - CImg I(27); - Ttfloat *ptrd = res.data(0,0,0,c); - 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 : { - cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) - cimg_forC(res,c) { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - CImg I(8); - Ttfloat *ptrd = res.data(0,0,0,c); - 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 : { - cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) - cimg_forC(res,c) { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - CImg I(36); - Ttfloat *ptrd = res.data(0,0,0,c); - 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 : { - cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) - cimg_forC(res,c) { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - CImg I(25); - Ttfloat *ptrd = res.data(0,0,0,c); - 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 : { - cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) - cimg_forC(res,c) { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - CImg I(16); - Ttfloat *ptrd = res.data(0,0,0,c); - 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 : { - cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) - cimg_forC(res,c) { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - CImg I(9); - Ttfloat *ptrd = res.data(0,0,0,c); - 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 : { - cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) - cimg_forC(res,c) { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - CImg I(4); - Ttfloat *ptrd = res.data(0,0,0,c); - 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 img = get_shared_channel(c%_spectrum); - const CImg K = _kernel.get_shared_channel(c%kernel._spectrum); - res.get_shared_channel(c).assign(img)*=K[0]; - } - break; - } - } - } + const int + _xend = xend==(int)(~0U>>1)?width() - 1:xend, + _yend = yend==(int)(~0U>>1)?height() - 1:yend, + _zend = zend==(int)(~0U>>1)?depth() - 1:zend, + i_xstride = (int)cimg::round(xstride), + i_ystride = (int)cimg::round(ystride), + i_zstride = (int)cimg::round(zstride), + i_xdilation = (int)cimg::round(_xdilation), + i_ydilation = (int)cimg::round(_ydilation), + i_zdilation = (int)cimg::round(_zdilation), + res_width = _xend - xstart + 1, + res_height = _yend - ystart + 1, + res_depth = _zend - zstart + 1, + smin = std::min(spectrum(),_kernel.spectrum()), + smax = std::max(spectrum(),_kernel.spectrum()), + cend = !channel_mode?spectrum()*_kernel.spectrum():smax; + const ulongT + res_wh = (ulongT)res_width*res_height, + res_whd = res_wh*res_depth; + + if (!res_whd) return CImg(); + res.assign(res_width,res_height,res_depth, + !channel_mode?_spectrum*_kernel._spectrum: + channel_mode==1?smax: + channel_mode==2?(int)std::ceil((float)smax/smin):1); + if (channel_mode>=2) res.fill(0); + + const ulongT res_siz = res_whd*res._spectrum; + const bool +#if cimg_use_openmp==1 + is_master_thread = !omp_get_thread_num(), +#else + is_master_thread = true, +#endif + is_outer_parallel = is_master_thread && + (res._spectrum>=cimg::nb_cpus() || (res_siz<=(cimg_openmp_sizefactor)*32768 && res._spectrum>1)), + is_inner_parallel = is_master_thread && + (!is_outer_parallel && res_whd>=(cimg_openmp_sizefactor)*32768), + is_int_stride_dilation = xstride==i_xstride && ystride==i_ystride && zstride==i_zstride && + _xdilation==i_xdilation && _ydilation==i_ydilation && _zdilation==i_zdilation; + cimg::unused(is_inner_parallel,is_outer_parallel); + const int + w = width(), h = height(), d = depth(), + w1 = w - 1, h1 = h - 1, d1 = d - 1, + w2 = 2*w, h2 = 2*h, d2 = 2*d; + const ulongT wh = (ulongT)w*h, whd = wh*d; + + // Reshape kernel to enable optimizations for a few cases. + if (boundary_conditions==1 && + _kernel._width>1 && _kernel._height>1 && + ((_kernel._depth==1 && _kernel._width<=5 && _kernel._height<=5) || + (_kernel._depth<=3 && _kernel._width<=3 && _kernel._height<=3)) && + xstart>=0 && ystart>=0 && zstart>=0 && + _xend=0 && i_ydilation>=0 && i_zdilation>=0) { + const unsigned int M = cimg::max(_kernel._width,_kernel._height,_kernel._depth); + _kernel.assign(_kernel.get_resize(M + 1 - (M%2),M + 1 - (M%2),_kernel._depth>1?M + 1 - (M%2):1,-100, + 0,0, + 1,1,1),false); + _xcenter = _ycenter = (int)M/2; + if (_kernel._depth>1) _ycenter = (int)M/2; } - if (!res) { // Generic version for other kernels and boundary conditions. - res.assign(_width,_height,_depth,std::max(_spectrum,kernel._spectrum)); - 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_pragma_openmp(parallel for cimg_openmp_if(!is_inner_parallel && is_outer_parallel)) - cimg_forC(res,c) _cimg_abort_try_omp { - cimg_abort_test; - const CImg img = get_shared_channel(c%_spectrum); - const CImg 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(is_inner_parallel)) - for (int z = mz1; z=mye || z=mze)?++x:((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); + // Optimized version for a few particular cases (3x3, 5x5 and 3x3x3 kernels, with a few other conditions). + if (boundary_conditions==1 && + _kernel._width==_kernel._height && + ((_kernel._depth==1 && (_kernel._width==3 || _kernel._width==5)) || + (_kernel._depth==_kernel._width && _kernel._width==3)) && + _xcenter==_kernel.width()/2 && _ycenter==_kernel.height()/2 && _zcenter==_kernel.depth()/2 && + xstart>=0 && ystart>=0 && zstart>=0 && + _xend=0 && i_ydilation>=0 && i_zdilation>=0) { + + switch (_kernel._depth) { + case 3 : { // 3x3x3 centered kernel + cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) + for (int c = 0; c I = get_shared_channel(c%_spectrum); + const CImg K = _kernel.get_shared_channel(!channel_mode?c/_spectrum:c%_kernel._spectrum); + CImg _resu = channel_mode<=1?res.get_shared_channel(c): + CImg(res.width(),res.height(),res.depth(),1); + if (is_normalized) { + const Ttfloat M = (Ttfloat)K.magnitude(2), M2 = M*M; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(is_inner_parallel)) + cimg_forXYZ(res,X,Y,Z) { + const int + x = xstart + X, y = ystart + Y, z = zstart + Z, + px = x - i_xdilation>0?x - i_xdilation:0, nx = x + i_xdilation0?y - i_ydilation:0, ny = y + i_ydilation0?z - i_zdilation:0, nz = z + i_zdilation0?x - i_xdilation:0, nx = x + i_xdilation0?y - i_ydilation:0, ny = y + i_ydilation0?z - i_zdilation:0, nz = z + i_zdilation I = get_shared_channel(c%_spectrum); + const CImg K = _kernel.get_shared_channel(!channel_mode?c/_spectrum:c%_kernel._spectrum); + CImg _resu = channel_mode<=1?res.get_shared_channel(c): + CImg(res.width(),res.height(),res.depth(),1); + if (is_normalized) { + const Ttfloat M = (Ttfloat)K.magnitude(2), M2 = M*M; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(is_inner_parallel)) + cimg_forXYZ(res,X,Y,z) { + const int + x = xstart + X, y = ystart + Y, + px = x - i_xdilation>0?x - i_xdilation:0, bx = px - i_xdilation>0?px - i_xdilation:0, + nx = x + i_xdilation0?y - i_ydilation:0, by = py - i_ydilation>0?py - i_ydilation:0, + ny = y + i_ydilation=mye || z=mze)?++x:((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); + } else { + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forXYZ(res,X,Y,z) { + const int + x = xstart + X, y = ystart + Y, + px = x - i_xdilation>0?x - i_xdilation:0, bx = px - i_xdilation>0?px - i_xdilation:0, + nx = x + i_xdilation0?y - i_ydilation:0, by = py - i_ydilation>0?py - i_ydilation:0, + ny = y + i_ydilation=mye || z=mze)?++x:((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; + } + if (channel_mode==2) + cimg_pragma_openmp(critical(_correlate)) res.get_shared_channel(c/smin)+=_resu; + else if (channel_mode==3) + cimg_pragma_openmp(critical(_correlate)) res.get_shared_channel(0)+=_resu; + } _cimg_abort_catch_openmp2 + } break; + + case 3 : { // 3x3 centered kernel + cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) + for (int c = 0; c I = get_shared_channel(c%_spectrum); + const CImg K = _kernel.get_shared_channel(!channel_mode?c/_spectrum:c%_kernel._spectrum); + CImg _resu = channel_mode<=1?res.get_shared_channel(c): + CImg(res.width(),res.height(),res.depth(),1); + if (is_normalized) { + const Ttfloat M = (Ttfloat)K.magnitude(2), M2 = M*M; + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(is_inner_parallel)) + cimg_forXYZ(res,X,Y,z) { + const int + x = xstart + X, y = ystart + Y, + px = x - i_xdilation>0?x - i_xdilation:0, nx = x + i_xdilation0?y - i_ydilation:0, ny = y + i_ydilation=mye || z=mze)?++x:((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; + } else { + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forXYZ(res,X,Y,z) { + const int + x = xstart + X, y = ystart + Y, + px = x - i_xdilation>0?x - i_xdilation:0, nx = x + i_xdilation0?y - i_ydilation:0, ny = y + i_ydilation=0 && ystart>=0 && zstart>=0 && + _xend I = get_crop(xstart,ystart,zstart,c%_spectrum,_xend,_yend,_zend,c%_spectrum); + if (valK!=1) I*=valK; + if (is_normalized) I.sign(); + switch (channel_mode) { + case 0 : // All + case 1 : // One for one + res.get_shared_channel(c) = I; + break; + case 2 : // Partial sum + cimg_pragma_openmp(critical(_correlate)) res.get_shared_channel(c/smin)+=I; + break; + case 3 : // Full sum + cimg_pragma_openmp(critical(_correlate)) res.get_shared_channel(0)+=I; + break; + } + } + } else { // Generic version + cimg_pragma_openmp(parallel for cimg_openmp_if(is_outer_parallel)) + for (int c = 0; c I = get_shared_channel(c%_spectrum); + const CImg K = _kernel.get_shared_channel(!channel_mode?c/_spectrum:c%_kernel._spectrum); + CImg _resu = channel_mode<=1?res.get_shared_channel(c): + CImg(res.width(),res.height(),res.depth(),1); + Ttfloat M = 0, M2 = 0; + if (is_normalized) { M = (Ttfloat)K.magnitude(2); M2 = cimg::sqr(M); } + +#define _cimg_correlate_x_int const int ix = xstart + i_xstride*x + i_xdilation*(p - _xcenter) +#define _cimg_correlate_y_int const int iy = ystart + i_ystride*y + i_ydilation*(q - _ycenter) +#define _cimg_correlate_z_int const int iz = zstart + i_zstride*z + i_zdilation*(r - _zcenter) +#define _cimg_correlate_x_float const float ix = xstart + xstride*x + _xdilation*(p - _xcenter) +#define _cimg_correlate_y_float const float iy = ystart + ystride*y + _ydilation*(q - _ycenter) +#define _cimg_correlate_z_float const float iz = zstart + zstride*z + _zdilation*(r - _zcenter) + +#define _cimg_correlate_x_int_dirichlet const bool is_in_x = ix>=0 && ix=0 && iy=0 && iz - CImg& convolve(const CImg& kernel, const bool boundary_conditions=true, const bool is_normalized=false) { + CImg& convolve(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_normalized=false, const unsigned int channel_mode=1, + const int xcenter=(int)(~0U>>1), + const int ycenter=(int)(~0U>>1), + const int zcenter=(int)(~0U>>1), + const int xstart=0, + const int ystart=0, + const int zstart=0, + const int xend=(int)(~0U>>1), + const int yend=(int)(~0U>>1), + const int zend=(int)(~0U>>1), + const float xstride=1, const float ystride=1, const float zstride=1, + const float xdilation=1, const float ydilation=1, const float zdilation=1, + const bool interpolation_type=false) { if (is_empty() || !kernel) return *this; - return get_convolve(kernel,boundary_conditions,is_normalized).move_to(*this); + return get_convolve(kernel,boundary_conditions,is_normalized,channel_mode, + xcenter,ycenter,zcenter,xstart,ystart,zstart,xend,yend,zend, + xstride,ystride,zstride,xdilation,ydilation,zdilation, + interpolation_type).move_to(*this); } //! Convolve image by a kernel \newinstance. template - CImg<_cimg_Ttfloat> get_convolve(const CImg& kernel, const bool boundary_conditions=true, - const bool is_normalized=false) const { - return _correlate(CImg(kernel._data,kernel.size()/kernel._spectrum,1,1,kernel._spectrum,true). - get_mirror('x').resize(kernel,-1),boundary_conditions,is_normalized,true); + CImg<_cimg_Ttfloat> get_convolve(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_normalized=false, const unsigned int channel_mode=1, + const int xcenter=(int)(~0U>>1), + const int ycenter=(int)(~0U>>1), + const int zcenter=(int)(~0U>>1), + const int xstart=0, + const int ystart=0, + const int zstart=0, + const int xend=(int)(~0U>>1), + const int yend=(int)(~0U>>1), + const int zend=(int)(~0U>>1), + const float xstride=1, const float ystride=1, const float zstride=1, + const float xdilation=1, const float ydilation=1, const float zdilation=1, + const bool interpolation_type=false) const { + return _correlate(kernel,boundary_conditions,is_normalized,channel_mode, + xcenter,ycenter,zcenter,xstart,ystart,zstart,xend,yend,zend, + xstride,ystride,zstride,xdilation,ydilation,zdilation, + interpolation_type,true); } //! Cumulate image values, optionally along specified axis. @@ -33889,7 +40397,8 @@ namespace cimg_library_suffixed { CImg& 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth*_spectrum>=16)) cimg_forYZC(*this,y,z,c) { T *ptrd = data(0,y,z,c); Tlong cumul = (Tlong)0; @@ -33898,7 +40407,8 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_height>=(cimg_openmp_sizefactor)*512 && + _width*_depth*_spectrum>=16)) cimg_forXZC(*this,x,z,c) { T *ptrd = data(x,0,z,c); Tlong cumul = (Tlong)0; @@ -33907,7 +40417,8 @@ namespace cimg_library_suffixed { } 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_depth>=(cimg_openmp_sizefactor)*512 && + _width*_depth*_spectrum>=16)) cimg_forXYC(*this,x,y,c) { T *ptrd = data(x,y,0,c); Tlong cumul = (Tlong)0; @@ -33916,14 +40427,15 @@ namespace cimg_library_suffixed { } 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_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(_spectrum>=(cimg_openmp_sizefactor)*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. + default : { // Global cumulation Tlong cumul = (Tlong)0; cimg_for(*this,ptrd,T) { cumul+=(Tlong)*ptrd; *ptrd = (T)cumul; } } @@ -33955,10 +40467,11 @@ namespace cimg_library_suffixed { /** \param kernel Structuring element. \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \param is_real Do the erosion in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). **/ template - CImg& erode(const CImg& kernel, const bool boundary_conditions=true, + CImg& erode(const CImg& kernel, const unsigned int boundary_conditions=1, const bool is_real=false) { if (is_empty() || !kernel) return *this; return get_erode(kernel,boundary_conditions,is_real).move_to(*this); @@ -33966,7 +40479,7 @@ namespace cimg_library_suffixed { //! Erode image by a structuring element \newinstance. template - CImg<_cimg_Tt> get_erode(const CImg& kernel, const bool boundary_conditions=true, + CImg<_cimg_Tt> get_erode(const CImg& kernel, const unsigned int boundary_conditions=1, const bool is_real=false) const { if (is_empty() || !kernel) return *this; if (!is_real && kernel==0) return CImg(width(),height(),depth(),spectrum(),0); @@ -33975,23 +40488,24 @@ namespace cimg_library_suffixed { 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; + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2, + w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(); const bool - is_inner_parallel = _width*_height*_depth>=32768, - is_outer_parallel = res.size()>=32768; + is_inner_parallel = _width*_height*_depth>=(cimg_openmp_sizefactor)*32768, + is_outer_parallel = res.size()>=(cimg_openmp_sizefactor)*32768; cimg::unused(is_inner_parallel,is_outer_parallel); - _cimg_abort_init_omp; + _cimg_abort_init_openmp; cimg_abort_init; cimg_pragma_openmp(parallel for cimg_openmp_if(!is_inner_parallel && is_outer_parallel)) - cimg_forC(res,c) _cimg_abort_try_omp { + cimg_forC(res,c) _cimg_abort_try_openmp { cimg_abort_test; const CImg img = get_shared_channel(c%_spectrum); const CImg K = kernel.get_shared_channel(c%kernel._spectrum); if (is_real) { // Real erosion - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(is_inner_parallel)) for (int z = mz1; z::max(); for (int zm = -mz1; zm<=mz2; ++zm) @@ -34002,45 +40516,50 @@ namespace cimg_library_suffixed { if (cval=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt min_val = cimg::type::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=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt min_val = cimg::type::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); + Tt cval; + switch (boundary_conditions) { + case 0 : cval = (Tt)(img.atXYZ(x + xm,y + ym,z + zm,0,(T)0) - mval); break; + case 1 : cval = (Tt)(img._atXYZ(x + xm,y + ym,z + zm) - mval); break; + case 2 : { + const int + nx = cimg::mod(x + xm,width()), + ny = cimg::mod(y + ym,height()), + nz = cimg::mod(z + zm,depth()); + cval = img(nx,ny,nz) - mval; + } break; + default : { + const int + tx = cimg::mod(x + xm,w2), + ty = cimg::mod(y + ym,h2), + tz = cimg::mod(z + zm,d2), + nx = tx=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt min_val = cimg::type::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::max(); for (int zm = -mz1; zm<=mz2; ++zm) @@ -34051,41 +40570,48 @@ namespace cimg_library_suffixed { if (cval=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt min_val = cimg::type::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=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt min_val = cimg::type::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)) { + Tt cval; + switch (boundary_conditions) { + case 0 : cval = (Tt)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); break; + case 1 : cval = (Tt)img._atXYZ(x + xm,y + ym,z + zm); break; + case 2 : { + const int + nx = cimg::mod(x + xm,width()), + ny = cimg::mod(y + ym,height()), + nz = cimg::mod(z + zm,depth()); + cval = img(nx,ny,nz); + } break; + default : { + const int + tx = cimg::mod(x + xm,w2), + ty = cimg::mod(y + ym,h2), + tz = cimg::mod(z + zm,d2), + nx = tx=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt min_val = cimg::type::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& 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. + 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 buf(L); - cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) + cimg_pragma_openmp(parallel for cimg_openmp_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; + const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + (ulongT)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; } + T *pd = data(0,y,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,k) { *pd = cur; pd+=off; } } else { for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs1 && _height>1) { // Along Y-axis. + 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 buf(L); - cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) + cimg_pragma_openmp(parallel for cimg_openmp_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; + const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + (ulongT)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; } + T *pd = data(x,0,z,c); cur = std::min(cur,*ptrse); cimg_forX(buf,k) { *pd = cur; pd+=off; } } else { for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs1 && _depth>1) { // Along Z-axis. + 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 buf(L); - cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) + cimg_pragma_openmp(parallel for cimg_openmp_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; + const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + (ulongT)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; } + T *pd = data(x,y,0,c); cur = std::min(cur,*ptrse); cimg_forX(buf,k) { *pd = cur; pd+=off; } } else { for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs - CImg& dilate(const CImg& kernel, const bool boundary_conditions=true, + CImg& dilate(const CImg& kernel, const unsigned int boundary_conditions=1, const bool is_real=false) { if (is_empty() || !kernel) return *this; return get_dilate(kernel,boundary_conditions,is_real).move_to(*this); @@ -34257,7 +40784,7 @@ namespace cimg_library_suffixed { //! Dilate image by a structuring element \newinstance. template - CImg<_cimg_Tt> get_dilate(const CImg& kernel, const bool boundary_conditions=true, + CImg<_cimg_Tt> get_dilate(const CImg& kernel, const unsigned int boundary_conditions=1, const bool is_real=false) const { if (is_empty() || !kernel || (!is_real && kernel==0)) return *this; typedef _cimg_Tt Tt; @@ -34265,23 +40792,24 @@ namespace cimg_library_suffixed { 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; + mxe = width() - mx2, mye = height() - my2, mze = depth() - mz2, + w2 = 2*width(), h2 = 2*height(), d2 = 2*depth(); const bool - is_inner_parallel = _width*_height*_depth>=32768, - is_outer_parallel = res.size()>=32768; + is_inner_parallel = _width*_height*_depth>=(cimg_openmp_sizefactor)*32768, + is_outer_parallel = res.size()>=(cimg_openmp_sizefactor)*32768; cimg::unused(is_inner_parallel,is_outer_parallel); - _cimg_abort_init_omp; + _cimg_abort_init_openmp; cimg_abort_init; cimg_pragma_openmp(parallel for cimg_openmp_if(!is_inner_parallel && is_outer_parallel)) - cimg_forC(res,c) _cimg_abort_try_omp { + cimg_forC(res,c) _cimg_abort_try_openmp { cimg_abort_test; const CImg img = get_shared_channel(c%_spectrum); const CImg K = kernel.get_shared_channel(c%kernel._spectrum); if (is_real) { // Real dilation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(is_inner_parallel)) for (int z = mz1; z::min(); for (int zm = -mz1; zm<=mz2; ++zm) @@ -34292,44 +40820,50 @@ namespace cimg_library_suffixed { if (cval>max_val) max_val = cval; } res(x,y,z,c) = max_val; - } _cimg_abort_catch_omp2 - if (boundary_conditions) - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) - cimg_forYZ(res,y,z) _cimg_abort_try_omp2 { - cimg_abort_test2; - for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt max_val = cimg::type::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; + } _cimg_abort_catch_openmp2 + + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forYZ(res,y,z) _cimg_abort_try_openmp2 { + cimg_abort_test2; + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt max_val = cimg::type::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); + Tt cval; + switch (boundary_conditions) { + case 0 : cval = (Tt)(img.atXYZ(x + xm,y + ym,z + zm,0,(T)0) + mval); break; + case 1 : cval = (Tt)(img._atXYZ(x + xm,y + ym,z + zm) + mval); break; + case 2 : { + const int + nx = cimg::mod(x + xm,width()), + ny = cimg::mod(y + ym,height()), + nz = cimg::mod(z + zm,depth()); + cval = img(nx,ny,nz) + mval; + } break; + default : { + const int + tx = cimg::mod(x + xm,w2), + ty = cimg::mod(y + ym,h2), + tz = cimg::mod(z + zm,d2), + nx = tx=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt max_val = cimg::type::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_catch_omp2 + if (cval>max_val) max_val = cval; + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_openmp2 + } else { // Binary dilation - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(is_inner_parallel)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(is_inner_parallel)) for (int z = mz1; z::min(); for (int zm = -mz1; zm<=mz2; ++zm) @@ -34340,41 +40874,48 @@ namespace cimg_library_suffixed { if (cval>max_val) max_val = cval; } res(x,y,z,c) = max_val; - } _cimg_abort_catch_omp2 - if (boundary_conditions) - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(is_inner_parallel)) - cimg_forYZ(res,y,z) _cimg_abort_try_omp2 { - cimg_abort_test2; - for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt max_val = cimg::type::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; + } _cimg_abort_catch_openmp2 + + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(is_inner_parallel)) + cimg_forYZ(res,y,z) _cimg_abort_try_openmp2 { + cimg_abort_test2; + for (int x = 0; x=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { + Tt max_val = cimg::type::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)) { + Tt cval; + switch (boundary_conditions) { + case 0 : cval = (Tt)img.atXYZ(x + xm,y + ym,z + zm,0,(T)0); break; + case 1 : cval = (Tt)img._atXYZ(x + xm,y + ym,z + zm); break; + case 2 : { + const int + nx = cimg::mod(x + xm,width()), + ny = cimg::mod(y + ym,height()), + nz = cimg::mod(z + zm,depth()); + cval = img(nx,ny,nz); + } break; + default : { + const int + tx = cimg::mod(x + xm,w2), + ty = cimg::mod(y + ym,h2), + tz = cimg::mod(z + zm,d2), + nx = tx=mye || z=mze)?++x:((x=mxe)?++x:(x=mxe))) { - Tt max_val = cimg::type::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_catch_omp2 + if (cval>max_val) max_val = cval; + } + } + res(x,y,z,c) = max_val; + } + } _cimg_abort_catch_openmp2 + } - } _cimg_abort_catch_omp + } _cimg_abort_catch_openmp cimg_abort_test; return res; } @@ -34386,21 +40927,21 @@ namespace cimg_library_suffixed { \param sz Depth of the structuring element. **/ CImg& 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. + 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 buf(L); - cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) + cimg_pragma_openmp(parallel for cimg_openmp_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; + const T *const ptrsb = data(0,y,z,c), *ptrs = ptrsb, *const ptrse = ptrs + (ulongT)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; } + T *pd = data(0,y,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,k) { *pd = cur; pd+=off; } } else { for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } @@ -34428,21 +40969,21 @@ namespace cimg_library_suffixed { } } - if (sy>1 && _height>1) { // Along Y-axis. + 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 buf(L); - cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) + cimg_pragma_openmp(parallel for cimg_openmp_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; + const T *const ptrsb = data(x,0,z,c), *ptrs = ptrsb, *const ptrse = ptrs + (ulongT)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; } + T *pd = data(x,0,z,c); cur = std::max(cur,*ptrse); cimg_forX(buf,k) { *pd = cur; pd+=off; } } else { for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } @@ -34470,21 +41011,21 @@ namespace cimg_library_suffixed { } } - if (sz>1 && _depth>1) { // Along Z-axis. + 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 buf(L); - cimg_pragma_openmp(parallel for collapse(3) firstprivate(buf) if (size()>524288)) + cimg_pragma_openmp(parallel for cimg_openmp_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; + const T *const ptrsb = data(x,y,0,c), *ptrs = ptrsb, *const ptrse = ptrs + (ulongT)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; } + T *pd = data(x,y,0,c); cur = std::max(cur,*ptrse); cimg_forX(buf,k) { *pd = cur; pd+=off; } } else { for (int p = s1; p>0 && ptrd<=ptrde; --p) { const T val = *ptrs; if (ptrs=cur) { cur = val; is_first = false; } @@ -34532,11 +41073,169 @@ namespace cimg_library_suffixed { return (+*this).dilate(s); } + //! Apply morphological closing by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + \param is_real Do the closing in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& closing(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_real=false) { + const int sx = kernel.width(), sy = kernel.height(), sz = kernel.depth(); + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + return get_closing(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Apply morphological closing by a structuring element \newinstance. + template + CImg get_closing(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_real=false) const { + const int sx = kernel.width(), sy = kernel.height(), sz = kernel.depth(); + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + const int sx1 = (int)(sx - 1)/2, sy1 = (int)(sy - 1)/2, sz1 = (int)(sz - 1)/2; + CImg res; + if (_depth>1) { // 3D + get_resize(width() + sx + 1,height() + sy + 1,depth() + sz + 1,spectrum(),0,boundary_conditions,0.5,0.5,0.5). + dilate(kernel,1,is_real).erode(kernel,1,is_real). + crop(sx1 + 1,sy1 + 1,sz1 + 1,sx1 + width(),sy1 + height(),sz1 + depth()).move_to(res); + } else if (_height>1) { // 2D + get_resize(width() + sx + 1,height() + sy + 1,1,spectrum(),0,boundary_conditions,0.5,0.5). + dilate(kernel,1,is_real).erode(kernel,1,is_real). + crop(sx1 + 1,sy1 + 1,sx1 + width(),sy1 + height()).move_to(res); + } else if (_width>1) { // 1D + get_resize(width() + sx + 1,1,1,spectrum(),0,boundary_conditions,0.5). + dilate(kernel,1,is_real).erode(kernel,1,is_real). + crop(sx1 + 1,sx1 + width()).move_to(res); + } + return res; + } + + //! Apply morphological closing by a rectangular structuring element of specified size. + CImg& closing(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + return get_closing(sx,sy,sz).move_to(*this); + } + + //! Apply morphological closing by a rectangular structuring element of specified size \newinstance. + CImg get_closing(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + const int sx1 = (int)(sx - 1)/2, sy1 = (int)(sy - 1)/2, sz1 = (int)(sz - 1)/2; + CImg res; + if (_depth>1) { // 3D + get_resize(width() + sx + 1,height() + sy + 1,depth() + sz + 1,spectrum(),0,1,0.5,0.5,0.5). + dilate(sx,sy,sz).erode(sx,sy,sz). + crop(sx1 + 1,sy1 + 1,sz1 + 1,sx1 + width(),sy1 + height(),sz1 + depth()).move_to(res); + } else if (_height>1) { // 2D + get_resize(width() + sx + 1,height() + sy + 1,1,spectrum(),0,1,0.5,0.5). + dilate(sx,sy).erode(sx,sy). + crop(sx1 + 1,sy1 + 1,sx1 + width(),sy1 + height()).move_to(res); + } else if (_width>1) { // 1D + get_resize(width() + sx + 1,1,1,spectrum(),0,1,0.5). + dilate(sx,1).erode(sx,1). + crop(sx1 + 1,sx1 + width()).move_to(res); + } + return res; + } + + //! Apply morphological closing by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& closing(const unsigned int s) { + return closing(s,s,s); + } + + //! Apply morphological closing by a square structuring element of specified size \newinstance. + CImg get_closing(const unsigned int s) const { + return (+*this).closing(s); + } + + //! Apply morphological opening by a structuring element. + /** + \param kernel Structuring element. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. + \param is_real Do the opening in real (a.k.a 'non-flat') mode (\c true) rather than binary mode (\c false). + **/ + template + CImg& opening(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_real=false) { + const int sx = kernel.width(), sy = kernel.height(), sz = kernel.depth(); + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + return get_opening(kernel,boundary_conditions,is_real).move_to(*this); + } + + //! Apply morphological opening by a structuring element \newinstance. + template + CImg get_opening(const CImg& kernel, const unsigned int boundary_conditions=1, + const bool is_real=false) const { + const int sx = kernel.width(), sy = kernel.height(), sz = kernel.depth(); + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + const int sx1 = (int)(sx - 1)/2, sy1 = (int)(sy - 1)/2, sz1 = (int)(sz - 1)/2; + CImg res; + if (_depth>1) { // 3D + get_resize(width() + sx + 1,height() + sy + 1,depth() + sz + 1,spectrum(),0,boundary_conditions,0.5,0.5,0.5). + erode(kernel,1,is_real).dilate(kernel,1,is_real). + crop(sx1 + 1,sy1 + 1,sz1 + 1,sx1 + width(),sy1 + height(),sz1 + depth()).move_to(res); + } else if (_height>1) { // 2D + get_resize(width() + sx + 1,height() + sy + 1,1,spectrum(),0,boundary_conditions,0.5,0.5). + erode(kernel,1,is_real).dilate(kernel,1,is_real). + crop(sx1 + 1,sy1 + 1,sx1 + width(),sy1 + height()).move_to(res); + } else if (_width>1) { // 1D + get_resize(width() + sx + 1,1,1,spectrum(),0,boundary_conditions,0.5). + erode(kernel,1,is_real).dilate(kernel,1,is_real). + crop(sx1 + 1,sx1 + width()).move_to(res); + } + return res; + } + + //! Apply morphological opening by a rectangular structuring element of specified size. + CImg& opening(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) { + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + return get_opening(sx,sy,sz).move_to(*this); + } + + //! Apply morphological opening by a rectangular structuring element of specified size \newinstance. + CImg get_opening(const unsigned int sx, const unsigned int sy, const unsigned int sz=1) const { + if (is_empty() || (sx<=1 && sy<=1 && sz<=1)) return *this; + const int sx1 = (int)(sx - 1)/2, sy1 = (int)(sy - 1)/2, sz1 = (int)(sz - 1)/2; + CImg res; + if (_depth>1) { // 3D + get_resize(width() + sx + 1,height() + sy + 1,depth() + sz + 1,spectrum(),0,1,0.5,0.5,0.5). + erode(sx,sy,sz).dilate(sx,sy,sz). + crop(sx1 + 1,sy1 + 1,sz1 + 1,sx1 + width(),sy1 + height(),sz1 + depth()).move_to(res); + } else if (_height>1) { // 2D + get_resize(width() + sx + 1,height() + sy + 1,1,spectrum(),0,1,0.5,0.5). + erode(sx,sy).dilate(sx,sy). + crop(sx1 + 1,sy1 + 1,sx1 + width(),sy1 + height()).move_to(res); + } else if (_width>1) { // 1D + get_resize(width() + sx + 1,1,1,spectrum(),0,1,0.5). + erode(sx,1).dilate(sx,1). + crop(sx1 + 1,sx1 + width()).move_to(res); + } + return res; + } + + //! Apply morphological opening by a square structuring element of specified size. + /** + \param s Size of the structuring element. + **/ + CImg& opening(const unsigned int s) { + return opening(s,s,s); + } + + //! Apply morphological opening by a square structuring element of specified size \newinstance. + CImg get_opening(const unsigned int s) const { + return (+*this).opening(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. + 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. **/ @@ -34550,7 +41249,7 @@ namespace cimg_library_suffixed { 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=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; @@ -34639,7 +41338,7 @@ namespace cimg_library_suffixed { unsigned int xs, ys, zs, ns, nmin = 0; float d, dmin = cimg::type::inf(); - T label = (T)0; + T nlabel = (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); @@ -34672,7 +41371,7 @@ namespace cimg_library_suffixed { _cimg_watershed_propagate(is_nx && is_ny && is_nz,nx,ny,nz); } } - (*this)(x,y,z) = label; + (*this)(x,y,z) = nlabel; labels(x,y,z) = ++nmin; } return *this; @@ -34696,7 +41395,7 @@ namespace cimg_library_suffixed { (*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) { + for (unsigned int pos = siz - 1, par = 0; pos && value>(tv)(*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)); @@ -34711,31 +41410,18 @@ namespace cimg_library_suffixed { (*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))(*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; - } - } + unsigned int pos = 0, swap = 0; + do { + const unsigned int left = 2*pos + 1, right = left + 1; + if (right(*this)(right,0)?left:right; + else if (left{ 0=smooth-filter | 1=1st-derivative | 2=2nd-derivative }. \param axis Axis along which the filter is computed. Can be { 'x' | 'y' | 'z' | 'c' }. - \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. **/ CImg& deriche(const float sigma, const unsigned int order=0, const char axis='x', - const bool boundary_conditions=true) { + const unsigned int boundary_conditions=1) { #define _cimg_deriche_apply \ - CImg Y(N); \ - Tfloat *ptrY = Y._data, yb = 0, yp = 0; \ + CImg Y(N); \ + double *ptrY = Y._data, yb = 0, yp = 0; \ T xp = (T)0; \ - if (boundary_conditions) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \ + if (boundary_conditions) { xp = *ptrX; yb = yp = (double)(coefp*xp); } \ for (int m = 0; m=0; --n) { \ const T xc = *(ptrX-=off); \ - const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \ + const double yc = (double)(a2*xn + a3*xa - b1*yn - b2*ya); \ xa = xn; xn = xc; ya = yn; yn = yc; \ *ptrX = (T)(*(--ptrY)+yc); \ } + + if (order>2) + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified order '%d' " + "('order' can be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).", + cimg_instance, + order); + 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 (naxis!='x' && naxis!='y' && naxis!='z' && naxis!='c') + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified axis '%c'.", + cimg_instance, + axis); + const double + nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width: + naxis=='y'?_height: + naxis=='z'?_depth:_spectrum)/100, + nnsigma = nsigma<0.1f?0.1f:nsigma; + if (is_empty() || (nsigma<0.1f && !order)) return *this; - const float - nnsigma = nsigma<0.1f?0.1f:nsigma, + if (boundary_conditions>1) { + const int w = width(), h = height(), d = depth(), s = spectrum(), border = (int)cimg::round(1 + 3*nnsigma); + switch (naxis) { + case 'x' : + return draw_image(get_resize(w + 2*border,h,d,s,0,boundary_conditions,0.5). + deriche(nnsigma,order,naxis,1).columns(border,w - 1 + border)); + case 'y' : + return draw_image(get_resize(w,h + 2*border,d,s,0,boundary_conditions,0,0.5). + deriche(nnsigma,order,naxis,1).rows(border,h - 1 + border)); + case 'z' : + return draw_image(get_resize(w,h,d + 2*border,s,0,boundary_conditions,0,0,0.5). + deriche(nnsigma,order,naxis,1).slices(border,d - 1 + border)); + default : + return draw_image(get_resize(w,h,d,s + 2*border,0,boundary_conditions,0,0,0,0.5). + deriche(nnsigma,order,naxis,1).channels(border,d - 1 + border)); + } + } + + const double alpha = 1.695f/nnsigma, - ema = (float)std::exp(-alpha), - ema2 = (float)std::exp(-2*alpha), + ema = std::exp(-alpha), + ema2 = std::exp(-2*alpha), b1 = -2*ema, b2 = ema2; - float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0; + double 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); + const double 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; + const double 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), + default : { + const double + ea = 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; @@ -34802,38 +41524,37 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && + _height*_depth*_spectrum>=16)) cimg_forXYZ(*this,x,y,z) { T *ptrX = data(x,y,z,0); _cimg_deriche_apply; } } } @@ -34842,7 +41563,7 @@ namespace cimg_library_suffixed { //! Apply recursive Deriche filter \newinstance. CImg get_deriche(const float sigma, const unsigned int order=0, const char axis='x', - const bool boundary_conditions=true) const { + const unsigned int boundary_conditions=1) const { return CImg(*this,false).deriche(sigma,order,axis,boundary_conditions); } @@ -34852,146 +41573,147 @@ namespace cimg_library_suffixed { \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 { 0=dirichlet | 1=neumann }. + \param order the order of the filter 0 (smoothing), 1st derivative, 2nd derivative, 3rd derivative + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann }. \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 unsigned int order, const bool boundary_conditions) { + double val[4] = {}; // 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) ); + sumsq = filter[0], sum = sumsq * sumsq, + a1 = filter[1], a2 = filter[2], a3 = filter[3], + scaleM = 1. / ( (1. + a1 - a2 + a3) * (1. - a1 - a2 - a3) * (1. + a2 + (a1 - a3) * a3) ); double M[9]; // Triggs matrix - M[0] = scaleM * (-a3 * a1 + 1.0 - a3 * a3 - a2); + M[0] = scaleM * (-a3 * a1 + 1. - 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[4] = -scaleM * (a2 - 1.) * (a2 + a3 * a1); + M[5] = -scaleM * a3 * (a3 * a1 + a3 * a3 + a2 - 1.); 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; n0; --k) val[k] = val[k - 1]; - } - if (!pass) data -= off; - } + 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. - a1 - a2 - a3), vplus = uplus/(1. - 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; n0; --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; n0; --k) x[k] = x[k - 1]; - } else { data-=off;} - for (int k = 3; k>0; --k) val[k] = val[k - 1]; - } - *data = (T)0; - } + 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; n0; --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; n0; --k) x[k] = x[k - 1]; - for (int k = 3; k>0; --k) val[k] = val[k - 1]; - } - *data = (T)0; - } + 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; n0; --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; n0; --k) x[k] = x[k - 1]; - for (int k = 3; k>0; --k) val[k] = val[k - 1]; - } - *data = (T)0; - } + 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; n0; --k) x[k] = x[k - 1]; + for (int k = 3; k>0; --k) val[k] = val[k - 1]; + } + *data = (T)0; + } } break; } } @@ -35001,7 +41723,8 @@ namespace cimg_library_suffixed { \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 { 'x' | 'y' | 'z' | 'c' }. - \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \note dirichlet boundary condition has a strange behavior I.T. Young, L.J. van Vliet, M. van Ginkel, Recursive Gabor filtering. @@ -35015,48 +41738,88 @@ namespace cimg_library_suffixed { vol. 54, pp. 2365-2367, 2006. **/ CImg& vanvliet(const float sigma, const unsigned int order, const char axis='x', - const bool boundary_conditions=true) { - if (is_empty()) return *this; + const unsigned int boundary_conditions=1) { + + if (order>2) + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified order '%d' " + "('order' can be { 0=smoothing | 1=1st-derivative | 2=2nd-derivative }).", + cimg_instance, + order); + + const char naxis = cimg::lowercase(axis); + if (naxis!='x' && naxis!='y' && naxis!='z' && naxis!='c') + throw CImgArgumentException(_cimg_instance + "deriche(): Invalid specified axis '%c'.", + cimg_instance, + axis); + const double + nsigma = sigma>=0?sigma:-sigma*(naxis=='x'?_width: + naxis=='y'?_height: + naxis=='z'?_depth:_spectrum)/100, + nnsigma = nsigma<0.5f?0.5f:nsigma; + + if (is_empty() || (nsigma<0.1f && !order)) return *this; + if (nsigma<0.5f) return deriche(nsigma,order,axis,boundary_conditions); if (!cimg::type::is_float()) return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions).move_to(*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; + + if (boundary_conditions>1) { + const int w = width(), h = height(), d = depth(), s = spectrum(), border = (int)cimg::round(1 + 3*nnsigma); + switch (naxis) { + case 'x' : + return draw_image(get_resize(w + 2*border,h,d,s,0,boundary_conditions,0.5). + vanvliet(nnsigma,order,naxis,1).columns(border,w - 1 + border)); + case 'y' : + return draw_image(get_resize(w,h + 2*border,d,s,0,boundary_conditions,0,0.5). + vanvliet(nnsigma,order,naxis,1).rows(border,h - 1 + border)); + case 'z' : + return draw_image(get_resize(w,h,d + 2*border,s,0,boundary_conditions,0,0,0.5). + vanvliet(nnsigma,order,naxis,1).slices(border,d - 1 + border)); + default : + return draw_image(get_resize(w,h,d,s + 2*border,0,boundary_conditions,0,0,0,0.5). + vanvliet(nnsigma,order,naxis,1).channels(border,d - 1 + border)); + } + } + const double - nnsigma = nsigma<0.5f?0.5f:nsigma, - m0 = 1.16680, m1 = 1.10783, m2 = 1.40586, + 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; + 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); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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; @@ -35064,7 +41827,7 @@ namespace cimg_library_suffixed { //! Blur image using Van Vliet recursive Gaussian filter. \newinstance. CImg get_vanvliet(const float sigma, const unsigned int order, const char axis='x', - const bool boundary_conditions=true) const { + const unsigned int boundary_conditions=1) const { return CImg(*this,false).vanvliet(sigma,order,axis,boundary_conditions); } @@ -35073,15 +41836,16 @@ namespace cimg_library_suffixed { \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 { false=dirichlet | true=neumann }. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \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. + - The blur is computed as a 0-order Vanvliet (gaussian) or Deriche filter (quasi-gaussian). - This is a recursive algorithm, not depending on the values of the standard deviations. \see deriche(), vanvliet(). **/ CImg& blur(const float sigma_x, const float sigma_y, const float sigma_z, - const bool boundary_conditions=true, const bool is_gaussian=false) { + const unsigned int boundary_conditions=1, const bool is_gaussian=true) { if (is_empty()) return *this; if (is_gaussian) { if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions); @@ -35097,24 +41861,26 @@ namespace cimg_library_suffixed { //! Blur image \newinstance. CImg 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 { + const unsigned int boundary_conditions=1, const bool is_gaussian=true) const { return CImg(*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 { 0=dirichlet | 1=neumann }.a - \param is_gaussian Use a gaussian kernel (VanVliet) is set, a pseudo-gaussian (Deriche) otherwise. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }.a + \param is_gaussian Use a gaussian kernel (VanVliet) is set, a quasi-gaussian (Deriche) otherwise. \see deriche(), vanvliet(). **/ - CImg& blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) { + CImg& blur(const float sigma, const unsigned int boundary_conditions=1, const bool is_gaussian=true) { 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 get_blur(const float sigma, const bool boundary_conditions=true, const bool is_gaussian=false) const { + CImg get_blur(const float sigma, const unsigned int boundary_conditions=1, + const bool is_gaussian=true) const { return CImg(*this,false).blur(sigma,boundary_conditions,is_gaussian); } @@ -35146,14 +41912,14 @@ namespace cimg_library_suffixed { unsigned int iamplitude = cimg::round(namplitude); const bool is_3d = (G._spectrum==6); T val_min, val_max = max_min(val_min); - _cimg_abort_init_omp; + _cimg_abort_init_openmp; cimg_abort_init; if (da<=0) { // Iterated oriented Laplacians CImg velocity(_width,_height,_depth,_spectrum); for (unsigned int iteration = 0; iterationveloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; } } - else // 2d version + else // 2D version cimg_forZC(*this,z,c) { cimg_abort_test; CImg_3x3(I,Tfloat); @@ -35187,16 +41953,16 @@ namespace cimg_library_suffixed { } if (veloc_max>0) *this+=(velocity*=dl/veloc_max); } - } else { // LIC-based smoothing. + } else { // LIC-based smoothing const ulongT whd = (ulongT)_width*_height*_depth; const float sqrt2amplitude = (float)std::sqrt(2*namplitude); const int dx1 = width() - 1, dy1 = height() - 1, dz1 = depth() - 1; CImg 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 = cimg::mod(180.0f,da)/2.0f; phi<=180; phi+=da) { + if (is_3d) { // 3D version + for (float phi = cimg::mod(180.f,da)/2.f; phi<=180; phi+=da) { const float phir = (float)(phi*cimg::PI/180), datmp = (float)(da/std::cos(phir)), - da2 = datmp<1?360.0f:datmp; + da2 = datmp<1?360.f:datmp; for (float theta = 0; theta<360; (theta+=da2),++N) { const float thetar = (float)(theta*cimg::PI/180), @@ -35222,9 +41988,10 @@ namespace cimg_library_suffixed { } cimg_abort_test; - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=256 && _height*_depth>=2) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && _height*_depth>=2) firstprivate(val)) - cimg_forYZ(*this,y,z) _cimg_abort_try_omp2 { + cimg_forYZ(*this,y,z) _cimg_abort_try_openmp2 { cimg_abort_test2; cimg_forX(*this,x) { val.fill(0); @@ -35296,11 +42063,11 @@ namespace cimg_library_suffixed { 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_catch_omp2 + } _cimg_abort_catch_openmp2 } } - } else { // 2d LIC algorithm - for (float theta = cimg::mod(360.0f,da)/2.0f; theta<360; (theta+=da),++N) { + } else { // 2D LIC algorithm + for (float theta = cimg::mod(360.f,da)/2.f; 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); @@ -35318,8 +42085,9 @@ namespace cimg_library_suffixed { } cimg_abort_test; - cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256 && _height>=2) firstprivate(val)) - cimg_forY(*this,y) _cimg_abort_try_omp2 { + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && _height>=2) + firstprivate(val)) + cimg_forY(*this,y) _cimg_abort_try_openmp2 { cimg_abort_test2; cimg_forX(*this,x) { val.fill(0); @@ -35385,13 +42153,13 @@ namespace cimg_library_suffixed { 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_catch_omp2 + } _cimg_abort_catch_openmp2 } } const Tfloat *ptrs = res._data; cimg_for(*this,ptrd,T) { - const Tfloat val = *(ptrs++)/N; - *ptrd = valval_max?val_max:(T)val); + const Tfloat _val = *(ptrs++)/N; + *ptrd = _valval_max?val_max:(T)_val); } } cimg_abort_test; @@ -35457,7 +42225,7 @@ namespace cimg_library_suffixed { \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). + (extended for 3D volumetric images). It is based on the reference implementation http://people.csail.mit.edu/jiawen/software/bilateralFilter.m **/ template @@ -35479,10 +42247,10 @@ namespace cimg_library_suffixed { _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), + _sigma_r = sigma_r>=0?sigma_r:-sigma_r*edge_delta/100, + _sampling_x = sampling_x?sampling_x:std::max(_sigma_x,1.f), + _sampling_y = sampling_y?sampling_y:std::max(_sigma_y,1.f), + _sampling_z = sampling_z?sampling_z:std::max(_sigma_z,1.f), _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, @@ -35500,7 +42268,7 @@ namespace cimg_library_suffixed { 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 + if (is_3d) { // 3D version of the algorithm CImg bgrid(bx,by,bz,br), bgridw(bx,by,bz,br); cimg_forC(*this,c) { const CImg _guide = guide.get_shared_channel(c%guide._spectrum); @@ -35519,7 +42287,7 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(size(),4096)) cimg_forXYZ(*this,x,y,z) { const float edge = (float)_guide(x,y,z); const float @@ -35531,7 +42299,7 @@ namespace cimg_library_suffixed { (*this)(x,y,z,c) = (T)(bval0/bval1); } } - } else { // 2d version of the algorithm + } else { // 2D version of the algorithm CImg bgrid(bx,by,br,2); cimg_forC(*this,c) { const CImg _guide = guide.get_shared_channel(c%guide._spectrum); @@ -35548,7 +42316,7 @@ namespace cimg_library_suffixed { } 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_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(size(),4096)) cimg_forXY(*this,x,y) { const float edge = (float)_guide(x,y); const float @@ -35605,12 +42373,15 @@ namespace cimg_library_suffixed { \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 { 0=dirichlet | 1=neumann }. + \param order the order of the filter 0 (smoothing), 1st derivative and 2nd derivative. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. */ 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 int order, const unsigned int boundary_conditions, const unsigned int nb_iter) { + const int nboundary_conditions = boundary_conditions>1 && boxsize<=3?1:boundary_conditions; + // Smooth. if (boxsize>1 && nb_iter) { const int w2 = (int)(boxsize - 1)/2; @@ -35620,13 +42391,13 @@ namespace cimg_library_suffixed { for (unsigned int iter = 0; iter=N) return boundary_conditions?ptr[(N - 1)*off]:T(); - return ptr[x*off]; + const unsigned int boundary_conditions, const int x) { + switch (boundary_conditions) { + case 0 : // Dirichlet + return x<0 || x>=N?(T)0:ptr[x*off]; + case 1 : { // Neumann + const int nx = x<0?0:x>=N?N - 1:x; + return ptr[nx*off]; + } + case 2 : { // Periodic + const int nx = cimg::mod(x,N); + return ptr[nx*off]; + } + default : { // Mirror + const int + N2 = 2*N, + tx = cimg::mod(x,N2), + nx = tx{ 'x' | 'y' | 'z' | 'c' }. - \param boundary_conditions Boundary conditions. Can be { 0=dirichlet | 1=neumann }. + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }. \param nb_iter Number of filter iterations. **/ CImg& boxfilter(const float boxsize, const int order, const char axis='x', - const bool boundary_conditions=true, + const unsigned int boundary_conditions=1, 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; + if (is_empty() || !nboxsize || (nboxsize<=1 && !order)) return *this; switch (naxis) { case 'x' : { - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width>=256 && _height*_depth*_spectrum>=16)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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); @@ -35726,7 +42519,7 @@ namespace cimg_library_suffixed { // Apply box filter of order 0,1 or 2 \newinstance. CImg get_boxfilter(const float boxsize, const int order, const char axis='x', - const bool boundary_conditions=true, + const unsigned int boundary_conditions=1, const unsigned int nb_iter=1) const { return CImg(*this,false).boxfilter(boxsize,order,axis,boundary_conditions,nb_iter); } @@ -35736,14 +42529,15 @@ namespace cimg_library_suffixed { \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 { false=dirichlet | true=neumann }. + \param boundary_conditions Boundary conditions. + Can be { false=dirichlet | true=neumann | 2=periodic | 3=mirror }. \param nb_iter Number of filter iterations. \note - This is a recursive algorithm, not depending on the values of the box kernel size. \see blur(). **/ CImg& blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, - const bool boundary_conditions=true, + const unsigned int boundary_conditions=1, const unsigned int nb_iter=1) { if (is_empty()) return *this; if (_width>1) boxfilter(boxsize_x,0,'x',boundary_conditions,nb_iter); @@ -35754,23 +42548,24 @@ namespace cimg_library_suffixed { //! Blur image with a box filter \newinstance. CImg get_blur_box(const float boxsize_x, const float boxsize_y, const float boxsize_z, - const bool boundary_conditions=true) const { + const unsigned int boundary_conditions=1) const { return CImg(*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 { 0=dirichlet | 1=neumann }.a + \param boundary_conditions Boundary conditions. + Can be { 0=dirichlet | 1=neumann | 2=periodic | 3=mirror }.a \see deriche(), vanvliet(). **/ - CImg& blur_box(const float boxsize, const bool boundary_conditions=true) { + CImg& blur_box(const float boxsize, const unsigned int boundary_conditions=1) { 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 get_blur_box(const float boxsize, const bool boundary_conditions=true) const { + CImg get_blur_box(const float boxsize, const unsigned int boundary_conditions=1) const { return CImg(*this,false).blur_box(boxsize,boundary_conditions); } @@ -35821,168 +42616,217 @@ namespace cimg_library_suffixed { //! Blur image using patch-based space. /** + \param guide Image used to model the smoothing weights. \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 sigma_r Amount of blur along the value axis. + \param patch_size Size of the patches. + \param lookup_size Size of the window to search similar patches. \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& blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, + template + CImg& blur_patch(const CImg& guide, + const float sigma_s, const float sigma_r, 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); + return get_blur_patch(guide,sigma_s,sigma_r,patch_size,lookup_size,smoothness,is_fast_approx).move_to(*this); } //! Blur image using patch-based space \newinstance. - CImg get_blur_patch(const float sigma_s, const float sigma_p, const unsigned int patch_size=3, + template + CImg get_blur_patch(const CImg& guide, + const float sigma_s, const float sigma_r, 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; } \ +#define _cimg_blur_patch3d_fast(N) { \ + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) \ + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) \ + firstprivate(P,Q)) \ + cimg_forXYZ(res,x,y,z) _cimg_abort_try_openmp2 { \ + cimg_abort_test2; \ + cimg_def##N##x##N##x##N(res,x,y,z); \ + tfloat *pP = P._data; cimg_forC(_guide,c) { cimg_get##N##x##N##x##N(_guide,x,y,z,c,pP,tfloat); 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; \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + tfloat sum_weights = 0; \ cimg_for_in##N##XYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) \ - if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.0f:1.0f; \ + const tfloat dx = (tfloat)p - x, dy = (tfloat)q - y, dz = (tfloat)r - z, \ + alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = alldist>3?0:1; \ sum_weights+=weight; \ - cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); \ + cimg_forC(res,c) res(x,y,z,c)+=(Tfloat)weight*(*this)(p,q,r,c); \ } \ - if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; \ + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,z,c)/=(Tfloat)sum_weights; \ else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ - } + } _cimg_abort_catch_openmp2 } -#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; } \ +#define _cimg_blur_patch3d(N) { \ + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) \ + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) \ + firstprivate(P,Q)) \ + cimg_forXYZ(res,x,y,z) _cimg_abort_try_openmp2 { \ + cimg_abort_test2; \ + cimg_def##N##x##N##x##N(res,x,y,z); \ + tfloat *pP = P._data; cimg_forC(_guide,c) { cimg_get##N##x##N##x##N(_guide,x,y,z,c,pP,tfloat); 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; \ + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \ + tfloat 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; } \ + tfloat *pQ = Q._data; cimg_forC(_guide,c) { cimg_get##N##x##N##x##N(_guide,p,q,r,c,pQ,tfloat); pQ+=N3; } \ + tfloat distance2 = 0; \ + pQ = Q._data; cimg_for(P,_pP,tfloat) { const tfloat dI = *_pP - *(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); \ + const tfloat dx = (tfloat)p - x, dy = (tfloat)q - y, dz = (tfloat)r - z, \ + alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = 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); \ + cimg_forC(res,c) res(x,y,z,c)+=(Tfloat)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; \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=(Tfloat)weight_max*(*this)(x,y,z,c); \ + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,z,c)/=(Tfloat)sum_weights; \ else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); \ - } + } _cimg_abort_catch_openmp2 } -#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; } \ +#define _cimg_blur_patch2d_fast(N) { \ + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) \ + firstprivate(P,Q)) \ + cimg_forXY(res,x,y) _cimg_abort_try_openmp2 { \ + cimg_abort_test2; \ + cimg_def##N##x##N(res,x,y); \ + tfloat *pP = P._data; cimg_forC(_guide,c) { cimg_get##N##x##N(_guide,x,y,0,c,pP,tfloat); pP+=N2; } \ const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ - float sum_weights = 0; \ + tfloat sum_weights = 0; \ cimg_for_in##N##XY(res,x0,y0,x1,y1,p,q) \ - if (cimg::abs((Tfloat)img(x,y,0,0) - (Tfloat)img(p,q,0,0))3?0.0f:1.0f; \ + const tfloat dx = (tfloat)p - x, dy = (tfloat)q - y, \ + alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = alldist>3?0:1; \ sum_weights+=weight; \ - cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); \ + cimg_forC(res,c) res(x,y,c)+=(Tfloat)weight*(*this)(p,q,c); \ } \ - if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; \ + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,c)/=(Tfloat)sum_weights; \ else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ - } + } _cimg_abort_catch_openmp2 } -#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; } \ +#define _cimg_blur_patch2d(N) { \ + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) \ + firstprivate(P,Q)) \ + cimg_forXY(res,x,y) _cimg_abort_try_openmp2 { \ + cimg_abort_test2; \ + cimg_def##N##x##N(res,x,y); \ + tfloat *pP = P._data; cimg_forC(_guide,c) { cimg_get##N##x##N(_guide,x,y,0,c,pP,tfloat); pP+=N2; } \ const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2; \ - float sum_weights = 0, weight_max = 0; \ + tfloat 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; } \ + tfloat *pQ = Q._data; cimg_forC(_guide,c) { cimg_get##N##x##N(_guide,p,q,0,c,pQ,tfloat); pQ+=N2; } \ + tfloat distance2 = 0; \ + pQ = Q._data; cimg_for(P,_pP,tfloat) { const tfloat dI = *_pP - *(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); \ + const tfloat dx = (tfloat)p - x, dy = (tfloat)q - y, \ + alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = 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); \ + cimg_forC(res,c) res(x,y,c)+=(Tfloat)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; \ + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=(Tfloat)weight_max*(*this)(x,y,c); \ + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,c)/=(Tfloat)sum_weights; \ else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); \ - } + } _cimg_abort_catch_openmp2 } + typedef _cimg_tfloat tfloat; + if (!is_sameXYZ(guide)) + throw CImgArgumentException(_cimg_instance + "blur_patch(): 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() || !patch_size || !lookup_size) return +*this; + Tfloat val_min, val_max = (Tfloat)max_min(val_min); + _cimg_abort_init_openmp; + cimg_abort_init; + CImg res(_width,_height,_depth,_spectrum,0); - const CImg _img = smoothness>0?get_blur(smoothness):CImg(),&img = smoothness>0?_img:*this; - CImg P(patch_size*patch_size*_spectrum), Q(P); + const CImg + __guide = guide?CImg(guide,guide.pixel_type()==cimg::type::string()): + CImg(*this,pixel_type()==cimg::type::string()), + _guide = smoothness>0?__guide.get_blur(smoothness):__guide.get_shared(); + CImg P(_guide._spectrum*patch_size*patch_size*(_depth>1?patch_size:1)), Q(P); + + t guide_min = (t)0, guide_max = (t)0; + if (sigma_r<0) guide_max = guide.max_min(guide_min); 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; + guide_delta = (float)(guide_max - guide_min), + _sigma_s = sigma_s>=0?sigma_s:-sigma_s*cimg::max(_width,_height,_depth)/100, + _sigma_r = sigma_r>=0?sigma_r:-sigma_r*guide_delta/100, + sigma_s2 = _sigma_s*_sigma_s, + sigma_r2 = _sigma_r*_sigma_r, + sigma_r3 = 3*_sigma_r, + Pnorm = P.size()*sigma_r2; 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 + 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); + if (is_fast_approx) { + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) + firstprivate(P,Q)) + cimg_forXYZ(res,x,y,z) _cimg_abort_try_openmp2 { // Fast + cimg_abort_test2; + P = _guide.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; + tfloat sum_weights = 0; cimg_for_inXYZ(res,x0,y0,z0,x1,y1,z1,p,q,r) - if (cimg::abs((Tfloat)img(x,y,z,0) - (Tfloat)img(p,q,r,0))3?0.0f:1.0f; + if (cimg::abs(_guide(x,y,z,0) - _guide(p,q,r,0))3?0:1; sum_weights+=weight; - cimg_forC(res,c) res(x,y,z,c)+=weight*(*this)(p,q,r,c); + cimg_forC(res,c) res(x,y,z,c)+=(Tfloat)weight*(*this)(p,q,r,c); } - if (sum_weights>0) cimg_forC(res,c) res(x,y,z,c)/=sum_weights; + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,z,c)/=(Tfloat)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); + } _cimg_abort_catch_openmp2 + } else { + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height*res._depth>=4) + firstprivate(P,Q)) + cimg_forXYZ(res,x,y,z) _cimg_abort_try_openmp2 { // Exact + cimg_abort_test2; + P = _guide.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; + x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; + tfloat 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); + (Q = _guide.get_crop(p - psize1,q - psize1,r - psize1,p + psize2,q + psize2,r + psize2,true))-=P; + const tfloat + dx = (tfloat)x - p, dy = (tfloat)y - q, dz = (tfloat)z - r, + distance2 = (tfloat)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2), + weight = 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); + cimg_forC(res,c) res(x,y,z,c)+=(Tfloat)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; + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,z,c)+=(Tfloat)weight_max*(*this)(x,y,z,c); + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,z,c)/=(Tfloat)sum_weights; else cimg_forC(res,c) res(x,y,z,c) = (Tfloat)((*this)(x,y,z,c)); - } + } _cimg_abort_catch_openmp2 + } } - } else switch (patch_size) { // 2d + } 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; @@ -35993,47 +42837,67 @@ namespace cimg_library_suffixed { 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); + if (is_fast_approx) { + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) _cimg_abort_try_openmp2 { // Fast + cimg_abort_test2; + P = _guide.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; + tfloat sum_weights = 0; cimg_for_inXY(res,x0,y0,x1,y1,p,q) - if ((Tfloat)cimg::abs(img(x,y,0) - (Tfloat)img(p,q,0))3?0.0f:1.0f; + if (cimg::abs(_guide(x,y,0) - _guide(p,q,0))3?0:1; sum_weights+=weight; - cimg_forC(res,c) res(x,y,c)+=weight*(*this)(p,q,c); + cimg_forC(res,c) res(x,y,c)+=(Tfloat)weight*(*this)(p,q,c); } - if (sum_weights>0) cimg_forC(res,c) res(x,y,c)/=sum_weights; + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,c)/=(Tfloat)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); + } _cimg_abort_catch_openmp2 + } else { + cimg_pragma_openmp(parallel for cimg_openmp_if(res._width>=(cimg_openmp_sizefactor)*32 && res._height>=4) + firstprivate(P,Q)) + cimg_forXY(res,x,y) _cimg_abort_try_openmp2 { // Exact + cimg_abort_test2; + P = _guide.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; + tfloat 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); + (Q = _guide.get_crop(p - psize1,q - psize1,p + psize2,q + psize2,true))-=P; + const tfloat + dx = (tfloat)x - p, dy = (tfloat)y - q, + distance2 = (tfloat)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2), + weight = 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); + cimg_forC(res,c) res(x,y,c)+=(Tfloat)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)); - } + sum_weights+=weight_max; cimg_forC(res,c) res(x,y,c)+=(Tfloat)weight_max*(*this)(x,y,c); + if (sum_weights>1e-10) cimg_forC(res,c) res(x,y,c)/=(Tfloat)sum_weights; + else cimg_forC(res,c) res(x,y,c) = (Tfloat)((*this)(x,y,c)); + } _cimg_abort_catch_openmp2 + } } } - return res; + cimg_abort_test; + return res.cut(val_min,val_max); + } + + //! Blur image using patch-based space \simplification. + CImg& blur_patch(const float sigma_s, const float sigma_r, const unsigned int patch_size=3, + const unsigned int lookup_size=4, const float smoothness=0, const bool is_fast_approx=true) { + return blur_patch(*this,sigma_s,sigma_r,patch_size,lookup_size,smoothness,is_fast_approx); + } + + //! Blur image using patch-based space \simplification \newinstance. + CImg get_blur_patch(const float sigma_s, const float sigma_r, const unsigned int patch_size=3, + const unsigned int lookup_size=4, const float smoothness=0, + const bool is_fast_approx=true) const { + return get_blur_patch(*this,sigma_s,sigma_r,patch_size,lookup_size,smoothness,is_fast_approx); } //! Blur image with the median filter. @@ -36053,10 +42917,11 @@ namespace cimg_library_suffixed { T *ptrd = res._data; cimg::unused(ptrd); const int hr = (int)n/2, hl = n - hr - 1; - if (res._depth!=1) { // 3d + 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. + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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, @@ -36064,14 +42929,15 @@ namespace cimg_library_suffixed { const Tfloat val0 = (Tfloat)(*this)(x,y,z,c); CImg values(n*n*n); unsigned int nb_values = 0; - T *ptrd = values.data(); + T *_ptrd = values.data(); cimg_for_inXYZ(*this,nx0,ny0,nz0,nx1,ny1,nz1,p,q,r) - if (cimg::abs((*this)(p,q,r,c) - val0)<=threshold) { *(ptrd++) = (*this)(p,q,r,c); ++nb_values; } + if (cimg::abs((*this)(p,q,r,c) - val0)<=threshold) { *(_ptrd++) = (*this)(p,q,r,c); ++nb_values; } res(x,y,z,c) = nb_values?values.get_shared_points(0,nb_values - 1).median():(*this)(x,y,z,c); } 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. + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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, @@ -36080,8 +42946,9 @@ namespace cimg_library_suffixed { } } else { 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. + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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, @@ -36089,9 +42956,9 @@ namespace cimg_library_suffixed { const Tfloat val0 = (Tfloat)(*this)(x,y,c); CImg values(n*n); unsigned int nb_values = 0; - T *ptrd = values.data(); + T *_ptrd = values.data(); cimg_for_inXY(*this,nx0,ny0,nx1,ny1,p,q) - if (cimg::abs((*this)(p,q,c) - val0)<=threshold) { *(ptrd++) = (*this)(p,q,c); ++nb_values; } + if (cimg::abs((*this)(p,q,c) - val0)<=threshold) { *(_ptrd++) = (*this)(p,q,c); ++nb_values; } res(x,y,c) = nb_values?values.get_shared_points(0,nb_values - 1).median():(*this)(x,y,c); } else { @@ -36100,7 +42967,7 @@ namespace cimg_library_suffixed { w2 = width() - 2, h2 = height() - 2, w3 = width() - 3, h3 = height() - 3, w4 = width() - 4, h4 = height() - 4; - switch (n) { // Without threshold. + switch (n) { // Without threshold case 3 : { cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) cimg_forC(*this,c) { @@ -36145,7 +43012,8 @@ namespace cimg_library_suffixed { } } break; default : { - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_width>=16 && _height*_spectrum>=4)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*16 && _height*_spectrum>=4)) cimg_forXYC(*this,x,y,c) { const int x0 = x - hl, y0 = y - hl, x1 = x + hr, y1 = y + hr, @@ -36175,11 +43043,12 @@ namespace cimg_library_suffixed { const float nedge = edge/2; CImg velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum); - if (_depth>1) { // 3d - if (sharpen_type) { // Shock filters. + if (_depth>1) { // 3D + if (sharpen_type) { // Shock filters CImg 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_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*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); @@ -36195,7 +43064,8 @@ namespace cimg_library_suffixed { *(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_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*512 && + _spectrum>=2)) cimg_forC(*this,c) { Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; CImg_3x3x3(I,Tfloat); @@ -36225,7 +43095,9 @@ namespace cimg_library_suffixed { } _veloc_max[c] = veloc_max; } - } else // Inverse diffusion. + } else // Inverse diffusion + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*512 && + _spectrum>=2)) cimg_forC(*this,c) { Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; CImg_3x3x3(I,Tfloat); @@ -36236,11 +43108,12 @@ namespace cimg_library_suffixed { } _veloc_max[c] = veloc_max; } - } else { // 2d. - if (sharpen_type) { // Shock filters. + } else { // 2D + if (sharpen_type) { // Shock filters CImg 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_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*32 && + _height>=(cimg_openmp_sizefactor)*16)) cimg_forY(G,y) { CImg val, vec; Tfloat *ptrG0 = G.data(0,y,0,0), *ptrG1 = G.data(0,y,0,1), *ptrG2 = G.data(0,y,0,2); @@ -36253,7 +43126,8 @@ namespace cimg_library_suffixed { *(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_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*512 && + _spectrum>=2)) cimg_forC(*this,c) { Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; CImg_3x3(I,Tfloat); @@ -36277,7 +43151,9 @@ namespace cimg_library_suffixed { } _veloc_max[c] = veloc_max; } - } else // Inverse diffusion. + } else // Inverse diffusion + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*512 && + _spectrum>=2)) cimg_forC(*this,c) { Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0; CImg_3x3(I,Tfloat); @@ -36305,165 +43181,127 @@ namespace cimg_library_suffixed { \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 + - 0 = Centered finite differences (default) - 1 = Forward finite differences - 2 = Using Sobel kernels - 3 = Using rotation invariant kernels - - 4 = Using Deriche recusrsive filter. - - 5 = Using Van Vliet recusrsive filter. + - 4 = Using Deriche recursive filter. + - 5 = Using Van Vliet recursive filter. **/ - CImgList get_gradient(const char *const axes=0, const int scheme=3) const { - CImgList 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(_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 get_gradient(const char *const axes=0, const int scheme=0) const { CImgList 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; + char __axes[4] = {}; + const char *_axes = axes?axes:__axes; + if (!axes) { + unsigned int k = 0; + if (_width>1) __axes[k++] = 'x'; + if (_height>1) __axes[k++] = 'y'; + if (_depth>1) __axes[k++] = 'z'; + } + + CImg grad; + while (*_axes) { + const char axis = cimg::lowercase(*(_axes++)); + if (axis!='x' && axis!='y' && axis!='z') + throw CImgArgumentException(_cimg_instance + "get_gradient(): Invalid specified axes '%s'.", + cimg_instance, + axes); + const longT off = axis=='x'?1:axis=='y'?_width:_width*_height; + if ((axis=='x' && _width==1) || (axis=='y' && _height==1) || (axis=='z' && _depth==1)) { + grad.assign(_width,_height,_depth,_spectrum,0).move_to(res); + continue; + } + + const int _scheme = axis=='z' && (scheme==2 || scheme==3)?0:scheme; + switch (_scheme) { + case -1 : { // Backward finite differences + grad.assign(_width,_height,_depth,_spectrum); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(size(),16384)) + cimg_forXYZC(*this,x,y,z,c) { + const ulongT pos = offset(x,y,z,c); + if ((axis=='x' && !x) || (axis=='y' && !y) || (axis=='z' && !z)) + grad[pos] = 0; + else + grad[pos] = (Tfloat)_data[pos] - _data[pos - off]; + } + grad.move_to(res); + } break; + case 1 : { // Forward finite differences + grad.assign(_width,_height,_depth,_spectrum); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(size(),16384)) + cimg_forXYZC(*this,x,y,z,c) { + const ulongT pos = offset(x,y,z,c); + if ((axis=='x' && x==width() - 1) || (axis=='y' && y==height() - 1) || (axis=='z' && z==depth() - 1)) + grad[pos] = 0; + else + grad[pos] = (Tfloat)_data[pos + off] - _data[pos]; + } + grad.move_to(res); + } break; + case 2 : { // Sobel scheme + grad.assign(_width,_height,_depth,_spectrum); + if (axis=='x') // X-axis + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*16384 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) grad(x,y,z,c) = - Ipp + Inp - 2*Ipc + 2*Inc - Ipn + Inn; + } + else // Y-axis + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*16384 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) grad(x,y,z,c) = - Ipp - 2*Icp - Inp + Ipn + 2*Icn + Inn; + } + grad.move_to(res); + } break; + case 3 : { // Rotation invariant scheme + const Tfloat a = (Tfloat)(0.25f*(2 - std::sqrt(2.f))), b = (Tfloat)(0.5f*(std::sqrt(2.f) - 1)); + grad.assign(_width,_height,_depth,_spectrum); + if (axis=='x') // X-axis + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*16384 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) grad(x,y,z,c) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn; + } + else // Y-axis + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*16384 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) grad(x,y,z,c) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn; + } + grad.move_to(res); + } break; + case 4 : // Deriche filter + get_deriche(0,1,axis).move_to(res); + break; + case 5 : // Van Vliet filter + get_vanvliet(0,1,axis).move_to(res); + break; + default : { // Central finite differences + grad.assign(_width,_height,_depth,_spectrum); + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(size(),16384)) + cimg_forXYZC(*this,x,y,z,c) { + const ulongT pos = offset(x,y,z,c); + if ((axis=='x' && !x) || (axis=='y' && !y) || (axis=='z' && !z)) + grad[pos] = ((Tfloat)_data[pos + off] - _data[pos])/2; + else if ((axis=='x' && x==width() - 1) || (axis=='y' && y==height() - 1) || (axis=='z' && z==depth() - 1)) + grad[pos] = ((Tfloat)_data[pos] - _data[pos - off])/2; + else + grad[pos] = ((Tfloat)_data[pos + off] - _data[pos - off])/2; + } + grad.move_to(res); + } break; } } - grad.assign(); return res; } @@ -36473,139 +43311,115 @@ namespace cimg_library_suffixed { **/ CImgList get_hessian(const char *const axes=0) const { CImgList 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) + char __axes[12] = {}; + const char *_axes = axes?axes:__axes; + if (!axes) { + unsigned int k = 0; + if (_width>1) { __axes[k++] = 'x'; __axes[k++] = 'x'; } + if (_width>1 && _height>1) { __axes[k++] = 'x'; __axes[k++] = 'y'; } + if (_width>1 && _depth>1) { __axes[k++] = 'x'; __axes[k++] = 'z'; } + if (_height>1) { __axes[k++] = 'y'; __axes[k++] = 'y'; } + if (_height>1 && _depth>1) { __axes[k++] = 'y'; __axes[k++] = 'z'; } + if (_depth>1) { __axes[k++] = 'z'; __axes[k++] = 'z'; } + } + const unsigned int len = (unsigned int)std::strlen(_axes); + if (len%2) throw CImgArgumentException(_cimg_instance "get_hessian(): Invalid specified axes '%s'.", cimg_instance, - naxes); + axes); + CImg hess; + for (unsigned int k = 0; k=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 - } + if (((axis1=='x' || axis2=='x') && _width==1) || + ((axis1=='y' || axis2=='y') && _height==1) || + ((axis1=='z' || axis2=='z') && _depth==1)) { + hess.fill(0).move_to(res); + continue; } - } 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 + + if (axis1==axis2) // Ixx, Iyy, Izz + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(size(),16384)) + cimg_forXYZC(*this,x,y,z,c) { + const ulongT pos = offset(x,y,z,c); + if ((axis1=='x' && !x) || (axis1=='y' && !y) || (axis1=='z' && !z)) + hess[pos] = (Tfloat)_data[pos + off] - _data[pos]; + else if ((axis1=='x' && x==width() - 1) || + (axis1=='y' && y==height() - 1) || + (axis1=='z' && z==depth() - 1)) + hess[pos] = (Tfloat)_data[pos - off] - _data[pos]; + else + hess[pos] = (Tfloat)_data[pos + off] + _data[pos - off] - 2*_data[pos]; } - } - } else for (unsigned int l = 0; laxis2) 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 + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*16384 && + _depth*_spectrum>=2)) + cimg_forZC(*this,z,c) { + CImg_3x3(I,Tfloat); + cimg_for3x3(*this,x,y,z,c,I,Tfloat) hess(x,y,z,c) = (Inn + Ipp - Inp - Ipn)/4; } - 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 + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*16384 && + _spectrum>=2)) + cimg_forC(*this,c) { + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) hess(x,y,z,c) = (Incn + Ipcp - Incp - Ipcn)/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 // Iyz + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*16384 && + _spectrum>=2)) + cimg_forC(*this,c) { + CImg_3x3x3(I,Tfloat); + cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) hess(x,y,z,c) = (Icnn + Icpp - Icnp - Icpn)/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_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); - } + hess.move_to(res); + } return res; } - //! Compute image laplacian. + //! Compute image Laplacian. CImg& laplacian() { return get_laplacian().move_to(*this); } - //! Compute image laplacian \newinstance. + //! Compute image Laplacian \newinstance. CImg get_laplacian() const { if (is_empty()) return CImg(); CImg res(_width,_height,_depth,_spectrum); - if (_depth>1) { // 3d - cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) + if (_depth>1) { // 3D + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*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)) + } else if (_height>1) { // 2D + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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)) + } else { // 1D + cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*1048576 && + _height*_depth*_spectrum>=2)) cimg_forC(*this,c) { Tfloat *ptrd = res.data(0,0,0,c); CImg_3x3(I,Tfloat); @@ -36627,10 +43441,11 @@ namespace cimg_library_suffixed { CImg get_structure_tensors(const bool is_fwbw_scheme=false) const { if (is_empty()) return *this; CImg res; - if (_depth>1) { // 3d + 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_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*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), @@ -36641,16 +43456,17 @@ namespace cimg_library_suffixed { 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; + cimg_pragma_openmp(atomic) *(ptrd0++)+=ix*ix; + cimg_pragma_openmp(atomic) *(ptrd1++)+=ix*iy; + cimg_pragma_openmp(atomic) *(ptrd2++)+=ix*iz; + cimg_pragma_openmp(atomic) *(ptrd3++)+=iy*iy; + cimg_pragma_openmp(atomic) *(ptrd4++)+=iy*iz; + cimg_pragma_openmp(atomic) *(ptrd5++)+=iz*iz; } } - } else { // Forward/backward finite differences. - cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=1048576 && _spectrum>=2)) + } else { // Forward/backward finite differences + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*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), @@ -36658,22 +43474,23 @@ namespace cimg_library_suffixed { 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; + ixf = Incc - Iccc, ixb = Iccc - Ipcc, ixc = (Incc - Ipcc)/2, + iyf = Icnc - Iccc, iyb = Iccc - Icpc, iyc = (Icnc - Icpc)/2, + izf = Iccn - Iccc, izb = Iccc - Iccp, izc = (Iccn - Iccp)/2; + cimg_pragma_openmp(atomic) *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; + cimg_pragma_openmp(atomic) *(ptrd1++)+=ixc*iyc; + cimg_pragma_openmp(atomic) *(ptrd2++)+=ixc*izc; + cimg_pragma_openmp(atomic) *(ptrd3++)+=(iyf*iyf + iyb*iyb)/2; + cimg_pragma_openmp(atomic) *(ptrd4++)+=iyc*izc; + cimg_pragma_openmp(atomic) *(ptrd5++)+=(izf*izf + izb*izb)/2; } } } - } else { // 2d + } 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_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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); @@ -36681,23 +43498,24 @@ namespace cimg_library_suffixed { const Tfloat ix = (Inc - Ipc)/2, iy = (Icn - Icp)/2; - *(ptrd0++)+=ix*ix; - *(ptrd1++)+=ix*iy; - *(ptrd2++)+=iy*iy; + cimg_pragma_openmp(atomic) *(ptrd0++)+=ix*ix; + cimg_pragma_openmp(atomic) *(ptrd1++)+=ix*iy; + cimg_pragma_openmp(atomic) *(ptrd2++)+=iy*iy; } } - } else { // Forward/backward finite differences (version 2). - cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=1048576 && _depth*_spectrum>=2)) + } else { // Forward/backward finite differences (version 2) + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*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; + ixf = Inc - Icc, ixb = Icc - Ipc, ixc = (Inc - Ipc)/2, + iyf = Icn - Icc, iyb = Icc - Icp, iyc = (Icn - Icp)/2; + cimg_pragma_openmp(atomic) *(ptrd0++)+=(ixf*ixf + ixb*ixb)/2; + cimg_pragma_openmp(atomic) *(ptrd1++)+=ixc*iyc; + cimg_pragma_openmp(atomic) *(ptrd2++)+=(iyf*iyf + iyb*iyb)/2; } } } @@ -36722,9 +43540,10 @@ namespace cimg_library_suffixed { power2 = power1/(1e-7f + 1 - anisotropy); blur(alpha).normalize(0,(T)255); - if (_depth>1) { // 3d + 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_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && + _height*_depth>=(cimg_openmp_sizefactor)*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), @@ -36748,9 +43567,10 @@ namespace cimg_library_suffixed { *(ptrd5++) = n1*(uz*uz + vz*vz) + n2*wz*wz; } } - } else { // for 2d images + } 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_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*256 && + _height>=(cimg_openmp_sizefactor)*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 val(2), vec(2,2); @@ -36790,7 +43610,7 @@ namespace cimg_library_suffixed { '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& displacement(const CImg& source, const float smoothness=0.1f, const float precision=5.0f, + CImg& displacement(const CImg& source, const float smoothness=0.1f, const float precision=5.f, const unsigned int nb_scales=0, const unsigned int iteration_max=10000, const bool is_backward=false, const CImg& guide=CImg::const_empty()) { @@ -36800,7 +43620,7 @@ namespace cimg_library_suffixed { //! Estimate displacement field between two images \newinstance. CImg get_displacement(const CImg& source, - const float smoothness=0.1f, const float precision=5.0f, + const float smoothness=0.1f, const float precision=5.f, const unsigned int nb_scales=0, const unsigned int iteration_max=10000, const bool is_backward=false, const CImg& guide=CImg::const_empty()) const { @@ -36832,9 +43652,9 @@ namespace cimg_library_suffixed { 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); + (unsigned int)cimg::round(std::log(mins/8.)/std::log(1.5),1,1); - const float _precision = (float)std::pow(10.0,-(double)precision); + const float _precision = (float)std::pow(10.,-(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); @@ -36846,7 +43666,7 @@ namespace cimg_library_suffixed { _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. + if (sw<5 && sh<5 && (!is_3d || sd<5)) continue; // Skip too small scales const CImg I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta, I2 = (get_resize(I1,2)-=tm)/=tdelta; @@ -36866,9 +43686,11 @@ namespace cimg_library_suffixed { 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) + if (is_3d) { // 3D version + if (smoothness>=0) // Isotropic regularization + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_height*_depth>=(cimg_openmp_sizefactor)*8 && + _width>=(cimg_openmp_sizefactor)*16) reduction(+:_energy)) cimg_forYZ(U,y,z) { const int @@ -36894,7 +43716,7 @@ namespace cimg_library_suffixed { 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 (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; @@ -36911,14 +43733,16 @@ namespace cimg_library_suffixed { } _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; + 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. + } else { // Anisotropic regularization const float nsmoothness = -smoothness; - cimg_pragma_openmp(parallel for collapse(2) cimg_openmp_if(_height*_depth>=8 && _width>=16) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_height*_depth>=(cimg_openmp_sizefactor)*8 && + _width>=(cimg_openmp_sizefactor)*16) reduction(+:_energy)) cimg_forYZ(U,y,z) { const int @@ -36959,7 +43783,7 @@ namespace cimg_library_suffixed { )/(1 + 2*(coef_a + coef_d + coef_f)*nsmoothness*dt); _energy_regul+=N; } - if (is_backward) { // Constraint displacement vectors to stay in image. + 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; @@ -36976,16 +43800,17 @@ namespace cimg_library_suffixed { } _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; + 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)) + } else { // 2D version + if (smoothness>=0) // Isotropic regularization + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(cimg_openmp_sizefactor)*8 && + _width>=(cimg_openmp_sizefactor)*16) reduction(+:_energy)) cimg_forY(U,y) { const int _p1y = y?y - 1:0, _n1y = yx) 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; @@ -37018,13 +43843,14 @@ namespace cimg_library_suffixed { } _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; + if (V) cimg_forXY(V,_x,_y) 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. + } else { // Anisotropic regularization const float nsmoothness = -smoothness; - cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=8 && _width>=16) reduction(+:_energy)) + cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(cimg_openmp_sizefactor)*8 && + _width>=(cimg_openmp_sizefactor)*16) reduction(+:_energy)) cimg_forY(U,y) { const int _p1y = y?y - 1:0, _n1y = yx) 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; @@ -37065,9 +43891,9 @@ namespace cimg_library_suffixed { } _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; + if (V) cimg_forXY(V,_x,_y) 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; } } } @@ -37081,7 +43907,7 @@ namespace cimg_library_suffixed { return U; } - //! Compute correspondence map between two images, using the patch-match algorithm. + //! Compute correspondence map between two images, using a patch-matching 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. @@ -37089,120 +43915,99 @@ namespace cimg_library_suffixed { \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 patch_penalization Penalization factor in score related patch occurrences. + if negative, also tells that identity result is not avoided. \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 - CImg& patchmatch(const CImg& patch_image, + CImg& matchpatch(const CImg& 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 float patch_penalization, const CImg &guide, CImg &matching_score) { - return get_patchmatch(patch_image,patch_width,patch_height,patch_depth, - nb_iterations,nb_randoms,guide,matching_score).move_to(*this); + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,patch_penalization,guide,matching_score).move_to(*this); } //! Compute correspondence map between two images, using the patch-match algorithm \newinstance. template - CImg get_patchmatch(const CImg& patch_image, + CImg get_matchpatch(const CImg& 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 float patch_penalization, const CImg &guide, CImg &matching_score) const { - return _patchmatch(patch_image,patch_width,patch_height,patch_depth, - nb_iterations,nb_randoms, + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,patch_penalization, guide,true,matching_score); } //! Compute correspondence map between two images, using the patch-match algorithm \overloading. template - CImg& patchmatch(const CImg& patch_image, + CImg& matchpatch(const CImg& 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 &guide) { - return get_patchmatch(patch_image,patch_width,patch_height,patch_depth, - nb_iterations,nb_randoms,guide).move_to(*this); + const unsigned int nb_iterations=5, + const unsigned int nb_randoms=5, + const float patch_penalization=0, + const CImg &guide=CImg::const_empty()) { + return get_matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,patch_penalization,guide).move_to(*this); } //! Compute correspondence map between two images, using the patch-match algorithm \overloading. template - CImg get_patchmatch(const CImg& patch_image, + CImg get_matchpatch(const CImg& 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 &guide) const { - return _patchmatch(patch_image,patch_width,patch_height,patch_depth, - nb_iterations,nb_randoms, - guide,false,CImg::empty()); - } - - //! Compute correspondence map between two images, using the patch-match algorithm \overloading. - CImg& patchmatch(const CImg& 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 get_patchmatch(const CImg& 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::const_empty(), - false,CImg::empty()); + const unsigned int nb_randoms=5, + const float patch_penalization=0, + const CImg &guide=CImg::const_empty()) const { + CImg matching_score; + return _matchpatch(patch_image,patch_width,patch_height,patch_depth, + nb_iterations,nb_randoms,patch_penalization,guide,false,matching_score); } template - CImg _patchmatch(const CImg& patch_image, + CImg _matchpatch(const CImg& 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 float patch_penalization, const CImg &guide, const bool is_matching_score, CImg &matching_score) const { if (is_empty()) return CImg::const_empty(); if (patch_image._spectrum!=_spectrum) throw CImgArgumentException(_cimg_instance - "patchmatch(): Instance image and specified patch image (%u,%u,%u,%u,%p) " + "matchpatch(): 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 " + "matchpatch(): 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 " + "matchpatch(): 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, @@ -37214,343 +44019,477 @@ namespace cimg_library_suffixed { 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).", + "matchpatch(): Specified guide (%u,%u,%u,%u,%p) has invalid dimensions " + "considering instance and patch 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 map(_width,_height,_depth,patch_image._depth>1?3:2); - CImg 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; + CImg a_map(_width,_height,_depth,patch_image._depth>1?3:2); + CImg is_updated(_width,_height,_depth,1,3); + CImg score(_width,_height,_depth), penalty; + const float _patch_penalization = cimg::abs(patch_penalization); + const bool allow_identity = patch_penalization>=0; + if (_patch_penalization!=0) + penalty.assign(patch_image._width,patch_image._height,patch_image._depth,1,0); - if (_depth>1 || patch_image._depth>1) { // 3d version. + 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; + + // Interleave image buffers to speed up patch comparison (more cache-friendly). + CImg in_this = get_permute_axes("cxyz"); + in_this._width = _width*_spectrum; + in_this._height = _height; + in_this._depth = _depth; + in_this._spectrum = 1; + CImg in_patch = patch_image.get_permute_axes("cxyz"); + in_patch._width = patch_image._width*patch_image._spectrum; + in_patch._height = patch_image._height; + in_patch._depth = patch_image._depth; + in_patch._spectrum = 1; + + if (_depth>1 || patch_image._depth>1) { // 3D version // Initialize correspondence map. - if (guide) cimg_forXYZ(*this,x,y,z) { // User-defined initialization. + if (guide) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if_size(_width,64)) + cimg_forXYZ(*this,x,y,z) { // User-defined initialization const int - cx1 = x<=psizew1?x:(x::inf()); - } else cimg_forXYZ(*this,x,y,z) { // Random initialization. - const int - cx1 = x<=psizew1?x:(x::inf()); + u - cx1,v - cy1,w - cz1, + u,v,w,0,allow_identity,cimg::type::inf()); + } else cimg_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + cimg_uint64 rng = (cimg::_rand(),cimg::rng()); +#if cimg_use_openmp!=0 + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for cimg_openmp_collapse(2)) + cimg_forXYZ(*this,x,y,z) { // Random initialization + const int + cx1 = x<=psizew1?x:(x::inf()); + } + cimg::srand(rng); } // Start iteration loop. cimg_abort_init; for (unsigned int iter = 0; iter64 && iter=(cimg_openmp_sizefactor)*64)) { + cimg_uint64 rng = (cimg::_rand(),cimg::rng()); - // 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 cimg_use_openmp!=0 + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for cimg_openmp_collapse(2)) + cimg_forXYZ(*this,X,Y,Z) { + const int + x = is_backward?width() - 1 - X:X, + y = is_backward?height() - 1 - Y:Y, + z = is_backward?depth() - 1 - Z:Z; + if (score(x,y,z)<=1e-5 || (constraint && guide(x,y,z,constraint)!=0)) continue; + const int + cx1 = x<=psizew1?x:(x0 && (is_updated(x - 1,y,z)&cmask)) { // Compare with left neighbor + u = a_map(x - 1,y,z,0); + v = a_map(x - 1,y,z,1); + w = a_map(x - 1,y,z,2); if (u>=cx1 - 1 && u=cy1 && v=cz1 && w0) { // 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 (is_forward && y>0 && (is_updated(x,y - 1,z)&cmask)) { // Compare with up neighbor + u = a_map(x,y - 1,z,0); + v = a_map(x,y - 1,z,1); + w = a_map(x,y - 1,z,2); if (u>=cx1 && u=cy1 - 1 && v=cz1 && w=cz1 && w0) { // 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 (is_forward && z>0 && (is_updated(x,y,z - 1)&cmask)) { // Compare with backward neighbor + u = a_map(x,y,z - 1,0); + v = a_map(x,y,z - 1,1); + w = a_map(x,y,z - 1,2); if (u>=cx1 && u=cy1 && v=cz1 - 1 && w=cx1 + 1 && u=cy1 && v=cz1 && w=cx1 && u=cy1 + 1 && v=cz1 && w=cx1 && u=cy1 && v=cz1 + 1 && w=(cimg_openmp_sizefactor)*64)) + cimg_forXYZ(score,x,y,z) { + const float p_score = score(x,y,z); + const int + cx1 = x<=psizew1?x:(x::inf()); + if (n_score!=p_score) { score(x,y,z) = n_score; is_updated(x,y) = 3; } + } } - } else { // 2d version. + } else { // 2D version // Initialize correspondence map. - if (guide) cimg_forXY(*this,x,y) { // Random initialization. + if (guide) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width,64)) + cimg_forXY(*this,x,y) { // User-defined initialization const int - cx1 = x<=psizew1?x:(x::inf()); - } else cimg_forXY(*this,x,y) { // Random initialization. - const int - cx1 = x<=psizew1?x:(x::inf()); + cx1 = x<=psizew1?x:(x::inf()); + } else cimg_pragma_openmp(parallel cimg_openmp_if_size(_width,64)) { + cimg_uint64 rng = (cimg::_rand(),cimg::rng()); + +#if cimg_use_openmp!=0 + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_forXY(*this,x,y) { // Random initialization + const int + cx1 = x<=psizew1?x:(x::inf()); + } + cimg::srand(rng); } // Start iteration loop. + cimg_abort_init; for (unsigned int iter = 0; iter64 && iter=(cimg_openmp_sizefactor)*64)) { + cimg_uint64 rng = (cimg::_rand(),cimg::rng()); - // 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 cimg_use_openmp!=0 + rng+=omp_get_thread_num(); +#endif + cimg_pragma_openmp(for) + cimg_forXY(*this,X,Y) { + const int + x = is_backward?width() - 1 - X:X, + y = is_backward?height() - 1 - Y:Y; + if (score(x,y)<=1e-5 || (constraint && guide(x,y,constraint)!=0)) continue; + const int + cx1 = x<=psizew1?x:(x0 && (is_updated(x - 1,y)&cmask)) { // Compare with left neighbor + u = a_map(x - 1,y,0); + v = a_map(x - 1,y,1); if (u>=cx1 - 1 && u=cy1 && v0) { // Compare with up neighbor. - const int u = map(x,y - 1,0), v = map(x,y - 1,1); + if (is_forward && y>0 && (is_updated(x,y - 1)&cmask)) { // Compare with up neighbor + u = a_map(x,y - 1,0); + v = a_map(x,y - 1,1); if (u>=cx1 && u=cy1 - 1 && v=cx1 + 1 && u=cy1 && v=cx1 && u=cy1 + 1 && v=(cimg_openmp_sizefactor)*64)) + cimg_forXY(score,x,y) { + const float p_score = score(x,y); + const int + cx1 = x<=psizew1?x:(x::inf()); + if (n_score!=p_score) { score(x,y) = n_score; is_updated(x,y) = 3; } + } } } + if (is_matching_score) score.move_to(matching_score); - return map; + return a_map; } // Compute SSD between two patches in different images. - static float _patchmatch(const CImg& img1, const CImg& img2, + static float _matchpatch(const CImg& img1, const CImg& img2, const CImg& penalty, 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 unsigned int psized, const unsigned int psizec, + const int x1, const int y1, const int z1, + const int x2, const int y2, const int z2, + const int xc, const int yc, const int zc, + const float patch_penalization, + const bool allow_identity, + const float max_score) { // 3D version + if (!allow_identity && cimg::hypot((float)x1 - x2,(float)y1 - y2,(float)z1 - z2)::inf(); + const T *p1 = img1.data(x1*psizec,y1,z1), *p2 = img2.data(x2*psizec,y2,z2); + const unsigned int psizewc = psizew*psizec; 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; + offx1 = (ulongT)img1._width - psizewc, + offx2 = (ulongT)img2._width - psizewc, + offy1 = (ulongT)img1._width*img1._height - (ulongT)psizeh*img1._width, + offy2 = (ulongT)img2._width*img2._height - (ulongT)psizeh*img2._width; float ssd = 0; - cimg_forC(img1,c) { + for (unsigned int k = 0; kmax_ssd) return max_ssd; + for (unsigned int i = 0; imax_score) return max_score; p1+=offx1; p2+=offx2; } p1+=offy1; p2+=offy2; } - return ssd; + return patch_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + + patch_penalization*psizewc*psizeh*psized*penalty(xc,yc,zc)/100); } - static float _patchmatch(const CImg& img1, const CImg& 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); + static float _matchpatch(const CImg& img1, const CImg& img2, const CImg& penalty, + const unsigned int psizew, const unsigned int psizeh, const unsigned int psizec, + const int x1, const int y1, + const int x2, const int y2, + const int xc, const int yc, + const float patch_penalization, + const bool allow_identity, + const float max_score) { // 2D version + if (!allow_identity && cimg::hypot((float)x1-x2,(float)y1-y2)::inf(); + const T *p1 = img1.data(x1*psizec,y1), *p2 = img2.data(x2*psizec,y2); + const unsigned int psizewc = psizew*psizec; 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; + offx1 = (ulongT)img1._width - psizewc, + offx2 = (ulongT)img2._width - psizewc; float ssd = 0; - cimg_forC(img1,c) { - for (unsigned int k = 0; kmax_ssd) return max_ssd; - p1+=offx1; p2+=offx2; - } - p1+=offy1; p2+=offy2; - } - p1+=offz1; p2+=offz2; + for (unsigned int j = 0; jmax_score) return max_score; + p1+=offx1; p2+=offx2; } - return ssd; + return patch_penalization==0?ssd:cimg::sqr(std::sqrt(ssd) + + patch_penalization*psizewc*psizeh*penalty(xc,yc)/100); } //! Compute Euclidean distance function to a specified value. @@ -37567,17 +44506,17 @@ namespace cimg_library_suffixed { **/ CImg& distance(const T& value, const unsigned int metric=2) { if (is_empty()) return *this; - if (cimg::type::string()!=cimg::type::string()) // For datatype < int. + if (cimg::type::string()!=pixel_type()) // For datatype < int return CImg(*this,false).distance((Tint)value,metric). cut((Tint)cimg::type::min(),(Tint)cimg::type::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::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. + 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; } @@ -37622,12 +44561,12 @@ namespace cimg_library_suffixed { longT *const t, longT *const dt) { longT q = s[0] = t[0] = 0; - for (int u = 1; u<(int)len; ++u) { // Forward scan. + 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. + for (int u = (int)len - 1; u>=0; --u) { dt[u] = f(u,s[q],g); if (u==t[q]) --q; } // Backward scan } CImg& _distance_core(longT (*const sep)(const longT, const longT, const longT *const), @@ -37636,28 +44575,30 @@ namespace cimg_library_suffixed { #define cimg_is_gcc49x (__GNUC__==4 && __GNUC_MINOR__==9) const ulongT wh = (ulongT)_width*_height; -#if defined(cimg_use_openmp) && !cimg_is_gcc49x +#if cimg_use_openmp!=0 && !cimg_is_gcc49x cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) #endif cimg_forC(*this,c) { CImg g(_width), dt(_width), s(_width), t(_width); CImg 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) +#if cimg_use_openmp!=0 && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*512 && + _height*_depth>=16) firstprivate(g,dt,s,t)) #endif - cimg_forYZ(*this,y,z) { // Over X-direction. + 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) +#if cimg_use_openmp!=0 && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_height>=(cimg_openmp_sizefactor)*512 && _width*_depth>=16) firstprivate(g,dt,s,t)) #endif - cimg_forXZ(*this,x,z) { // Over Y-direction. + 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]; @@ -37665,11 +44606,12 @@ namespace cimg_library_suffixed { } 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) +#if cimg_use_openmp!=0 && !cimg_is_gcc49x + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if(_depth>=(cimg_openmp_sizefactor)*512 && _width*_height>=16) firstprivate(g,dt,s,t)) #endif - cimg_forXY(*this,x,y) { // Over Z-direction. + 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]; @@ -37695,11 +44637,12 @@ namespace cimg_library_suffixed { cimg_pragma_openmp(parallel for cimg_openmp_if(_spectrum>=2)) cimg_forC(*this,c) { CImg img = get_shared_channel(c); - cimg_pragma_openmp(parallel for collapse(3) cimg_openmp_if(_width*_height*_depth>=1024)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) + cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*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=0; --z,--nz) { // Backward scan. + 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; @@ -37752,7 +44695,7 @@ namespace cimg_library_suffixed { "have incompatible dimensions.", cimg_instance, metric._width,metric._height,metric._depth,metric._spectrum); - typedef typename cimg::superset::type td; // Type used for computing cumulative distances. + typedef typename cimg::superset::type td; // Type used for computing cumulative distances CImg result(_width,_height,_depth,_spectrum), Q; CImg is_queued(_width,_height,_depth,1); if (return_path) return_path.assign(_width,_height,_depth,_spectrum); @@ -37802,7 +44745,7 @@ namespace cimg_library_suffixed { } if (is_high_connectivity) { - const float sqrt2 = std::sqrt(2.0f), sqrt3 = std::sqrt(3.0f); + const float sqrt2 = std::sqrt(2.f), sqrt3 = std::sqrt(3.f); // Diagonal neighbors on slice z. if (x - 1>=0 && y - 1>=0 && @@ -37822,7 +44765,7 @@ namespace cimg_library_suffixed { 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 (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; @@ -37861,7 +44804,7 @@ namespace cimg_library_suffixed { } } - if (z + 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; @@ -37941,7 +44884,7 @@ namespace cimg_library_suffixed { cimg_instance, metric._width,metric._height,metric._depth,metric._spectrum); CImg result(_width,_height,_depth,_spectrum,cimg::type::max()), Q; - CImg state(_width,_height,_depth); // -1=far away, 0=narrow, 1=frozen. + CImg 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) { @@ -38041,7 +44984,7 @@ namespace cimg_library_suffixed { const Tfloat M = (Tfloat)cimg::type::max(); T T1 = (T)std::min(x - 1>=0?res(x - 1,y,z):M,x + 11) { // 3d. + if (_depth>1) { // 3D T T2 = (T)std::min(y - 1>=0?res(x,y - 1,z):M,y + 1=0?res(x,y,z - 1):M,z + 11) { // 2d. + } else if (_height>1) { // 2D T T2 = (T)std::min(y - 1>=0?res(x,y - 1,z):M,y + 1T2) cimg::swap(T1,T2); if (P<=0) return (Tfloat)T1; if (T2=_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) { + for (unsigned int pos = siz - 1, par = 0; pos && value>(t)(*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)); } @@ -38101,7 +45044,7 @@ namespace cimg_library_suffixed { CImg velocity(*this,false); for (unsigned int iteration = 0; iteration1) { // 3d + 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)veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc; } else *(ptrd++) = 0; - } else { // 2d version + } 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) get_haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) const { if (is_empty() || !nb_scales) return +*this; CImg res; - const Tfloat sqrt2 = std::sqrt(2.0f); + const Tfloat sqrt2 = std::sqrt(2.f); if (nb_scales==1) { switch (cimg::lowercase(axis)) { // Single scale transform case 'x' : { @@ -38404,39 +45347,39 @@ namespace cimg_library_suffixed { return *this; } - //! Compute 1d Fast Fourier Transform, along a specified axis. + //! 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. + \param is_inverse Tells if the forward (\c false) or inverse (\c true) FFT is computed. **/ - CImgList get_FFT(const char axis, const bool is_invert=false) const { + CImgList get_FFT(const char axis, const bool is_inverse=false) const { CImgList res(*this,CImg()); - CImg::FFT(res[0],res[1],axis,is_invert); + CImg::FFT(res[0],res[1],axis,is_inverse); return res; } - //! Compute n-d Fast Fourier Transform. + //! Compute n-D Fast Fourier Transform. /* - \param is_invert Tells if the forward (\c false) or inverse (\c true) FFT is computed. + \param is_inverse Tells if the forward (\c false) or inverse (\c true) FFT is computed. **/ - CImgList get_FFT(const bool is_invert=false) const { + CImgList get_FFT(const bool is_inverse=false) const { CImgList res(*this,CImg()); - CImg::FFT(res[0],res[1],is_invert); + CImg::FFT(res[0],res[1],is_inverse); return res; } - //! Compute 1d Fast Fourier Transform, along a specified axis. + //! 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. + \param is_inverse Tells if the forward (\c false) or inverse (\c true) FFT is computed. **/ - static void FFT(CImg& real, CImg& imag, const char axis, const bool is_invert=false) { + static void FFT(CImg& real, CImg& imag, const char axis, const bool is_inverse=false, + const unsigned int nb_threads=0) { 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 " @@ -38444,86 +45387,119 @@ namespace cimg_library_suffixed { 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 : + const char _axis = cimg::lowercase(axis); + if (_axis!='x' && _axis!='y' && _axis!='z') 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); + cimg::unused(nb_threads); +#ifdef cimg_use_fftw3 + cimg::mutex(12); +#ifndef cimg_use_fftw3_singlethread + fftw_plan_with_nthreads(nb_threads?nb_threads:cimg::nb_cpus()); +#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) along the X-axis.", + pixel_type(), + cimg::strbuffersize(sizeof(fftw_complex)*real._width*real._height*real._depth), + real._width,real._height,real._depth,real._spectrum); + double *const ptrf = (double*)data_in; + fftw_plan data_plan = + _axis=='x'?fftw_plan_many_dft(1,(int*)&real._width,real.height()*real.depth(), + data_in,0,1,real.width(), + data_in,0,1,real.width(), + is_inverse?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE): + _axis=='y'?fftw_plan_many_dft(1,(int*)&real._height,real.width()*real.depth(), + data_in,0,1,real.height(), + data_in,0,1,real.height(), + is_inverse?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE): + fftw_plan_many_dft(1,(int*)&real._depth,real.width()*real.height(), + data_in,0,1,real.depth(), + data_in,0,1,real.depth(), + is_inverse?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE); + cimg_forC(real,c) { + CImg realc = real.get_shared_channel(c), imagc = imag.get_shared_channel(c); + switch (_axis) { + case 'x' : + cimg_pragma_openmp(parallel for cimg_openmp_if_size(real.width()*real.height()*real.depth(),125000)) + cimg_forXYZ(realc,x,y,z) { + const ulongT + i = realc.offset(x,y,z), + j = 2*(x + (ulongT)y*realc._width + (ulongT)z*realc._width*realc._height); + ptrf[j] = (double)realc[i]; + ptrf[j + 1] = (double)imagc[i]; + } + break; + case 'y' : + cimg_pragma_openmp(parallel for cimg_openmp_if_size(real.width()*real.height()*real.depth(),125000)) + cimg_forXYZ(realc,x,y,z) { + const ulongT + i = realc.offset(x,y,z), + j = 2*(y + (ulongT)x*realc._height + (ulongT)z*realc._width*realc._height); + ptrf[j] = (double)realc[i]; + ptrf[j + 1] = (double)imagc[i]; + } + break; + default : + cimg_pragma_openmp(parallel for cimg_openmp_if_size(real.width()*real.height()*real.depth(),125000)) + cimg_forXYZ(realc,x,y,z) { + const ulongT + i = realc.offset(x,y,z), + j = 2*(z + (ulongT)x*realc._depth + (ulongT)y*realc._width*realc._depth); + ptrf[j] = (double)realc[i]; + ptrf[j + 1] = (double)imagc[i]; + } + } + + fftw_execute(data_plan); + + const double a = is_inverse?1.0/(_axis=='x'?real.width():_axis=='y'?real.height():real.depth()):1.0; + switch (_axis) { + case 'x' : + cimg_pragma_openmp(parallel for cimg_openmp_if_size(real.width()*real.height()*real.depth(),125000)) + cimg_forXYZ(realc,x,y,z) { + const ulongT + i = 2*(x + (ulongT)y*realc._width + (ulongT)z*realc._width*realc._height), + j = realc.offset(x,y,z); + realc[j] = (T)(a*ptrf[i]); + imagc[j] = (T)(a*ptrf[i + 1]); + } + break; + case 'y' : + cimg_pragma_openmp(parallel for cimg_openmp_if_size(real.width()*real.height()*real.depth(),125000)) + cimg_forXYZ(realc,x,y,z) { + const ulongT + i = 2*(y + (ulongT)x*realc._height + (ulongT)z*realc._width*realc._height), + j = realc.offset(x,y,z); + realc[j] = (T)(a*ptrf[i]); + imagc[j] = (T)(a*ptrf[i + 1]); + } + break; + default : + cimg_pragma_openmp(parallel for cimg_openmp_if_size(real.width()*real.height()*real.depth(),125000)) + cimg_forXYZ(realc,x,y,z) { + const ulongT + i = 2*(z + (ulongT)x*realc._depth + (ulongT)y*realc._width*realc._depth), + j = realc.offset(x,y,z); + realc[j] = (T)(a*ptrf[i]); + imagc[j] = (T)(a*ptrf[i + 1]); + } + } } + fftw_destroy_plan(data_plan); fftw_free(data_in); +#ifndef cimg_use_fftw3_singlethread + fftw_cleanup_threads(); +#endif cimg::mutex(12,0); #else - switch (cimg::lowercase(axis)) { - case 'x' : { // Fourier along X, using built-in functions. + switch (_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) " @@ -38548,7 +45524,7 @@ namespace cimg_library_suffixed { for (unsigned int i = 0; i>1; if (((N - 1)&N) && N!=1) throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " @@ -38594,7 +45570,7 @@ namespace cimg_library_suffixed { for (unsigned int i = 0; i>1; if (((N - 1)&N) && N!=1) throw CImgInstanceException("CImgList<%s>::FFT(): Specified real and imaginary parts (%u,%u,%u,%u) " @@ -38640,7 +45616,7 @@ namespace cimg_library_suffixed { for (unsigned int i = 0; i::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. + //! 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 is_inverse 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& real, CImg& imag, const bool is_invert=false, const unsigned int nb_threads=0) { + static void FFT(CImg& real, CImg& imag, const bool is_inverse=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 " @@ -38691,51 +45661,40 @@ namespace cimg_library_suffixed { pixel_type(), real._width,real._height,real._depth,real._spectrum,real._data, imag._width,imag._height,imag._depth,imag._spectrum,imag._data); - + cimg::unused(nb_threads); #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); + fftw_plan_with_nthreads(nb_threads?nb_threads:cimg::nb_cpus()); #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); + 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); + double *const ptrf = (double*)data_in; + fftw_plan data_plan = + real._depth>1?fftw_plan_dft_3d(real._depth,real._height,real._width,data_in,data_in, + is_inverse?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE): + real._height>1?fftw_plan_dft_2d(real._height,real._width,data_in,data_in, + is_inverse?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE): + fftw_plan_dft_1d(real._width,data_in,data_in, + is_inverse?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 realc = real.get_shared_channel(c), imagc = imag.get_shared_channel(c); + cimg_pragma_openmp(parallel for cimg_openmp_if_size(real.width()*real.height()*real.depth(),125000)) + cimg_rofoff(realc,i) { const ulongT i2 = 2*i; ptrf[i2] = (double)realc[i]; ptrf[i2 + 1] = (double)imagc[i]; } 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; x1) 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); + if (real._depth>1) FFT(real,imag,'z',is_inverse); + if (real._height>1) FFT(real,imag,'y',is_inverse); + if (real._width>1) FFT(real,imag,'x',is_inverse); #endif } //@} //------------------------------------- // - //! \name 3d Objects Management + //! \name 3D Objects Management //@{ //------------------------------------- - //! Shift 3d object's vertices. + //! Rotate 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. + \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 second 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). + **/ + CImg& rotate_object3d(const float x, const float y, const float z, const float w, + const bool is_quaternion=false) { + return get_rotate_object3d(x,y,z,w,is_quaternion).move_to(*this); + } + + CImg get_rotate_object3d(const float x, const float y, const float z, const float w, + const bool is_quaternion=false) const { + if (_height!=3 || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "rotate_object3d(): Instance is not a set of 3D vertices.", + cimg_instance); + return CImg::rotation_matrix(x,y,z,w,is_quaternion)**this; + } + + //! 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& 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.", + "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. + //! Shift 3D object's vertices \newinstance. CImg get_shift_object3d(const float tx, const float ty=0, const float tz=0) const { return CImg(*this,false).shift_object3d(tx,ty,tz); } - //! Shift 3d object's vertices, so that it becomes centered. + //! Shift 3D object's vertices, so that it becomes centered. /** \note The object center is computed as its barycenter. **/ CImg& shift_object3d() { if (_height!=3 || _depth>1 || _spectrum>1) throw CImgInstanceException(_cimg_instance - "shift_object3d(): Instance is not a set of 3d vertices.", + "shift_object3d(): Instance is not a set of 3D vertices.", cimg_instance); CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); @@ -38798,21 +45778,21 @@ namespace cimg_library_suffixed { return *this; } - //! Shift 3d object's vertices, so that it becomes centered \newinstance. + //! Shift 3D object's vertices, so that it becomes centered \newinstance. CImg get_shift_object3d() const { return CImg(*this,false).shift_object3d(); } - //! Resize 3d object. + //! 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. + \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& 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.", + "resize_object3d(): Instance is not a set of 3D vertices.", cimg_instance); CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); @@ -38826,16 +45806,16 @@ namespace cimg_library_suffixed { return *this; } - //! Resize 3d object \newinstance. + //! Resize 3D object \newinstance. CImg get_resize_object3d(const float sx, const float sy=-100, const float sz=-100) const { return CImg(*this,false).resize_object3d(sx,sy,sz); } - //! Resize 3d object to unit size. + //! Resize 3D object to unit size. CImg resize_object3d() { if (_height!=3 || _depth>1 || _spectrum>1) throw CImgInstanceException(_cimg_instance - "resize_object3d(): Instance is not a set of 3d vertices.", + "resize_object3d(): Instance is not a set of 3D vertices.", cimg_instance); CImg xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2); @@ -38848,16 +45828,16 @@ namespace cimg_library_suffixed { return *this; } - //! Resize 3d object to unit size \newinstance. + //! Resize 3D object to unit size \newinstance. CImg get_resize_object3d() const { return CImg(*this,false).resize_object3d(); } - //! Merge two 3d objects together. + //! 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. + \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 CImg& append_object3d(CImgList& primitives, const CImg& obj_vertices, @@ -38866,7 +45846,7 @@ namespace cimg_library_suffixed { 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.", + "set of 3D vertices.", cimg_instance, obj_vertices._width,obj_vertices._height, obj_vertices._depth,obj_vertices._spectrum,obj_vertices._data); @@ -38874,7 +45854,7 @@ namespace cimg_library_suffixed { 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.", + "append_object3d(): Instance is not a set of 3D vertices.", cimg_instance); const unsigned int P = _width; @@ -38884,21 +45864,21 @@ namespace cimg_library_suffixed { for (unsigned int i = N; i &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. + 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. + //! 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[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 @@ -38907,7 +45887,7 @@ namespace cimg_library_suffixed { if (is_empty()) return *this; if (_height!=3) throw CImgInstanceException(_cimg_instance - "texturize_object3d(): image instance is not a set of 3d points.", + "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 @@ -38915,7 +45895,7 @@ namespace cimg_library_suffixed { cimg_instance, coords._width,coords._height,coords._depth,coords._spectrum,coords._data); CImg _coords; - if (!coords) { // If no texture coordinates specified, do a default XY-projection. + 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), @@ -38933,13 +45913,13 @@ namespace cimg_library_suffixed { CImg &p = primitives[l]; const unsigned int siz = p.size(); switch (siz) { - case 1 : { // Point. + 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. + 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), @@ -38948,7 +45928,7 @@ namespace cimg_library_suffixed { else colors[l].assign(colors[texture_ind],true); CImg::vector(i0,i1,x0,y0,x1,y1).move_to(p); } break; - case 3 : case 9 : { // Triangle. + 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), @@ -38958,7 +45938,7 @@ namespace cimg_library_suffixed { else colors[l].assign(colors[texture_ind],true); CImg::vector(i0,i1,i2,x0,y0,x1,y1,x2,y2).move_to(p); } break; - case 4 : case 12 : { // Quadrangle. + 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 @@ -38975,13 +45955,13 @@ namespace cimg_library_suffixed { return *this; } - //! Generate a 3d elevation of the image instance. + //! Generate a 3D elevation of the image instance. /** - \param[out] primitives The returned list of the 3d object primitives + \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[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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code const CImg img("reference.jpg"); @@ -39015,13 +45995,13 @@ namespace cimg_library_suffixed { CImg::vector((tc)r,(tc)g,(tc)b).move_to(colors); } const typename CImg::_functor2d_int func(elevation); - return elevation3d(primitives,func,0,0,_width - 1.0f,_height - 1.0f,_width,_height); + return elevation3d(primitives,func,0,0,_width - 1.f,_height - 1.f,_width,_height); } - //! Generate the 3d projection planes of the image instance. + //! 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[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. @@ -39067,14 +46047,14 @@ namespace cimg_library_suffixed { return points; } - //! Generate a isoline of the image instance as a 3d object. + //! Generate a isoline of the image instance as a 3D object. /** - \param[out] primitives The returned list of the 3d object primitives + \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 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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code const CImg img("reference.jpg"); @@ -39093,30 +46073,174 @@ namespace cimg_library_suffixed { cimg_instance); if (_depth>1) throw CImgInstanceException(_cimg_instance - "get_isoline3d(): Instance is not a 2d image.", + "get_isoline3d(): Instance is not a 2D image.", cimg_instance); primitives.assign(); if (is_empty()) return *this; CImg 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()); + vertices = isoline3d(primitives,func,isovalue,0,0,width() - 1.f,height() - 1.f,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); + vertices = isoline3d(primitives,func,isovalue,0,0,width() - 1.f,height() - 1.f,size_x,size_y); } return vertices; } - //! Generate an isosurface of the image instance as a 3d object. + //! Compute isolines of a function, as a 3D object. /** - \param[out] primitives The returned list of the 3d object primitives + \param[out] primitives Primitives data of the resulting 3D object. + \param func Elevation functor. Must have operator()(x,y) defined. + \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 + static CImg isoline3d(CImgList& 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) { + CImgList vertices; + primitives.assign(); + typename CImg::_functor_isoline3d add_vertex(vertices); + typename CImg::_functor_isoline3d add_segment(primitives); + isoline3d(add_vertex,add_segment,func,isovalue,x0,y0,x1,y1,size_x,size_y); + return vertices>'x'; + } + + //! Compute isolines of a function, as a 3D object. + /** + \param[out] add_vertex : Functor with operator()(x,y,z) defined for adding new vertex. + \param[out] add_segment : Functor with operator()(i,j) defined for adding new segment. + \param func Elevation function. Is of type float (*func)(const float x,const float y). + \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 + static void isoline3d(tv& add_vertex, tf& add_segment, const tfunc& func, const float isovalue, + const float x0, const float y0, const float x1, const float y1, + const int size_x, const int size_y) { + 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; + + if (!nxm1 || !nym1) return; + const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1; + CImg indices1(nx,1,1,2,-1), indices2(nx,1,1,2); + CImg values1(nx), values2(nx); + float X = x0, Y = y0, nX = X + dx, nY = Y + dy; + int nb_vertices = 0; + + // 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 + static CImg isoline3d(CImgList& 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 + static int _isoline3d_index(const unsigned int edge, const CImg& indices1, const CImg& 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; + } + + //! 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 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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code const CImg img = CImg("reference.jpg").resize(-100,-100,20); @@ -39138,196 +46262,19 @@ namespace cimg_library_suffixed { CImg 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, + vertices = isosurface3d(primitives,func,isovalue,0,0,0,width() - 1.f,height() - 1.f,depth() - 1.f, 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, + vertices = isosurface3d(primitives,func,isovalue,0,0,0,width() - 1.f,height() - 1.f,depth() - 1.f, size_x,size_y,size_z); } return vertices; } - //! Compute 3d elevation of a function as a 3d object. + //! Compute isosurface of a function, as a 3D object. /** - \param[out] primitives Primitives data of the resulting 3d object. - \param func Elevation function. Is of type float (*func)(const float x,const float y). - \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 - static CImg elevation3d(CImgList& 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=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 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 - static CImg elevation3d(CImgList& 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 float (*func)(const float x,const float y). - \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 - static CImg isoline3d(CImgList& 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(); - const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1; - CImgList vertices; - CImg indices1(nx,1,1,2,-1), indices2(nx,1,1,2); - CImg 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::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::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::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::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::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 - static CImg isoline3d(CImgList& 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 - static int _isoline3d_indice(const unsigned int edge, const CImg& indices1, const CImg& 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[out] primitives Primitives data of the resulting 3D object. \param func Implicit function. Is of type float (*func)(const float x, const float y, const float z). \param isovalue Isovalue to extract. \param x0 X-coordinate of the starting point. @@ -39346,6 +46293,36 @@ namespace cimg_library_suffixed { 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) { + CImgList vertices; + primitives.assign(); + typename CImg::_functor_isosurface3d add_vertex(vertices); + typename CImg::_functor_isosurface3d add_triangle(primitives); + isosurface3d(add_vertex,add_triangle,func,isovalue,x0,y0,z0,x1,y1,z1,size_x,size_y,size_z); + return vertices>'x'; + } + + //! Compute isosurface of a function, as a 3D object. + /** + \param[out] add_vertex : Functor with operator()(x,y,z) defined for adding new vertex. + \param[out] add_triangle : Functor with operator()(i,j) defined for adding new segment. + \param func Implicit function. Is of type float (*func)(const float x, const float y, const float z). + \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 + static void isosurface3d(tv& add_vertex, tf& add_triangle, 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, const int size_y, const int size_z) { 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, @@ -39634,13 +46611,12 @@ namespace cimg_library_suffixed { nxm1 = nx - 1, nym1 = ny - 1, nzm1 = nz - 1; - primitives.assign(); - if (!nxm1 || !nym1 || !nzm1) return CImg(); + if (!nxm1 || !nym1 || !nzm1) return; const float dx = (x1 - x0)/nxm1, dy = (y1 - y0)/nym1, dz = (z1 - z0)/nzm1; - CImgList vertices; CImg indices1(nx,ny,1,3,-1), indices2(indices1); CImg values1(nx,ny), values2(nx,ny); float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0; + int nb_vertices = 0; // Fill the first plane with function values Y = y0; @@ -39655,8 +46631,12 @@ namespace cimg_library_suffixed { for (unsigned int zi = 0; zi::vector(Xi,Y,Z).move_to(vertices); + indices1(xi,yi,0) = nb_vertices++; + add_vertex(Xi,Y,Z); } if ((edge&2) && indices1(nxi,yi,1)<0) { const float Yi = Y + (isovalue-val1)*dy/(val2-val1); - indices1(nxi,yi,1) = vertices.width(); - CImg::vector(nX,Yi,Z).move_to(vertices); + indices1(nxi,yi,1) = nb_vertices++; + add_vertex(nX,Yi,Z); } if ((edge&4) && indices1(xi,nyi,0)<0) { const float Xi = X + (isovalue-val3)*dx/(val2-val3); - indices1(xi,nyi,0) = vertices.width(); - CImg::vector(Xi,nY,Z).move_to(vertices); + indices1(xi,nyi,0) = nb_vertices++; + add_vertex(Xi,nY,Z); } if ((edge&8) && indices1(xi,yi,1)<0) { const float Yi = Y + (isovalue-val0)*dy/(val3-val0); - indices1(xi,yi,1) = vertices.width(); - CImg::vector(X,Yi,Z).move_to(vertices); + indices1(xi,yi,1) = nb_vertices++; + add_vertex(X,Yi,Z); } if ((edge&16) && indices2(xi,yi,0)<0) { const float Xi = X + (isovalue-val4)*dx/(val5-val4); - indices2(xi,yi,0) = vertices.width(); - CImg::vector(Xi,Y,nZ).move_to(vertices); + indices2(xi,yi,0) = nb_vertices++; + add_vertex(Xi,Y,nZ); } if ((edge&32) && indices2(nxi,yi,1)<0) { const float Yi = Y + (isovalue-val5)*dy/(val6-val5); - indices2(nxi,yi,1) = vertices.width(); - CImg::vector(nX,Yi,nZ).move_to(vertices); + indices2(nxi,yi,1) = nb_vertices++; + add_vertex(nX,Yi,nZ); } if ((edge&64) && indices2(xi,nyi,0)<0) { const float Xi = X + (isovalue-val7)*dx/(val6-val7); - indices2(xi,nyi,0) = vertices.width(); - CImg::vector(Xi,nY,nZ).move_to(vertices); + indices2(xi,nyi,0) = nb_vertices++; + add_vertex(Xi,nY,nZ); } if ((edge&128) && indices2(xi,yi,1)<0) { const float Yi = Y + (isovalue-val4)*dy/(val7-val4); - indices2(xi,yi,1) = vertices.width(); - CImg::vector(X,Yi,nZ).move_to(vertices); + indices2(xi,yi,1) = nb_vertices++; + add_vertex(X,Yi,nZ); } if ((edge&256) && indices1(xi,yi,2)<0) { const float Zi = Z+ (isovalue-val0)*dz/(val4-val0); - indices1(xi,yi,2) = vertices.width(); - CImg::vector(X,Y,Zi).move_to(vertices); + indices1(xi,yi,2) = nb_vertices++; + add_vertex(X,Y,Zi); } if ((edge&512) && indices1(nxi,yi,2)<0) { const float Zi = Z + (isovalue-val1)*dz/(val5-val1); - indices1(nxi,yi,2) = vertices.width(); - CImg::vector(nX,Y,Zi).move_to(vertices); + indices1(nxi,yi,2) = nb_vertices++; + add_vertex(nX,Y,Zi); } if ((edge&1024) && indices1(nxi,nyi,2)<0) { const float Zi = Z + (isovalue-val2)*dz/(val6-val2); - indices1(nxi,nyi,2) = vertices.width(); - CImg::vector(nX,nY,Zi).move_to(vertices); + indices1(nxi,nyi,2) = nb_vertices++; + add_vertex(nX,nY,Zi); } if ((edge&2048) && indices1(xi,nyi,2)<0) { const float Zi = Z + (isovalue-val3)*dz/(val7-val3); - indices1(xi,nyi,2) = vertices.width(); - CImg::vector(X,nY,Zi).move_to(vertices); + indices1(xi,nyi,2) = nb_vertices++; + add_vertex(X,nY,Zi); } // Create triangles @@ -39744,11 +46724,11 @@ namespace cimg_library_suffixed { 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::vector(i0,i2,i1).move_to(primitives); + const int + i0 = _isosurface3d_index(p0,indices1,indices2,xi,yi,nxi,nyi), + i1 = _isosurface3d_index(p1,indices1,indices2,xi,yi,nxi,nyi), + i2 = _isosurface3d_index(p2,indices1,indices2,xi,yi,nxi,nyi); + add_triangle(i0,i2,i1); } } } @@ -39756,10 +46736,9 @@ namespace cimg_library_suffixed { cimg::swap(values1,values2); cimg::swap(indices1,indices2); } - return vertices>'x'; } - //! Compute isosurface of a function, as a 3d object \overloading. + //! Compute isosurface of a function, as a 3D object \overloading. template static CImg isosurface3d(CImgList& primitives, const char *const expression, const float isovalue, const float x0, const float y0, const float z0, @@ -39770,7 +46749,7 @@ namespace cimg_library_suffixed { } template - static int _isosurface3d_indice(const unsigned int edge, const CImg& indices1, const CImg& indices2, + static int _isosurface3d_index(const unsigned int edge, const CImg& indices1, const CImg& indices2, const unsigned int x, const unsigned int y, const unsigned int nx, const unsigned int ny) { switch (edge) { @@ -39853,14 +46832,89 @@ namespace cimg_library_suffixed { } }; - //! Generate a 3d box object. + struct _functor_isoline3d { + CImgList& list; + _functor_isoline3d(CImgList& _list):list(_list) {} + template + void operator()(const t x, const t y, const t z) { CImg::vector((T)x,(T)y,(T)z).move_to(list); } + template + void operator()(const t i, const t j) { CImg::vector((T)i,(T)j).move_to(list); } + }; + + struct _functor_isosurface3d { + CImgList& list; + _functor_isosurface3d(CImgList& _list):list(_list) {} + template + void operator()(const t x, const t y, const t z) { CImg::vector((T)x,(T)y,(T)z).move_to(list); } + }; + + //! Compute 3D elevation of a function as a 3D object. /** - \param[out] primitives The returned list of the 3d object primitives + \param[out] primitives Primitives data of the resulting 3D object. + \param func Elevation function. Is of type float (*func)(const float x,const float y). + \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 + static CImg elevation3d(CImgList& 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=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 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 + static CImg elevation3d(CImgList& 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); + } + + //! 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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code CImgList faces3d; @@ -39879,14 +46933,14 @@ namespace cimg_library_suffixed { 0., 0., 0., 0.,size_z,size_z,size_z,size_z); } - //! Generate a 3d cone. + //! Generate a 3D cone. /** - \param[out] primitives The returned list of the 3d object primitives + \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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code CImgList faces3d; @@ -39903,27 +46957,27 @@ namespace cimg_library_suffixed { CImgList 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); + for (float delta = 360.f/subdivisions, angle = 0; angle<360; angle+=delta) { + const float a = (float)(angle*cimg::PI/180); CImg::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::vector(1,next,curr).move_to(primitives); CImg::vector(0,curr,next).move_to(primitives); } return vertices>'x'; } - //! Generate a 3d cylinder. + //! Generate a 3D cylinder. /** - \param[out] primitives The returned list of the 3d object primitives + \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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code CImgList faces3d; @@ -39940,14 +46994,14 @@ namespace cimg_library_suffixed { CImgList 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::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0.0f).move_to(vertices); + for (float delta = 360.f/subdivisions, angle = 0; angle<360; angle+=delta) { + const float a = (float)(angle*cimg::PI/180); + CImg::vector((float)(radius*std::cos(a)),(float)(radius*std::sin(a)),0.f).move_to(vertices); CImg::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::vector(0,next,curr).move_to(primitives); CImg::vector(1,curr + 1,next + 1).move_to(primitives); CImg::vector(curr,next,next + 1,curr + 1).move_to(primitives); @@ -39955,15 +47009,15 @@ namespace cimg_library_suffixed { return vertices>'x'; } - //! Generate a 3d torus. + //! Generate a 3D torus. /** - \param[out] primitives The returned list of the 3d object primitives + \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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code CImgList faces3d; @@ -39980,21 +47034,21 @@ namespace cimg_library_suffixed { if (!subdivisions1 || !subdivisions2) return CImg(); CImgList vertices; for (unsigned int v = 0; v::vector(x,y,z).move_to(vertices); } } for (unsigned int vv = 0; vv::vector(svv + nu,svv + uu,snv + uu,snv + nu).move_to(primitives); @@ -40003,15 +47057,15 @@ namespace cimg_library_suffixed { return vertices>'x'; } - //! Generate a 3d XY-plane. + //! Generate a 3D XY-plane. /** - \param[out] primitives The returned list of the 3d object primitives + \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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code CImgList faces3d; @@ -40033,18 +47087,18 @@ namespace cimg_library_suffixed { CImg::vector(fx*x,fy*y,0).move_to(vertices); for (unsigned int y = 0; y::vector(off1,off4,off3,off2).move_to(primitives); + CImg::vector(off1,off4,off3,off2).move_to(primitives); } return vertices>'x'; } - //! Generate a 3d sphere. + //! Generate a 3D sphere. /** - \param[out] primitives The returned list of the 3d object primitives + \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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code CImgList faces3d; @@ -40059,9 +47113,9 @@ namespace cimg_library_suffixed { // 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 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); + const double tmp = (1 + std::sqrt(5.f))/2, a = 1./std::sqrt(1 + tmp*tmp), b = tmp*a; + CImgList vertices(12,1,3,1,1, b,a,0., -b,a,0., -b,-a,0., b,-a,0., a,0.,b, a,0.,-b, + -a,0.,-b, -a,0.,b, 0.,b,a, 0.,-b,a, 0.,-b,-a, 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); @@ -40071,8 +47125,8 @@ namespace cimg_library_suffixed { // Recurse subdivisions for (unsigned int i = 0; i'x')*=radius; } - //! Generate a 3d ellipsoid. + //! Generate a 3D ellipsoid. /** - \param[out] primitives The returned list of the 3d object primitives + \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 image (0<=i<=N - 1). + \return The N vertices (xi,yi,zi) of the 3D object as a Nx3 CImg image (0<=i<=N - 1). \par Example \code CImgList faces3d; @@ -40138,19 +47192,19 @@ namespace cimg_library_suffixed { (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 vertices = sphere3d(primitives,1.0,subdivisions); + CImg vertices = sphere3d(primitives,1.,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. + //! 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. + \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 CImg& object3dtoCImg3d(const CImgList& primitives, @@ -40160,7 +47214,7 @@ namespace cimg_library_suffixed { return get_object3dtoCImg3d(primitives,colors,opacities,full_check).move_to(*this); } - //! Convert 3d object into a CImg3d representation \overloading. + //! Convert 3D object into a CImg3d representation \overloading. template CImg& object3dtoCImg3d(const CImgList& primitives, const CImgList& colors, @@ -40168,19 +47222,19 @@ namespace cimg_library_suffixed { return get_object3dtoCImg3d(primitives,colors,full_check).move_to(*this); } - //! Convert 3d object into a CImg3d representation \overloading. + //! Convert 3D object into a CImg3d representation \overloading. template CImg& object3dtoCImg3d(const CImgList& primitives, const bool full_check=true) { return get_object3dtoCImg3d(primitives,full_check).move_to(*this); } - //! Convert 3d object into a CImg3d representation \overloading. + //! Convert 3D object into a CImg3d representation \overloading. CImg& object3dtoCImg3d(const bool full_check=true) { return get_object3dtoCImg3d(full_check).move_to(*this); } - //! Convert 3d object into a CImg3d representation \newinstance. + //! Convert 3D object into a CImg3d representation \newinstance. template CImg get_object3dtoCImg3d(const CImgList& primitives, const CImgList& colors, @@ -40189,7 +47243,7 @@ namespace cimg_library_suffixed { CImg 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).", + "object3dtoCImg3d(): Invalid specified 3D object (%u,%u) (%s).", cimg_instance,_width,primitives._width,error_message.data()); CImg res(1,_size_object3dtoCImg3d(primitives,colors,opacities)); float *ptrd = res._data; @@ -40225,7 +47279,7 @@ namespace cimg_library_suffixed { const tc *ptrc = color._data; if (color.size()==3) { *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*(ptrc++); *(ptrd++) = (float)*ptrc; } else { - *(ptrd++) = -128.0f; + *(ptrd++) = -128.f; int shared_ind = -1; if (color.is_shared()) for (int i = 0; i CImg get_object3dtoCImg3d(const CImgList& primitives, const CImgList& colors, @@ -40325,7 +47379,7 @@ namespace cimg_library_suffixed { return get_object3dtoCImg3d(primitives,colors,opacities,full_check); } - //! Convert 3d object into a CImg3d representation \overloading. + //! Convert 3D object into a CImg3d representation \overloading. template CImg get_object3dtoCImg3d(const CImgList& primitives, const bool full_check=true) const { @@ -40333,7 +47387,7 @@ namespace cimg_library_suffixed { return get_object3dtoCImg3d(primitives,colors,opacities,full_check); } - //! Convert 3d object into a CImg3d representation \overloading. + //! Convert 3D object into a CImg3d representation \overloading. CImg get_object3dtoCImg3d(const bool full_check=true) const { CImgList opacities, colors; CImgList primitives(width(),1,1,1,1); @@ -40341,12 +47395,12 @@ namespace cimg_library_suffixed { return get_object3dtoCImg3d(primitives,colors,opacities,full_check); } - //! Convert CImg3d representation into a 3d object. + //! 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. + \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 CImg& CImg3dtoobject3d(CImgList& primitives, @@ -40356,7 +47410,7 @@ namespace cimg_library_suffixed { return get_CImg3dtoobject3d(primitives,colors,opacities,full_check).move_to(*this); } - //! Convert CImg3d representation into a 3d object \newinstance. + //! Convert CImg3d representation into a 3D object \newinstance. template CImg get_CImg3dtoobject3d(CImgList& primitives, CImgList& colors, @@ -40378,24 +47432,32 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::max()); \ + const float _sc_nopacity = cimg::abs((float)opacity), _sc_copacity = 1 - std::max((float)opacity,0.f); \ + const ulongT _sc_whd = (ulongT)_width*_height*_depth; \ + cimg::unused(_sc_maxval); #define cimg_draw_scanline(x0,x1,y,color,opacity,brightness) \ - _draw_scanline(x0,x1,y,color,opacity,brightness,_sc_nopacity,_sc_copacity,_sc_whd) + _draw_scanline(x0,x1,y,color,opacity,brightness,_sc_nopacity,_sc_copacity,_sc_whd,_sc_maxval) // [internal] The following _draw_scanline() routines are *non user-friendly functions*, // used only for internal purpose. @@ -40422,8 +47486,7 @@ namespace cimg_library_suffixed { CImg& _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::max(),(T)cimg::type::max()); + const float nopacity, const float copacity, const ulongT whd, const T _sc_maxval) { const int nx0 = x0>0?x0:0, nx1 = x1=0) { const tc *col = color; @@ -40452,11 +47515,11 @@ namespace cimg_library_suffixed { } } else { // Brightness>1 if (sizeof(T)!=1) cimg_forC(*this,c) { - const T val = (T)((2-brightness)**(col++) + (brightness - 1)*maxval); + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*_sc_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); + const T val = (T)((2-brightness)**(col++) + (brightness - 1)*_sc_maxval); std::memset(ptrd,(int)val,dx + 1); ptrd+=whd; } @@ -40476,7 +47539,7 @@ namespace cimg_library_suffixed { } } else { // Brightness>1 cimg_forC(*this,c) { - const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*maxval)*nopacity; + const Tfloat val = ((2-brightness)**(col++) + (brightness - 1)*_sc_maxval)*nopacity; for (int x = dx; x>=0; --x) { *ptrd = (T)(val + *ptrd*copacity); ++ptrd; } ptrd+=off; } @@ -40486,7 +47549,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a 3d point. + //! Draw a 3D point. /** \param x0 X-coordinate of the point. \param y0 Y-coordinate of the point. @@ -40512,7 +47575,7 @@ namespace cimg_library_suffixed { cimg_instance); if (x0>=0 && y0>=0 && z0>=0 && x0=1) cimg_forC(*this,c) { *ptrd = (T)*(col++); ptrd+=whd; } @@ -40521,7 +47584,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a 2d point \simplification. + //! Draw a 2D point \simplification. template CImg& draw_point(const int x0, const int y0, const tc *const color, const float opacity=1) { @@ -40554,7 +47617,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a 2d line. + //! Draw a 2D line. /** \param x0 X-coordinate of the starting line point. \param y0 Y-coordinate of the starting line point. @@ -40575,79 +47638,48 @@ namespace cimg_library_suffixed { \endcode **/ template - CImg& draw_line(const int x0, const int y0, - const int x1, const int y1, + CImg& draw_line(int x0, int y0, + int x1, 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); + if (is_empty() || !opacity || !pattern || + std::min(y0,y1)>=height() || std::max(y0,y1)<0 || + std::min(x0,x1)>=width() || std::max(x0,x1)<0) return *this; + int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dy01 = y1 - y0; + + const bool is_horizontal = cimg::abs(dx01)>cimg::abs(dy01); + if (is_horizontal) cimg::swap(x0,y0,x1,y1,w1,h1,dx01,dy01); + if (pattern==~0U && y0>y1) { cimg::swap(x0,x1,y0,y1); dx01*=-1; dy01*=-1; } + static unsigned int hatch = ~0U - (~0U>>1); if (init_hatch) hatch = ~0U - (~0U>>1); - const bool xdir = x0=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=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; } + cimg_init_scanline(opacity); + const int + step = y0<=y1?1:-1, + hdy01 = dy01*cimg::sign(dx01)/2, + cy0 = cimg::cut(y0,0,h1), + cy1 = cimg::cut(y1,0,h1) + step; + dy01+=dy01?0:1; + + for (int y = cy0; y!=cy1; y+=step) { + const int + yy0 = y - y0, + x = x0 + (dx01*yy0 + hdy01)/dy01; + if (x>=0 && x<=w1 && pattern&hatch) { + T *const ptrd = is_horizontal?data(y,x):data(x,y); + cimg_forC(*this,c) { + const T val = color[c]; + ptrd[c*_sc_whd] = opacity>=1?val:(T)(val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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; } } + if (!(hatch>>=1)) hatch = ~0U - (~0U>>1); } return *this; } - //! Draw a 2d line, with z-buffering. + //! Draw a 2D line, with z-buffering. /** \param zbuffer Zbuffer image. \param x0 X-coordinate of the starting point. @@ -40661,14 +47693,13 @@ namespace cimg_library_suffixed { \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 + template CImg& draw_line(CImg& zbuffer, - const int x0, const int y0, const float z0, - const int x1, const int y1, const float z1, + int x0, int y0, const float z0, + int x1, 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::type tzfloat; - if (is_empty() || z0<=0 || z1<=0) return *this; + if (is_empty() || z0<=0 || z1<=0 || !opacity || !pattern) return *this; if (!color) throw CImgArgumentException(_cimg_instance "draw_line(): Specified color is (null).", @@ -40679,197 +47710,52 @@ namespace cimg_library_suffixed { "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=height() || std::max(y0,y1)<0 || std::min(x0,x1)>=width() || std::max(x0,x1)<0) return *this; + + float iz0 = 1/z0, iz1 = 1/z1; 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)(nx00?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; - } + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dy01 = y1 - y0; + float diz01 = iz1 - iz0; + + const bool is_horizontal = cimg::abs(dx01)>cimg::abs(dy01); + if (is_horizontal) cimg::swap(x0,y0,x1,y1,w1,h1,dx01,dy01); + if (pattern==~0U && y0>y1) { + cimg::swap(x0,x1,y0,y1,iz0,iz1); + dx01*=-1; dy01*=-1; diz01*=-1; + } - //! 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 - CImg& 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; } + cimg_init_scanline(opacity); + + const int + step = y0<=y1?1:-1, hdy01 = dy01*cimg::sign(dx01)/2, + cy0 = cimg::cut(y0,0,h1), cy1 = cimg::cut(y1,0,h1) + step; + dy01+=dy01?0:1; + + for (int y = cy0; y!=cy1; y+=step) { + const int + yy0 = y - y0, + x = x0 + (dx01*yy0 + hdy01)/dy01; + const float iz = iz0 + diz01*yy0/dy01; + tz *const ptrz = is_horizontal?zbuffer.data(y,x):zbuffer.data(x,y); + + if (x>=0 && x<=w1 && pattern&hatch && iz>=*ptrz) { + *ptrz = (tz)iz; + T *const ptrd = is_horizontal?data(y,x):data(x,y); + cimg_forC(*this,c) { + const T val = color[c]; + ptrd[c*_sc_whd] = opacity>=1?val:(T)(val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); } } + if (!(hatch>>=1)) hatch = ~0U - (~0U>>1); } return *this; } - //! Draw a textured 2d line. + //! Draw a textured 2D line. /** \param x0 X-coordinate of the starting line point. \param y0 Y-coordinate of the starting line point. @@ -40893,117 +47779,69 @@ namespace cimg_library_suffixed { \endcode **/ template - CImg& draw_line(const int x0, const int y0, - const int x1, const int y1, + CImg& draw_line(int x0, int y0, + int x1, int y1, const CImg& texture, - const int tx0, const int ty0, - const int tx1, const int ty1, + int tx0, int ty0, + int tx1, int ty1, const float opacity=1, const unsigned int pattern=~0U, const bool init_hatch=true) { - if (is_empty()) return *this; + + if (is_empty() || !opacity || !pattern) 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); + + if (std::min(y0,y1)>=height() || std::max(y0,y1)<0 || std::min(x0,x1)>=width() || std::max(x0,x1)<0) return *this; + + int w1 = width() - 1, h1 = height() - 1; + longT + dx01 = (longT)x1 - x0, dy01 = (longT)y1 - y0, + dtx01 = (longT)tx1 - tx0, dty01 = (longT)ty1 - ty0; + + const bool is_horizontal = cimg::abs(dx01)>cimg::abs(dy01); + if (is_horizontal) cimg::swap(x0,y0,x1,y1,w1,h1,dx01,dy01); + if (pattern==~0U && y0>y1) { + cimg::swap(x0,x1,y0,y1,tx0,tx1,ty0,ty1); + dx01*=-1; dy01*=-1; dtx01*=-1; dty01*=-1; + } + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; static unsigned int hatch = ~0U - (~0U>>1); if (init_hatch) hatch = ~0U - (~0U>>1); - const bool xdir = x0=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)(nx00?dx:1); - const ulongT - whd = (ulongT)_width*_height*_depth, - twh = (ulongT)texture._width*texture._height; + cimg_init_scanline(opacity); - 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; } + const int step = y0<=y1?1:-1, cy0 = cimg::cut(y0,0,h1), cy1 = cimg::cut(y1,0,h1) + step; + const longT + hdy01 = dy01*cimg::sign(dx01)/2, + hdy01tx = dy01*cimg::sign(dtx01)/2, + hdy01ty = dy01*cimg::sign(dty01)/2; + + dy01+=dy01?0:1; + + for (int y = cy0; y!=cy1; y+=step) { + const longT + yy0 = (longT)y - y0, + x = x0 + (dx01*yy0 + hdy01)/dy01, + tx = tx0 + (dtx01*yy0 + hdy01tx)/dy01, + ty = ty0 + (dty01*yy0 + hdy01ty)/dy01; + if (x>=0 && x<=w1 && pattern&hatch) { + T *const ptrd = is_horizontal?data(y,x):data(x,y); + const tc *const color = &texture._atXY(tx,ty); + cimg_forC(*this,c) { + const T val = color[c*twhd]; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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; } } + if (!(hatch>>=1)) hatch = ~0U - (~0U>>1); } return *this; } - //! Draw a textured 2d line, with perspective correction. + //! Draw a textured 2D line, with perspective correction. /** \param x0 X-coordinate of the starting point. \param y0 Y-coordinate of the starting point. @@ -41021,14 +47859,14 @@ namespace cimg_library_suffixed { \param init_hatch Tells if the hash variable must be reinitialized. **/ template - CImg& draw_line(const int x0, const int y0, const float z0, - const int x1, const int y1, const float z1, + CImg& draw_line(int x0, int y0, const float z0, + int x1, int y1, const float z1, const CImg& 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 (is_empty() || z0<=0 || z1<=0 || !opacity || !pattern) return *this; if (texture._depth>1 || texture._spectrum<_spectrum) throw CImgArgumentException(_cimg_instance "draw_line(): Invalid specified texture (%u,%u,%u,%u,%p).", @@ -41036,115 +47874,60 @@ namespace cimg_library_suffixed { 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); + + if (std::min(y0,y1)>=height() || std::max(y0,y1)<0 || std::min(x0,x1)>=width() || std::max(x0,x1)<0) return *this; + + float iz0 = 1/z0, iz1 = 1/z1; + int w1 = width() - 1, h1 = height() - 1; + longT dx01 = (longT)x1 - x0, dy01 = (longT)y1 - y0; + float + diz01 = iz1 - iz0, + txz0 = tx0*iz0, txz1 = tx1*iz1, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, + dtxz01 = txz1 - txz0, dtyz01 = tyz1 - tyz0; + + const bool is_horizontal = cimg::abs(dx01)>cimg::abs(dy01); + if (is_horizontal) cimg::swap(x0,y0,x1,y1,w1,h1,dx01,dy01); + if (pattern==~0U && y0>y1) { + cimg::swap(x0,x1,y0,y1,iz0,iz1,txz0,txz1,tyz0,tyz1); + dx01*=-1; dy01*=-1; diz01*=-1; dtxz01*=-1; dtyz01*=-1; + } + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; static unsigned int hatch = ~0U - (~0U>>1); if (init_hatch) hatch = ~0U - (~0U>>1); - const bool xdir = x0=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)(nx00?dx:1); - const ulongT - whd = (ulongT)_width*_height*_depth, - twh = (ulongT)texture._width*texture._height; + cimg_init_scanline(opacity); - 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; } + const int step = y0<=y1?1:-1, cy0 = cimg::cut(y0,0,h1), cy1 = cimg::cut(y1,0,h1) + step; + const longT hdy01 = dy01*cimg::sign(dx01)/2; + + dy01+=dy01?0:1; + + for (int y = cy0; y!=cy1; y+=step) { + const longT + yy0 = (longT)y - y0, + x = x0 + (dx01*yy0 + hdy01)/dy01; + const float + iz = iz0 + diz01*yy0/dy01, + txz = txz0 + dtxz01*yy0/dy01, + tyz = tyz0 + dtyz01*yy0/dy01; + if (x>=0 && x<=w1 && pattern&hatch) { + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz); + T *const ptrd = is_horizontal?data(y,x):data(x,y); + const tc *const color = &texture._atXY(tx,ty); + cimg_forC(*this,c) { + const T val = color[c*twhd]; + ptrd[c*_sc_whd] = opacity>=1?val:(T)(val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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; } } + if (!(hatch>>=1)) hatch = ~0U - (~0U>>1); } return *this; } - //! Draw a textured 2d line, with perspective correction and z-buffering. + //! Draw a textured 2D line, with perspective correction and z-buffering. /** \param zbuffer Z-buffer image. \param x0 X-coordinate of the starting point. @@ -41164,15 +47947,14 @@ namespace cimg_library_suffixed { **/ template CImg& draw_line(CImg& zbuffer, - const int x0, const int y0, const float z0, - const int x1, const int y1, const float z1, + int x0, int y0, const float z0, + int x1, int y1, const float z1, const CImg& 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::type tzfloat; - if (is_empty() || z0<=0 || z1<=0) return *this; + if (is_empty() || z0<=0 || z1<=0 || !opacity || !pattern) return *this; if (!is_sameXY(zbuffer)) throw CImgArgumentException(_cimg_instance "draw_line(): Instance and specified Z-buffer (%u,%u,%u,%u,%p) have " @@ -41186,133 +47968,58 @@ namespace cimg_library_suffixed { 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); + + if (std::min(y0,y1)>=height() || std::max(y0,y1)<0 || std::min(x0,x1)>=width() || std::max(x0,x1)<0) return *this; + + float iz0 = 1/z0, iz1 = 1/z1; + int w1 = width() - 1, h1 = height() - 1; + longT dx01 = (longT)x1 - x0, dy01 = (longT)y1 - y0; + float + diz01 = iz1 - iz0, + txz0 = tx0*iz0, txz1 = tx1*iz1, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, + dtxz01 = txz1 - txz0, dtyz01 = tyz1 - tyz0; + + const bool is_horizontal = cimg::abs(dx01)>cimg::abs(dy01); + if (is_horizontal) cimg::swap(x0,y0,x1,y1,w1,h1,dx01,dy01); + if (pattern==~0U && y0>y1) { + cimg::swap(x0,x1,y0,y1,iz0,iz1,txz0,txz1,tyz0,tyz1); + dx01*=-1; dy01*=-1; diz01*=-1; dtxz01*=-1; dtyz01*=-1; + } + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; static unsigned int hatch = ~0U - (~0U>>1); if (init_hatch) hatch = ~0U - (~0U>>1); - const bool xdir = x0=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)(nx00?dx:1); - const ulongT - whd = (ulongT)_width*_height*_depth, - twh = (ulongT)texture._width*texture._height; + cimg_init_scanline(opacity); - 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; } - } + const int step = y0<=y1?1:-1, cy0 = cimg::cut(y0,0,h1), cy1 = cimg::cut(y1,0,h1) + step; + const longT hdy01 = dy01*cimg::sign(dx01)/2; + + dy01+=dy01?0:1; + + for (int y = cy0; y!=cy1; y+=step) { + const longT + yy0 = (longT)y - y0, + x = x0 + (dx01*yy0 + hdy01)/dy01; + const float + iz = iz0 + diz01*yy0/dy01, + txz = txz0 + dtxz01*yy0/dy01, + tyz = tyz0 + dtyz01*yy0/dy01; + tz *const ptrz = is_horizontal?zbuffer.data(y,x):zbuffer.data(x,y); + + if (x>=0 && x<=w1 && pattern&hatch && iz>=*ptrz) { + *ptrz = (tz)iz; + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz); + T *const ptrd = is_horizontal?data(y,x):data(x,y); + const tc *const color = &texture._atXY(tx,ty); + cimg_forC(*this,c) { + const T val = color[c*twhd]; + ptrd[c*_sc_whd] = opacity>=1?val:(T)(val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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; } } + if (!(hatch>>=1)) hatch = ~0U - (~0U>>1); } return *this; } @@ -41328,44 +48035,37 @@ namespace cimg_library_suffixed { - This function uses several call to the single CImg::draw_line() procedure, depending on the vectors size in \p points. **/ - template - CImg& draw_line(const CImg& points, + template + CImg& draw_line(const CImg& 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 : + if (is_empty() || !points) return *this; + if (!color) throw CImgArgumentException(_cimg_instance - "draw_line(): Invalid specified point set (%u,%u,%u,%u,%p).", + "draw_line(): Specified color is (null).", + cimg_instance); + if (points.height()!=2) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified point set (%u,%u,%u,%u).", cimg_instance, - points._width,points._height,points._depth,points._spectrum,points._data); + points._width,points._height,points._depth,points._spectrum); + CImg ipoints; + if (cimg::type::is_float()) ipoints = points.get_round(); + else ipoints.assign(points,cimg::type::string()==cimg::type::string()); - case 2 : { - const int x0 = (int)points(0,0), y0 = (int)points(0,1); - int ox = x0, oy = y0; - for (unsigned int i = 1; i0)?(float)std::atan2(v,u):0.0f, + deg = (float)(angle*cimg::PI/180), ang = (sq>0)?(float)std::atan2(v,u):0.f, l = (length>=0)?length:-length*(float)std::sqrt(sq)/100; if (sq>0) { const float @@ -41400,7 +48100,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a 2d spline. + //! Draw a 2D spline. /** \param x0 X-coordinate of the starting curve point \param y0 Y-coordinate of the starting curve point @@ -41416,9 +48116,9 @@ namespace cimg_library_suffixed { \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 + - 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 + - The spline is drawn as a sequence 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 @@ -41466,47 +48166,7 @@ namespace cimg_library_suffixed { 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 - CImg& 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. + //! Draw a textured 2D spline. /** \param x0 X-coordinate of the starting curve point \param y0 Y-coordinate of the starting curve point @@ -41544,7 +48204,8 @@ namespace cimg_library_suffixed { 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); + y0<=0?0:y0>=texture.height()?texture.height() - 1:y0).data(), + opacity); bool ninit_hatch = init_hatch; const float ax = u0 + u1 + 2*(x0 - x1), @@ -41592,7 +48253,7 @@ namespace cimg_library_suffixed { cimg_instance, points._width,points._height,points._depth,points._spectrum,points._data); - case 2 : { + default : { 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; @@ -41605,20 +48266,6 @@ namespace cimg_library_suffixed { 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=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 - CImg& _draw_triangle(const int x0, const int y0, - const int x1, const int y1, - const int x2, const int y2, + CImg& _draw_triangle(int x0, int y0, + int x1, int y1, + int x2, 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=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); + if (y0>y1) cimg::swap(x0,x1,y0,y1); + if (y0>y2) cimg::swap(x0,x2,y0,y2); + if (y1>y2) cimg::swap(x1,x2,y1,y2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + h1 = height() - 1, + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1); + const longT + dx01 = (longT)x1 - x0, dx02 = (longT)x2 - x0, dx12 = (longT)x2 - x1, + dy01 = std::max((longT)1,(longT)y1 - y0), + dy02 = std::max((longT)1,(longT)y2 - y0), + dy12 = std::max((longT)1,(longT)y2 - y1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float cbs = cimg::cut(brightness,0,2); + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const longT yy0 = (longT)y - y0, yy1 = (longT)y - y1; + longT + xm = yxM) cimg::swap(xm,xM); + cimg_draw_scanline(xm,xM,y,color,opacity,cbs); } return *this; } - //! Draw a filled 2d triangle. + //! Draw a filled 2D triangle. /** \param x0 X-coordinate of the first vertex. \param y0 Y-coordinate of the first vertex. @@ -42049,7 +48413,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a outlined 2d triangle. + //! Draw a outlined 2D triangle. /** \param x0 X-coordinate of the first vertex. \param y0 Y-coordinate of the first vertex. @@ -42078,7 +48442,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a filled 2d triangle, with z-buffering. + //! Draw a filled 2D triangle, with z-buffering. /** \param zbuffer Z-buffer image. \param x0 X-coordinate of the first vertex. @@ -42096,12 +48460,11 @@ namespace cimg_library_suffixed { **/ template CImg& draw_triangle(CImg& 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, + int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const tc *const color, const float opacity=1, const float brightness=1) { - typedef typename cimg::superset::type tzfloat; if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (!color) throw CImgArgumentException(_cimg_instance @@ -42113,95 +48476,61 @@ namespace cimg_library_suffixed { "different dimensions.", cimg_instance, zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); - static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::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=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; + + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int w1 = width() - 1, h1 = height() - 1, cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1); + const longT + dx01 = (longT)x1 - x0, dx02 = (longT)x2 - x0, dx12 = (longT)x2 - x1, + dy01 = std::max((longT)1,(longT)y1 - y0), + dy02 = std::max((longT)1,(longT)y2 - y0), + dy12 = std::max((longT)1,(longT)y2 - y1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1; + + const float cbs = cimg::cut(brightness,0,2); + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const longT yy0 = (longT)y - y0, yy1 = (longT)y - y1; + longT + xm = yxM) cimg::swap(xm,xM,izm,izM); + if (xM>=0 && xm<=w1) { + const int + cxm = (int)cimg::cut(xm,(longT)0,(longT)w1), + cxM = (int)cimg::cut(xM,(longT)0,(longT)w1); + T *ptrd = data(cxm,y); + tz *ptrz = zbuffer.data(cxm,y); + const longT dxmM = std::max((longT)1,xM - xm); + const float dizmM = izM - izm; + + for (int x = cxm; x<=cxM; ++x) { + const longT xxm = x - xm; + const float iz = izm + dizmM*xxm/dxmM; + if (iz>=*ptrz) { + *ptrz = (tz)iz; cimg_forC(*this,c) { - const T val = (T)((2 - nbrightness)**(col++) + (nbrightness - 1)*maxval); - *ptrd = (T)(nopacity*val + *ptrd*copacity); - ptrd+=whd; + const Tfloat val = cbs<=1?color[c]*cbs:(2 - cbs)*color[c] + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - ptrd-=offx; } - zleft+=pentez; + ++ptrd; ++ptrz; } } - zr+=pzr; zl+=pzl; } return *this; } - //! Draw a Gouraud-shaded 2d triangle. + //! 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. @@ -42210,84 +48539,84 @@ namespace cimg_library_suffixed { \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 bs0 Brightness factor of the first vertex (in [0,2]). + \param bs1 brightness factor of the second vertex (in [0,2]). + \param bs2 brightness factor of the third vertex (in [0,2]). \param opacity Drawing opacity. **/ template - CImg& draw_triangle(const int x0, const int y0, - const int x1, const int y1, - const int x2, const int y2, + CImg& draw_triangle(int x0, int y0, + int x1, int y1, + int x2, int y2, const tc *const color, - const float brightness0, - const float brightness1, - const float brightness2, + float bs0, + float bs1, + float bs2, 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::max(),(T)cimg::type::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 (xrightcleft?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; + + if (y0>y1) cimg::swap(x0,x1,y0,y1,bs0,bs1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,bs0,bs2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,bs1,bs2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int w1 = width() - 1, h1 = height() - 1, cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1); + const longT + dx01 = (longT)x1 - x0, dx02 = (longT)x2 - x0, dx12 = (longT)x2 - x1, + dy01 = std::max((longT)1,(longT)y1 - y0), + dy02 = std::max((longT)1,(longT)y2 - y0), + dy12 = std::max((longT)1,(longT)y2 - y1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float dbs01 = bs1 - bs0, dbs02 = bs2 - bs0, dbs12 = bs2 - bs1; + + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const longT yy0 = (longT)y - y0, yy1 = (longT)y - y1; + longT + xm = yxM) cimg::swap(xm,xM,bsm,bsM); + if (xM>=0 && xm<=w1) { + const int + cxm = (int)cimg::cut(xm,(longT)0,(longT)w1), + cxM = (int)cimg::cut(xM,(longT)0,(longT)w1); + T *ptrd = data(cxm,y); + const longT dxmM = std::max((longT)1,xM - xm); + const float dbsmM = bsM - bsm; + + for (int x = cxm; x<=cxM; ++x) { + const longT xxm = (longT)x - xm; + const float cbs = cimg::cut(bsm + dbsmM*xxm/dxmM,0,2); + cimg_forC(*this,c) { + const Tfloat val = cbs<=1?color[c]*cbs:(2 - cbs)*color[c] + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); + } + ++ptrd; } - 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. + //! Draw a Gouraud-shaded 2D triangle, with z-buffering \overloading. template CImg& draw_triangle(CImg& 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, + int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, 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::type tzfloat; + float bs0, + float bs1, + float bs2, + float opacity=1) { if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (!color) throw CImgArgumentException(_cimg_instance @@ -42299,77 +48628,64 @@ namespace cimg_library_suffixed { "different dimensions.", cimg_instance, zbuffer._width,zbuffer._height,zbuffer._depth,zbuffer._spectrum,zbuffer._data); - static const T maxval = (T)std::min(cimg::type::max(),(T)cimg::type::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 (xrightcleft?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; + + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,bs0,bs1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,bs0,bs2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,bs1,bs2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float + diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1, + dbs01 = bs1 - bs0, dbs02 = bs2 - bs0, dbs12 = bs2 - bs1; + + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; + int + xm = yxM) cimg::swap(xm,xM,izm,izM,bsm,bsM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + tz *ptrz = zbuffer.data(cxm,y); + const int dxmM = std::max(1,xM - xm); + const float dizmM = izM - izm, dbsmM = bsM - bsm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float iz = izm + dizmM*xxm/dxmM; + if (iz>=*ptrz) { + *ptrz = (tz)iz; + const float cbs = cimg::cut(bsm + dbsmM*xxm/dxmM,0,2); cimg_forC(*this,c) { - *ptrd = (T)(cleft<256?cleft**(col++)/256:((512 - cleft)**(col++)+(cleft - 256)*maxval)/256); - ptrd+=whd; + const Tfloat val = cbs<=1?color[c]*cbs:(2 - cbs)*color[c] + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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); + ++ptrd; ++ptrz; } - zr+=pzr; zl+=pzl; + } } return *this; } - //! Draw a color-interpolated 2d triangle. + //! 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. @@ -42378,25 +48694,72 @@ namespace cimg_library_suffixed { \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 color2 Pointer to \c spectrum() consecutive values of type \c T, defining the color of the second 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 - CImg& 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, + template + CImg& draw_triangle(int x0, int y0, + int x1, int y1, + int x2, int y2, + const tc *color0, + const tc *color1, + const tc *color2, 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); + typedef typename cimg::superset::type stc; + if (is_empty()) return *this; + if (!color0 || !color1 || !color2) + throw CImgArgumentException(_cimg_instance + "draw_triangle(): One of the specified color is (null).", + cimg_instance); + + if (y0>y1) cimg::swap(x0,x1,y0,y1,color0,color1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,color0,color2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,color1,color2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int w1 = width() - 1, h1 = height() - 1, cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1); + const longT + dx01 = (longT)x1 - x0, dx02 = (longT)x2 - x0, dx12 = (longT)x2 - x1, + dy01 = std::max((longT)1,(longT)y1 - y0), + dy02 = std::max((longT)1,(longT)y2 - y0), + dy12 = std::max((longT)1,(longT)y2 - y1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + cimg_init_scanline(opacity); + + cimg_forC(*this,c) { + const stc dcolor01 = color1[c] - color0[c], dcolor02 = color2[c] - color0[c], dcolor12 = color2[c] - color1[c]; + + for (int y = cy0; y<=cy2; ++y) { + const longT yy0 = (longT)y - y0, yy1 = (longT)y - y1; + longT + xm = yxM) cimg::swap(xm,xM,colorm,colorM); + if (xM>=0 && xm<=w1) { + const int + cxm = (int)cimg::cut(xm,(longT)0,(longT)w1), + cxM = (int)cimg::cut(xM,(longT)0,(longT)w1); + T *ptrd = data(cxm,y); + const longT dxmM = std::max((longT)1,xM - xm); + const stc dcolormM = colorM - colorm; + + for (int x = cxm; x<=cxM; ++x) { + const longT xxm = (longT)x - xm; + const stc col = colorm + dcolormM*xxm/dxmM; + ptrd[c*_sc_whd] = (T)(opacity>=1?col:col*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); + ++ptrd; + } + } + } + } return *this; } - //! Draw a textured 2d triangle. + //! 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. @@ -42415,13 +48778,13 @@ namespace cimg_library_suffixed { \param brightness Brightness factor of the drawing (in [0,2]). **/ template - CImg& draw_triangle(const int x0, const int y0, - const int x1, const int y1, - const int x2, const int y2, + CImg& draw_triangle(int x0, int y0, + int x1, int y1, + int x2, int y2, const CImg& texture, - const int tx0, const int ty0, - const int tx1, const int ty1, - const int tx2, const int ty2, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, const float opacity=1, const float brightness=1) { if (is_empty()) return *this; @@ -42432,118 +48795,71 @@ namespace cimg_library_suffixed { 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::max(),cimg::type::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) { + + if (y0>y1) cimg::swap(x0,x1,y0,y1,tx0,tx1,ty0,ty1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,tx0,tx2,ty0,ty2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,tx1,ty1,tx2,ty2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2, + dtx01 = tx1 - tx0, dtx02 = tx2 - tx0, dtx12 = tx2 - tx1, + dty01 = ty1 - ty0, dty02 = ty2 - ty0, dty12 = ty2 - ty1, + hdy01tx = dy01*cimg::sign(dtx01)/2, hdy02tx = dy02*cimg::sign(dtx02)/2, hdy12tx = dy12*cimg::sign(dtx12)/2, + hdy01ty = dy01*cimg::sign(dty01)/2, hdy02ty = dy02*cimg::sign(dty02)/2, hdy12ty = dy12*cimg::sign(dty12)/2; + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; + const float cbs = cimg::cut(brightness,0,2); + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; int - xleft = xleft0, xright = xright0, - txleft = txleft0, txright = txright0, - tyleft = tyleft0, tyright = tyright0; - if (xrighttxleft?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); + xm = yxM) cimg::swap(xm,xM,txm,txM,tym,tyM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + const int + dxmM = std::max(1,xM - xm), hdxmM = dxmM/2, + dtxmM = txM - txm, dtymM = tyM - tym; + + for (int x = cxm; x<=cxM; ++x) { + const int + xxm = x - xm, + tx = (txm*dxmM + dtxmM*xxm + hdxmM)/dxmM, + ty = (tym*dxmM + dtymM*xxm + hdxmM)/dxmM; + const tc *const color = &texture._atXY(tx,ty); cimg_forC(*this,c) { - *ptrd = (T)*col; - ptrd+=whd; col+=twh; + const Tfloat val = cbs<=1?color[c*twhd]*cbs:(2 - cbs)*color[c*twhd] + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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); + ++ptrd; } } } return *this; } - //! Draw a 2d textured triangle, with perspective correction. + //! Draw a 2D textured triangle, with perspective correction. template - CImg& 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, + CImg& draw_triangle(int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const CImg& texture, - const int tx0, const int ty0, - const int tx1, const int ty1, - const int tx2, const int ty2, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, const float opacity=1, const float brightness=1) { if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; @@ -42554,135 +48870,84 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::max()); + + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,tx0,tx1,ty0,ty1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,tx0,tx2,ty0,ty2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,tx1,tx2,ty1,ty2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; 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; + diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1, + txz0 = tx0*iz0, txz1 = tx1*iz1, txz2 = tx2*iz2, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, tyz2 = ty2*iz2, + dtxz01 = txz1 - txz0, dtxz02 = txz2 - txz0, dtxz12 = txz2 - txz1, + dtyz01 = tyz1 - tyz0, dtyz02 = tyz2 - tyz0, dtyz12 = tyz2 - tyz1; + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; + const float cbs = cimg::cut(brightness,0,2); + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; + int + xm = y=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)); + izm = yxM) cimg::swap(xm,xM,txzm,txzM,tyzm,tyzM,izm,izM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + const int dxmM = std::max(1,xM - xm); + const float dizmM = izM - izm, dtxzmM = txzM - txzm, dtyzmM = tyzM - tyzm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float + iz = izm + dizmM*xxm/dxmM, + txz = txzm + dtxzmM*xxm/dxmM, + tyz = tyzm + dtyzmM*xxm/dxmM; + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz); + const tc *const color = &texture._atXY(tx,ty); cimg_forC(*this,c) { - *ptrd = (T)*col; - ptrd+=whd; col+=twh; + const Tfloat val = cbs<=1?color[c*twhd]*cbs:(2 - cbs)*color[c*twhd] + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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; + ++ptrd; } } - 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. + //! Draw a textured 2D triangle, with perspective correction and z-buffering. template CImg& draw_triangle(CImg& 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, + int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const CImg& texture, - const int tx0, const int ty0, - const int tx1, const int ty1, - const int tx2, const int ty2, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, const float opacity=1, const float brightness=1) { - typedef typename cimg::superset::type tzfloat; if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (!is_sameXY(zbuffer)) throw CImgArgumentException(_cimg_instance @@ -42698,145 +48963,77 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::max()); + + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,tx0,tx1,ty0,ty1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,tx0,tx2,ty0,ty2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,tx1,tx2,ty1,ty2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; 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=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; + diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1, + txz0 = tx0*iz0, txz1 = tx1*iz1, txz2 = tx2*iz2, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, tyz2 = ty2*iz2, + dtxz01 = txz1 - txz0, dtxz02 = txz2 - txz0, dtxz12 = txz2 - txz1, + dtyz01 = tyz1 - tyz0, dtyz02 = tyz2 - tyz0, dtyz12 = tyz2 - tyz1; + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; + const float cbs = cimg::cut(brightness,0,2); + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; + int + xm = yxM) cimg::swap(xm,xM,txzm,txzM,tyzm,tyzM,izm,izM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + tz *ptrz = zbuffer.data(cxm,y); + const int dxmM = std::max(1,xM - xm); + const float dizmM = izM - izm, dtxzmM = txzM - txzm, dtyzmM = tyzM - tyzm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float iz = izm + dizmM*xxm/dxmM; + if (iz>=*ptrz) { + *ptrz = (tz)iz; + const float + txz = txzm + dtxzmM*xxm/dxmM, + tyz = tyzm + dtyzmM*xxm/dxmM; + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz); + const tc *const color = &texture._atXY(tx,ty); + cimg_forC(*this,c) { + const Tfloat val = cbs<=1?color[c*twhd]*cbs:(2 - cbs)*color[c*twhd] + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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; } + ++ptrd; ++ptrz; + } } - zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; } return *this; } - //! Draw a Phong-shaded 2d triangle. + //! 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. @@ -42855,14 +49052,14 @@ namespace cimg_library_suffixed { \param opacity Drawing opacity. **/ template - CImg& draw_triangle(const int x0, const int y0, - const int x1, const int y1, - const int x2, const int y2, + CImg& draw_triangle(int x0, int y0, + int x1, int y1, + int x2, int y2, const tc *const color, const CImg& light, - const int lx0, const int ly0, - const int lx1, const int ly1, - const int lx2, const int ly2, + int lx0, int ly0, + int lx1, int ly1, + int lx2, int ly2, const float opacity=1) { if (is_empty()) return *this; if (!color) @@ -42873,85 +49070,76 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::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) { + + if (y0>y1) cimg::swap(x0,x1,y0,y1,lx0,lx1,ly0,ly1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,lx0,lx2,ly0,ly2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,lx1,lx2,ly1,ly2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2, + dlx01 = lx1 - lx0, dlx02 = lx2 - lx0, dlx12 = lx2 - lx1, + dly01 = ly1 - ly0, dly02 = ly2 - ly0, dly12 = ly2 - ly1, + hdy01lx = dy01*cimg::sign(dlx01)/2, hdy02lx = dy02*cimg::sign(dlx02)/2, hdy12lx = dy12*cimg::sign(dlx12)/2, + hdy01ly = dy01*cimg::sign(dly01)/2, hdy02ly = dy02*cimg::sign(dly02)/2, hdy12ly = dy12*cimg::sign(dly12)/2; + + const ulongT lwhd = (ulongT)light._width*light._height*light._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; int - xleft = xleft0, xright = xright0, - lxleft = lxleft0, lxright = lxright0, - lyleft = lyleft0, lyright = lyright0; - if (xrightlxleft?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; + xm = yxM) cimg::swap(xm,xM,lxm,lxM,lym,lyM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + const int + dxmM = std::max(1,xM - xm), hdxmM = dxmM/2, + dlxmM = lxM - lxm, dlymM = lyM - lym; + + for (int x = cxm; x<=cxM; ++x) { + const int + xxm = x - xm, + lx = (lxm*dxmM + dlxmM*xxm + hdxmM)/dxmM, + ly = (lym*dxmM + dlymM*xxm + hdxmM)/dxmM; + const tl *const lig = &light._atXY(lx,ly); + cimg_forC(*this,c) { + const tc col = color[c]; + const float cbs = cimg::cut((float)lig[c*lwhd],0,2); + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); + } + ++ptrd; } - 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. + //! Draw a Phong-shaded 2D triangle, with z-buffering. template CImg& draw_triangle(CImg& 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, + int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const tc *const color, const CImg& light, - const int lx0, const int ly0, - const int lx1, const int ly1, - const int lx2, const int ly2, + int lx0, int ly0, + int lx1, int ly1, + int lx2, int ly2, const float opacity=1) { - typedef typename cimg::superset::type tzfloat; if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (!color) throw CImgArgumentException(_cimg_instance @@ -42969,95 +49157,77 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::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; } + + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,lx0,lx1,ly0,ly1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,lx0,lx2,ly0,ly2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,lx1,lx2,ly1,ly2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2, + dlx01 = lx1 - lx0, dlx02 = lx2 - lx0, dlx12 = lx2 - lx1, + dly01 = ly1 - ly0, dly02 = ly2 - ly0, dly12 = ly2 - ly1, + hdy01lx = dy01*cimg::sign(dlx01)/2, hdy02lx = dy02*cimg::sign(dlx02)/2, hdy12lx = dy12*cimg::sign(dlx12)/2, + hdy01ly = dy01*cimg::sign(dly01)/2, hdy02ly = dy02*cimg::sign(dly02)/2, hdy12ly = dy12*cimg::sign(dly12)/2; + const float diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1; + + const ulongT lwhd = (ulongT)light._width*light._height*light._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; int - xleft = xleft0, xright = xright0, - lxleft = lxleft0, lxright = lxright0, - lyleft = lyleft0, lyright = lyright0; - tzfloat zleft = zl, zright = zr; - if (xrightlxleft?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); + xm = yxM) cimg::swap(xm,xM,lxm,lxM,lym,lyM,izm,izM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + tz *ptrz = zbuffer.data(cxm,y); + const int + dxmM = std::max(1,xM - xm), hdxmM = dxmM/2, + dlxmM = lxM - lxm, dlymM = lyM - lym; + const float dizmM = izM - izm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float iz = izm + dizmM*xxm/dxmM; + if (iz>=*ptrz) { + *ptrz = (tz)iz; + const int + lx = (lxm*dxmM + dlxmM*xxm + hdxmM)/dxmM, + ly = (lym*dxmM + dlymM*xxm + hdxmM)/dxmM; + const tl *const lig = &light._atXY(lx,ly); 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; + const float cbs = cimg::cut((float)lig[c*lwhd],0,2); + const tc col = color[c]; + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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); + ++ptrd; ++ptrz; } - zr+=pzr; zl+=pzl; + } } return *this; } - //! Draw a textured Gouraud-shaded 2d triangle. + //! 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. @@ -43072,22 +49242,22 @@ namespace cimg_library_suffixed { \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 bs0 Brightness factor of the first vertex. + \param bs1 Brightness factor of the second vertex. + \param bs2 Brightness factor of the third vertex. \param opacity Drawing opacity. **/ template - CImg& draw_triangle(const int x0, const int y0, - const int x1, const int y1, - const int x2, const int y2, + CImg& draw_triangle(int x0, int y0, + int x1, int y1, + int x2, int y2, const CImg& 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, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, + float bs0, + float bs1, + float bs2, const float opacity=1) { if (is_empty()) return *this; if (texture._depth>1 || texture._spectrum<_spectrum) @@ -43097,91 +49267,82 @@ namespace cimg_library_suffixed { 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::max(),cimg::type::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) { + bs0,bs1,bs2,opacity); + + if (y0>y1) cimg::swap(x0,x1,y0,y1,tx0,tx1,ty0,ty1,bs0,bs1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,tx0,tx2,ty0,ty2,bs0,bs2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,tx1,tx2,ty1,ty2,bs1,bs2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2, + dtx01 = tx1 - tx0, dtx02 = tx2 - tx0, dtx12 = tx2 - tx1, + dty01 = ty1 - ty0, dty02 = ty2 - ty0, dty12 = ty2 - ty1, + hdy01tx = dy01*cimg::sign(dtx01)/2, hdy02tx = dy02*cimg::sign(dtx02)/2, hdy12tx = dy12*cimg::sign(dtx12)/2, + hdy01ty = dy01*cimg::sign(dty01)/2, hdy02ty = dy02*cimg::sign(dty02)/2, hdy12ty = dy12*cimg::sign(dty12)/2; + const float dbs01 = bs1 - bs0, dbs02 = bs2 - bs0, dbs12 = bs2 - bs1; + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; int - xleft = xleft0, xright = xright0, - cleft = cleft0, cright = cright0, - txleft = txleft0, txright = txright0, - tyleft = tyleft0, tyright = tyright0; - if (xrightcleft?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; + xm = yxM) cimg::swap(xm,xM,txm,txM,tym,tyM,bsm,bsM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + const int + dxmM = std::max(1,xM - xm), hdxmM = dxmM/2, + dtxmM = txM - txm, dtymM = tyM - tym; + const float dbsmM = bsM - bsm; + + for (int x = cxm; x<=cxM; ++x) { + const int + xxm = x - xm, + tx = (txm*dxmM + dtxmM*xxm + hdxmM)/dxmM, + ty = (tym*dxmM + dtymM*xxm + hdxmM)/dxmM; + const float cbs = cimg::cut(bsm + dbsmM*xxm/dxmM,0,2); + const tc *const color = &texture._atXY(tx,ty); + cimg_forC(*this,c) { + const tc col = color[c*twhd]; + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); + } + ++ptrd; } - 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. + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction \overloading. template - CImg& 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, + CImg& draw_triangle(int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const CImg& 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, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, + float bs0, + float bs1, + float bs2, const float opacity=1) { if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (texture._depth>1 || texture._spectrum<_spectrum) @@ -43190,114 +49351,91 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::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; } + bs0,bs1,bs2,opacity); + + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,tx0,tx1,ty0,ty1,bs0,bs1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,tx0,tx2,ty0,ty2,bs0,bs2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,tx1,tx2,ty1,ty2,bs1,bs2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float + diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1, + txz0 = tx0*iz0, txz1 = tx1*iz1, txz2 = tx2*iz2, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, tyz2 = ty2*iz2, + dtxz01 = txz1 - txz0, dtxz02 = txz2 - txz0, dtxz12 = txz2 - txz1, + dtyz01 = tyz1 - tyz0, dtyz02 = tyz2 - tyz0, dtyz12 = tyz2 - tyz1, + dbs01 = bs1 - bs0, dbs02 = bs2 - bs0, dbs12 = bs2 - bs1; + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; int - xleft = xleft0, xright = xright0, - cleft = cleft0, cright = cright0; + xm = ycleft?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; + izm = yxM) cimg::swap(xm,xM,txzm,txzM,tyzm,tyzM,izm,izM,bsm,bsM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + const int dxmM = std::max(1,xM - xm); + const float dizmM = izM - izm, dtxzmM = txzM - txzm, dtyzmM = tyzM - tyzm, dbsmM = bsM - bsm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float + iz = izm + dizmM*xxm/dxmM, + txz = txzm + dtxzmM*xxm/dxmM, + tyz = tyzm + dtyzmM*xxm/dxmM, + cbs = cimg::cut(bsm + dbsmM*xxm/dxmM,0,2); + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz); + const tc *const color = &texture._atXY(tx,ty); + cimg_forC(*this,c) { + const tc col = color[c*twhd]; + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); + } + ++ptrd; } - 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. + //! Draw a textured Gouraud-shaded 2D triangle, with perspective correction and z-buffering \overloading. template CImg& draw_triangle(CImg& 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, + int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const CImg& 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, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, + float bs0, + float bs1, + float bs2, const float opacity=1) { - typedef typename cimg::superset::type tzfloat; if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (!is_sameXY(zbuffer)) throw CImgArgumentException(_cimg_instance @@ -43311,105 +49449,82 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::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 (xrightcleft?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)); + return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,bs0,bs1,bs2,opacity); + + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,tx0,tx1,ty0,ty1,bs0,bs1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,tx0,tx2,ty0,ty2,bs0,bs2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,tx1,tx2,ty1,ty2,bs1,bs2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float + diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1, + txz0 = tx0*iz0, txz1 = tx1*iz1, txz2 = tx2*iz2, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, tyz2 = ty2*iz2, + dtxz01 = txz1 - txz0, dtxz02 = txz2 - txz0, dtxz12 = txz2 - txz1, + dtyz01 = tyz1 - tyz0, dtyz02 = tyz2 - tyz0, dtyz12 = tyz2 - tyz1, + dbs01 = bs1 - bs0, dbs02 = bs2 - bs0, dbs12 = bs2 - bs1; + + const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; + int + xm = yxM) cimg::swap(xm,xM,txzm,txzM,tyzm,tyzM,izm,izM,bsm,bsM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + tz *ptrz = zbuffer.data(cxm,y); + const int dxmM = std::max(1,xM - xm); + const float dizmM = izM - izm, dtxzmM = txzM - txzm, dtyzmM = tyzM - tyzm, dbsmM = bsM - bsm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float iz = izm + dizmM*xxm/dxmM; + if (iz>=*ptrz) { + *ptrz = (tz)iz; + const float + txz = txzm + dtxzmM*xxm/dxmM, + tyz = tyzm + dtyzmM*xxm/dxmM, + cbs = cimg::cut(bsm + dbsmM*xxm/dxmM,0,2); + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz); + const tc *const color = &texture._atXY(tx,ty); cimg_forC(*this,c) { - *ptrd = (T)(cleft<256?cleft**col/256:((512 - cleft)**col + (cleft - 256)*maxval)/256); - ptrd+=whd; col+=twh; + const tc col = color[c*twhd]; + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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); + ++ptrd; ++ptrz; } - zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } } return *this; } - //! Draw a textured Phong-shaded 2d triangle. + //! 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. @@ -43434,17 +49549,17 @@ namespace cimg_library_suffixed { \param opacity Drawing opacity. **/ template - CImg& draw_triangle(const int x0, const int y0, - const int x1, const int y1, - const int x2, const int y2, + CImg& draw_triangle(int x0, int y0, + int x1, int y1, + int x2, int y2, const CImg& texture, - const int tx0, const int ty0, - const int tx1, const int ty1, - const int tx2, const int ty2, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, const CImg& light, - const int lx0, const int ly0, - const int lx1, const int ly1, - const int lx2, const int ly2, + int lx0, int ly0, + int lx1, int ly1, + int lx2, int ly2, const float opacity=1) { if (is_empty()) return *this; if (texture._depth>1 || texture._spectrum<_spectrum) @@ -43460,107 +49575,91 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::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) { + if (y0>y1) cimg::swap(x0,x1,y0,y1,tx0,tx1,ty0,ty1,lx0,lx1,ly0,ly1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,tx0,tx2,ty0,ty2,lx0,lx2,ly0,ly2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,tx1,tx2,ty1,ty2,lx1,lx2,ly1,ly2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2, + dtx01 = tx1 - tx0, dtx02 = tx2 - tx0, dtx12 = tx2 - tx1, + dty01 = ty1 - ty0, dty02 = ty2 - ty0, dty12 = ty2 - ty1, + hdy01tx = dy01*cimg::sign(dtx01)/2, hdy02tx = dy02*cimg::sign(dtx02)/2, hdy12tx = dy12*cimg::sign(dtx12)/2, + hdy01ty = dy01*cimg::sign(dty01)/2, hdy02ty = dy02*cimg::sign(dty02)/2, hdy12ty = dy12*cimg::sign(dty12)/2, + dlx01 = lx1 - lx0, dlx02 = lx2 - lx0, dlx12 = lx2 - lx1, + dly01 = ly1 - ly0, dly02 = ly2 - ly0, dly12 = ly2 - ly1, + hdy01lx = dy01*cimg::sign(dlx01)/2, hdy02lx = dy02*cimg::sign(dlx02)/2, hdy12lx = dy12*cimg::sign(dlx12)/2, + hdy01ly = dy01*cimg::sign(dly01)/2, hdy02ly = dy02*cimg::sign(dly02)/2, hdy12ly = dy12*cimg::sign(dly12)/2; + + const ulongT + twhd = (ulongT)texture._width*texture._height*texture._depth, + lwhd = (ulongT)light._width*light._height*light._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; int - xleft = xleft0, xright = xright0, - lxleft = lxleft0, lxright = lxright0, - lyleft = lyleft0, lyright = lyright0, - txleft = txleft0, txright = txright0, - tyleft = tyleft0, tyright = tyright0; - if (xrightlxleft?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; + xm = yxM) cimg::swap(xm,xM,txm,txM,tym,tyM,lxm,lxM,lym,lyM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + const int + dxmM = std::max(1,xM - xm), hdxmM = dxmM/2, + dtxmM = txM - txm, dtymM = tyM - tym, + dlxmM = lxM - lxm, dlymM = lyM - lym; + + for (int x = cxm; x<=cxM; ++x) { + const int + xxm = x - xm, + tx = (txm*dxmM + dtxmM*xxm + hdxmM)/dxmM, + ty = (tym*dxmM + dtymM*xxm + hdxmM)/dxmM, + lx = (lxm*dxmM + dlxmM*xxm + hdxmM)/dxmM, + ly = (lym*dxmM + dlymM*xxm + hdxmM)/dxmM; + const tc *const color = &texture._atXY(tx,ty); + const tl *const lig = &light._atXY(lx,ly); + cimg_forC(*this,c) { + const tc col = color[c*twhd]; + const float cbs = cimg::cut((float)lig[c*lwhd],0,2); + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); + } + ++ptrd; } - 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. + //! Draw a textured Phong-shaded 2D triangle, with perspective correction. template - CImg& 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, + CImg& draw_triangle(int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const CImg& texture, - const int tx0, const int ty0, - const int tx1, const int ty1, - const int tx2, const int ty2, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, const CImg& light, - const int lx0, const int ly0, - const int lx1, const int ly1, - const int lx2, const int ly2, + int lx0, int ly0, + int lx1, int ly1, + int lx2, int ly2, const float opacity=1) { if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (texture._depth>1 || texture._spectrum<_spectrum) @@ -43578,132 +49677,106 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::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; } + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,tx0,tx1,ty0,ty1,lx0,lx1,ly0,ly1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,tx0,tx2,ty0,ty2,lx0,lx2,ly0,ly2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,tx1,tx2,ty1,ty2,lx1,lx2,ly1,ly2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float + diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1, + txz0 = tx0*iz0, txz1 = tx1*iz1, txz2 = tx2*iz2, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, tyz2 = ty2*iz2, + dtxz01 = txz1 - txz0, dtxz02 = txz2 - txz0, dtxz12 = txz2 - txz1, + dtyz01 = tyz1 - tyz0, dtyz02 = tyz2 - tyz0, dtyz12 = tyz2 - tyz1, + lxz0 = lx0*iz0, lxz1 = lx1*iz1, lxz2 = lx2*iz2, + lyz0 = ly0*iz0, lyz1 = ly1*iz1, lyz2 = ly2*iz2, + dlxz01 = lxz1 - lxz0, dlxz02 = lxz2 - lxz0, dlxz12 = lxz2 - lxz1, + dlyz01 = lyz1 - lyz0, dlyz02 = lyz2 - lyz0, dlyz12 = lyz2 - lyz1; + + const ulongT + twhd = (ulongT)texture._width*texture._height*texture._depth, + lwhd = (ulongT)light._width*light._height*light._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; int - xleft = xleft0, xright = xright0, - lxleft = lxleft0, lxright = lxright0, - lyleft = lyleft0, lyright = lyright0; + xm = ylxleft?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; + izm = yxM) cimg::swap(xm,xM,izm,izM,txzm,txzM,tyzm,tyzM,lxzm,lxzM,lyzm,lyzM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + const int dxmM = std::max(1,xM - xm); + const float + dizmM = izM - izm, + dtxzmM = txzM - txzm, dtyzmM = tyzM - tyzm, + dlxzmM = lxzM - lxzm, dlyzmM = lyzM - lyzm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float + iz = izm + dizmM*xxm/dxmM, + txz = txzm + dtxzmM*xxm/dxmM, + tyz = tyzm + dtyzmM*xxm/dxmM, + lxz = lxzm + dlxzmM*xxm/dxmM, + lyz = lyzm + dlyzmM*xxm/dxmM; + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz), + lx = (int)cimg::round(lxz/iz), + ly = (int)cimg::round(lyz/iz); + const tc *const color = &texture._atXY(tx,ty); + const tl *const lig = &light._atXY(lx,ly); + cimg_forC(*this,c) { + const tc col = color[c*twhd]; + const float cbs = cimg::cut((float)lig[c*lwhd],0,2); + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); + } + ++ptrd; } - 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. + //! Draw a textured Phong-shaded 2D triangle, with perspective correction and z-buffering. template CImg& draw_triangle(CImg& 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, + int x0, int y0, const float z0, + int x1, int y1, const float z1, + int x2, int y2, const float z2, const CImg& texture, - const int tx0, const int ty0, - const int tx1, const int ty1, - const int tx2, const int ty2, + int tx0, int ty0, + int tx1, int ty1, + int tx2, int ty2, const CImg& light, - const int lx0, const int ly0, - const int lx1, const int ly1, - const int lx2, const int ly2, + int lx0, int ly0, + int lx1, int ly1, + int lx2, int ly2, const float opacity=1) { - typedef typename cimg::superset::type tzfloat; if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this; if (!is_sameXY(zbuffer)) throw CImgArgumentException(_cimg_instance @@ -43726,123 +49799,96 @@ namespace cimg_library_suffixed { 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::max(),(T)cimg::type::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; } + float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2; + if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,tx0,tx1,ty0,ty1,lx0,lx1,ly0,ly1); + if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,tx0,tx2,ty0,ty2,lx0,lx2,ly0,ly2); + if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,tx1,tx2,ty1,ty2,lx1,lx2,ly1,ly2); + if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this; + + const int + w1 = width() - 1, h1 = height() - 1, + dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1, + dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1), + cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1), + hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2; + const float + diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1, + txz0 = tx0*iz0, txz1 = tx1*iz1, txz2 = tx2*iz2, + tyz0 = ty0*iz0, tyz1 = ty1*iz1, tyz2 = ty2*iz2, + dtxz01 = txz1 - txz0, dtxz02 = txz2 - txz0, dtxz12 = txz2 - txz1, + dtyz01 = tyz1 - tyz0, dtyz02 = tyz2 - tyz0, dtyz12 = tyz2 - tyz1, + lxz0 = lx0*iz0, lxz1 = lx1*iz1, lxz2 = lx2*iz2, + lyz0 = ly0*iz0, lyz1 = ly1*iz1, lyz2 = ly2*iz2, + dlxz01 = lxz1 - lxz0, dlxz02 = lxz2 - lxz0, dlxz12 = lxz2 - lxz1, + dlyz01 = lyz1 - lyz0, dlyz02 = lyz2 - lyz0, dlyz12 = lyz2 - lyz1; + + const ulongT + twhd = (ulongT)texture._width*texture._height*texture._depth, + lwhd = (ulongT)light._width*light._height*light._depth; + cimg_init_scanline(opacity); + + for (int y = cy0; y<=cy2; ++y) { + const int yy0 = y - y0, yy1 = y - y1; 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 (xrightlxleft?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); + xm = yxM) cimg::swap(xm,xM,izm,izM,txzm,txzM,tyzm,tyzM,lxzm,lxzM,lyzm,lyzM); + if (xM>=0 && xm<=w1) { + const int + cxm = cimg::cut(xm,0,w1), + cxM = cimg::cut(xM,0,w1); + T *ptrd = data(cxm,y); + tz *ptrz = zbuffer.data(cxm,y); + const int dxmM = std::max(1,xM - xm); + const float + dizmM = izM - izm, + dtxzmM = txzM - txzm, dtyzmM = tyzM - tyzm, + dlxzmM = lxzM - lxzm, dlyzmM = lyzM - lyzm; + + for (int x = cxm; x<=cxM; ++x) { + const int xxm = x - xm; + const float iz = izm + dizmM*xxm/dxmM; + if (iz>=*ptrz) { + *ptrz = (tz)iz; + const float + txz = txzm + dtxzmM*xxm/dxmM, + tyz = tyzm + dtyzmM*xxm/dxmM, + lxz = lxzm + dlxzmM*xxm/dxmM, + lyz = lyzm + dlyzmM*xxm/dxmM; + const int + tx = (int)cimg::round(txz/iz), + ty = (int)cimg::round(tyz/iz), + lx = (int)cimg::round(lxz/iz), + ly = (int)cimg::round(lyz/iz); + const tc *const color = &texture._atXY(tx,ty); + const tl *const lig = &light._atXY(lx,ly); 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; + const tc col = color[c*twhd]; + const float cbs = cimg::cut((float)lig[c*lwhd],0,2); + const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval; + ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity); } - 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); + ++ptrd; ++ptrz; } - zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl; + } } return *this; } - //! Draw a filled 4d rectangle. + //! 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. @@ -43865,24 +49911,24 @@ namespace cimg_library_suffixed { nz0 = z0=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); + 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); + 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.f); 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; v0 && ly>0 && lz>0 && lc>0) + for (int v = 0; v=1) { - if (sizeof(T)!=1) { for (int x = 0; x - CImg& 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. + //! 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. @@ -43951,7 +49977,7 @@ namespace cimg_library_suffixed { return draw_rectangle(x0,y0,0,x1,y1,_depth - 1,color,opacity); } - //! Draw a outlined 2d rectangle \overloading. + //! Draw a outlined 2D rectangle \overloading. template CImg& draw_rectangle(const int x0, const int y0, const int x1, const int y1, @@ -43971,7 +49997,7 @@ namespace cimg_library_suffixed { draw_line(nx0,ny1 - 1,nx0,ny0 + 1,color,opacity,pattern,false); } - //! Draw a filled 2d polygon. + //! 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. @@ -43985,63 +50011,65 @@ namespace cimg_library_suffixed { 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); + if (points.height()!=2) + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Invalid specified point set (%u,%u,%u,%u).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum); + CImg ipoints; + if (cimg::type::is_float()) ipoints = points.get_round(); + else ipoints.assign(points,cimg::type::string()==cimg::type::string()); + + if (ipoints._width==1) return draw_point(ipoints(0,0),ipoints(0,1),color,opacity); + if (ipoints._width==2) return draw_line(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1),color,opacity); + if (ipoints._width==3) return draw_triangle(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1), + ipoints(2,0),ipoints(2,1),color,opacity); + cimg_init_scanline(opacity); int xmin = 0, ymin = 0, - xmax = points.get_shared_row(0).max_min(xmin), - ymax = points.get_shared_row(1).max_min(ymin); + xmax = ipoints.get_shared_row(0).max_min(xmin), + ymax = ipoints.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 Xs(points._width,ymax - ymin + 1); + CImg Xs(ipoints._width,ymax - ymin + 1); CImg 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 an = (nn + 1)%ipoints._width; + const int x0 = ipoints(n,0), y0 = ipoints(n,1); + if (ipoints(nn,1)==y0) while (ipoints(an,1)==y0) { nn = an; (an+=1)%=ipoints._width; } + const int x1 = ipoints(nn,0), y1 = ipoints(nn,1); unsigned int tn = an; - while (points(tn,1)==y1) (tn+=1)%=points._width; - + while (ipoints(tn,1)==y1) (tn+=1)%=ipoints._width; if (y0!=y1) { const int - y2 = (int)points(tn,1), + y2 = ipoints(tn,1), x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1, - dy = cimg::sign(y01), + step = cimg::sign(y01), tmax = std::max(1,cimg::abs(y01)), - tend = tmax - (dy==cimg::sign(y12)); + htmax = tmax*cimg::sign(x01)/2, + tend = tmax - (step==cimg::sign(y12)); unsigned int y = (unsigned int)y0 - ymin; - for (int t = 0; t<=tend; ++t, y+=dy) - if (yn; n = nn; nn = an; } - cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>32)) + cimg_pragma_openmp(parallel for cimg_openmp_if(Xs._height>=(cimg_openmp_sizefactor)*512)) cimg_forY(Xs,y) { const CImg Xsy = Xs.get_shared_points(0,count[y] - 1,y).sort(); int px = width(); - for (unsigned int n = 0; n - CImg& draw_polygon(const CImg& points, + //! Draw a outlined 2D or 3D polygon \overloading. + template + CImg& draw_polygon(const CImg& 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 : + if (is_empty() || !points) return *this; + if (!color) throw CImgArgumentException(_cimg_instance - "draw_polygon(): Invalid specified point set.", + "draw_polygon(): Specified color is (null).", cimg_instance); - case 2 : { // 2d version. - CImg 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 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 ipoints; + if (cimg::type::is_float()) ipoints = points.get_round(); + else ipoints.assign(points,cimg::type::string()==cimg::type::string()); + + if (ipoints._width==1) return draw_point(ipoints(0,0),ipoints(0,1),color,opacity); + if (ipoints._width==2) return draw_line(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1), + color,opacity,pattern); + if (ipoints._width==3) return draw_triangle(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1), + ipoints(2,0),ipoints(2,1),color,opacity,pattern); + bool ninit_hatch = true; + const int x0 = ipoints(0,0), y0 = ipoints(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i CImg& 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); + return _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,0U,true); } - //! Draw a filled 2d ellipse \overloading. + //! Draw a filled 2D ellipse \overloading. /** \param x0 X-coordinate of the ellipse center. \param y0 Y-coordinate of the ellipse center. @@ -44141,7 +50148,7 @@ namespace cimg_library_suffixed { color,opacity); } - //! Draw an outlined 2d ellipse. + //! Draw an outlined 2D ellipse. /** \param x0 X-coordinate of the ellipse center. \param y0 Y-coordinate of the ellipse center. @@ -44155,11 +50162,11 @@ namespace cimg_library_suffixed { template CImg& 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); + if (pattern) _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,pattern,false); return *this; } - //! Draw an outlined 2d ellipse \overloading. + //! Draw an outlined 2D ellipse \overloading. /** \param x0 X-coordinate of the ellipse center. \param y0 Y-coordinate of the ellipse center. @@ -44180,68 +50187,74 @@ namespace cimg_library_suffixed { } template - CImg& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle, + CImg& _draw_ellipse(const int x0, const int y0, const float radius1, const float radius2, const float angle, const tc *const color, const float opacity, - const unsigned int pattern) { - if (is_empty()) return *this; + const unsigned int pattern, const bool is_filled) { + if (is_empty() || (!is_filled && !pattern)) return *this; + const float radiusM = std::max(radius1,radius2); + if (radius1<0 || radius2<0 || x0 - radiusM>=width() || y0 + radiusM<0 || y0 - radiusM>=height()) 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,(int)cimg::round(r1),color,opacity,pattern); - else return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity); + const int iradius1 = (int)cimg::round(radius1), iradius2 = (int)cimg::round(radius2); + if (!iradius1 && !iradius2) return draw_point(x0,y0,color,opacity); + if (iradius1==iradius2) { + if (is_filled) return draw_circle(x0,y0,iradius1,color,opacity); + else if (pattern==~0U) return draw_circle(x0,y0,iradius1,color,opacity,pattern); } - 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)cimg::round(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 ang = (float)(angle*cimg::PI/180); + + if (!is_filled) { // Outlined + const float ca = std::cos(ang), sa = std::sin(ang); + CImg points((unsigned int)cimg::round(6*radiusM),2); + cimg_forX(points,k) { + const float + _ang = (float)(2*cimg::PI*k/points._width), + X = (float)(radius1*std::cos(_ang)), + Y = (float)(radius2*std::sin(_ang)); + points(k,0) = (int)cimg::round(x0 + (X*ca - Y*sa)); + points(k,1) = (int)cimg::round(y0 + (X*sa + Y*ca)); + } + draw_polygon(points,color,opacity,pattern); + } else { // Filled + cimg_init_scanline(opacity); const float - Y = y - y0 + (y0?(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=height()?height() - 1:_ymax; + for (int y = ymin; y<=ymax; ++y) { + const float + Y = y - y0 + 0.5f, + B = 2*t2*Y, + C = t3*Y*Y - 1, + D = B*B - 4*t1*C; + if (D>=0) { + const float sD = std::sqrt(D); + const int + xmin = (int)(x0 + cimg::round((-B - sD)/t12)), + xmax = (int)(x0 + cimg::round((-B + sD)/t12)); + cimg_draw_scanline(xmin,xmax,y,color,opacity,1); } } - oxmin = xmin; oxmax = xmax; } return *this; } - //! Draw a filled 2d circle. + //! Draw a filled 2D circle. /** \param x0 X-coordinate of the circle center. \param y0 Y-coordinate of the circle center. @@ -44255,12 +50268,13 @@ namespace cimg_library_suffixed { CImg& draw_circle(const int x0, const int y0, int radius, const tc *const color, const float opacity=1) { if (is_empty()) return *this; + if (radius<0 || x0 - radius>=width() || y0 + radius<0 || y0 - radius>=height()) 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 (!radius) return draw_point(x0,y0,color,opacity); + cimg_init_scanline(opacity); if (y0>=0 && y0=0) { @@ -44280,7 +50294,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw an outlined 2d circle. + //! Draw an outlined 2D circle. /** \param x0 X-coordinate of the circle center. \param y0 Y-coordinate of the circle center. @@ -44293,7 +50307,7 @@ namespace cimg_library_suffixed { CImg& 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 (pattern!=~0U) return draw_ellipse(x0,y0,radius,radius,0,color,opacity,pattern); if (is_empty()) return *this; if (!color) throw CImgArgumentException(_cimg_instance @@ -44301,6 +50315,7 @@ namespace cimg_library_suffixed { 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; @@ -44336,39 +50351,25 @@ namespace cimg_library_suffixed { 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 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=1) for (int x = 0; xwidth()?x0 + sprite.width() - width():0), + ly = sprite.height() - sy0 - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0), + lz = sprite.depth() - sz0 - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0), + lc = sprite.spectrum() - sc0 - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0); + + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (lx>0 && ly>0 && lz>0 && lc>0) { + for (int c = 0; c=1) for (int x = 0; x=1 && !is_shared()) return assign(sprite,false); - const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bc = (c0<0); + 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=1) - for (int y = 0; ywidth()?x0 + sprite.width() - width():0), + ly = sprite.height() - sy0 - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0), + lz = sprite.depth() - sz0 - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0), + lc = sprite.spectrum() - sc0 - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0); + const ulongT slx = lx*sizeof(T); + + const float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); + if (lx>0 && ly>0 && lz>0 && lc>0) { + for (int c = 0; c=1) std::memcpy(ptrd,ptrs,slx); + else for (int x = 0; xwidth()?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; cwidth()?x0 + sprite.width() - width():0), + ly = sprite.height() - sy0 - (y0 + sprite.height()>height()?y0 + sprite.height() - height():0), + lz = sprite.depth() - sz0 - (z0 + sprite.depth()>depth()?z0 + sprite.depth() - depth():0), + lc = sprite.spectrum() - sc0 - (c0 + sprite.spectrum()>spectrum()?c0 + sprite.spectrum() - spectrum():0); + const ulongT msize = mask.size(); + + if (lx>0 && ly>0 && lz>0 && lc>0) { + for (int c = 0; c& 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& font, ...) { - if (!font) return *this; + const float opacity, const CImgList* const font, ...) { + if (!font || !*font) return *this; CImg 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); + 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. @@ -44579,12 +50549,11 @@ namespace cimg_library_suffixed { CImg& draw_text(const int x0, const int y0, const char *const text, const tc *const foreground_color, const int, - const float opacity, const CImgList& font, ...) { - if (!font) return *this; + const float opacity, const CImgList* const font, ...) { + if (!font || !*font) return *this; CImg 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); + 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. @@ -44595,12 +50564,11 @@ namespace cimg_library_suffixed { CImg& draw_text(const int x0, const int y0, const char *const text, const int, const tc *const background_color, - const float opacity, const CImgList& font, ...) { - if (!font) return *this; + const float opacity, const CImgList* const font, ...) { + if (!font || !*font) return *this; CImg 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); + 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. @@ -44613,7 +50581,7 @@ namespace cimg_library_suffixed { \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). + \param font_height Height of the text font (exact match for 13,32,64,128, interpolated otherwise). **/ template CImg& draw_text(const int x0, const int y0, @@ -44622,8 +50590,7 @@ namespace cimg_library_suffixed { const float opacity=1, const unsigned int font_height=13, ...) { if (!font_height) return *this; CImg tmp(2048); - std::va_list ap; va_start(ap,font_height); - cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + std::va_list ap; va_start(ap,font_height); cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); const CImgList& font = CImgList::font(font_height,true); _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font,true); return *this; @@ -44638,8 +50605,7 @@ namespace cimg_library_suffixed { if (!font_height) return *this; cimg::unused(background_color); CImg tmp(2048); - std::va_list ap; va_start(ap,font_height); - cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + 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); } @@ -44651,8 +50617,7 @@ namespace cimg_library_suffixed { const float opacity=1, const unsigned int font_height=13, ...) { if (!font_height) return *this; CImg tmp(2048); - std::va_list ap; va_start(ap,font_height); - cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + 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); } @@ -44662,55 +50627,116 @@ namespace cimg_library_suffixed { const tc1 *const foreground_color, const tc2 *const background_color, const float opacity, const CImgList& font, const bool is_native_font) { - if (!text) return *this; - if (!font) - throw CImgArgumentException(_cimg_instance - "draw_text(): Empty specified font.", - cimg_instance); - + if (!text || !font) return *this; 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; + const int padding_x = font[0]._height<48?1:font[0]._height<128?(int)std::ceil(font[0]._height/51.0f + 0.745f):4; + unsigned char o_ch, ch = 0; + int x, y, w; + CImg left_paddings(text_length,1,1,1,0); + const CImg empty = CImg::empty(); + + if (is_empty() || is_native_font) { + // Pre-compute necessary size of the image as well as left paddings of each character. + x = y = w = 0; + o_ch = 0; for (unsigned int i = 0; iw) w = x; x = 0; break; - case '\t' : x+=4*font[' ']._width; break; - default : if (c10) y+=font[10]._height; else y+=font[0]._height; + if (x>w) w = x; + x = 0; + break; + case '\t' : + if (font._width>32) x+=4*font[32]._width; else x+=4*font[0]._width; + break; + case ' ' : + if (font._width>32) x+=font[32]._width; else x+=font[0]._width; + break; + default : if (ch'9')) || o_ch==';' || o_ch==':' || o_ch=='!') + left_padding = 4*padding_x; + else if (((o_ch=='i' || o_ch=='l' || o_ch=='I' || o_ch=='J' || o_ch=='M' || o_ch=='N') && + ((ch>='0' && ch<='9') || + (ch>='a' && ch<='z' && ch!='v' && ch!='x' && ch!='y') || + (ch>='B' && ch<='Z' && ch!='J' && ch!='T' && ch!='V' && ch!='X' && ch!='Y'))) || + o_ch=='.' || o_ch=='\'' || ch=='\'') + left_padding = padding_x; + else if ((o_ch<'0' || o_ch>'9') && ch!='-') { + const CImg &mask = ch + 256U' ' && o_ch>' ' && mask._height>13) { + const CImg &o_mask = o_ch + 256U13) { + const int w1 = mask.width()>0?o_mask.width() - 1:0, w2 = w1>1?w1 - 1:0, w3 = w2>1?w2 - 1:0; + left_padding = -10; + cimg_forY(mask,k) { + const int + lpad = o_mask(w1,k)>=8?0: + o_mask._width<=2 || o_mask(w2,k)>=8?-1: + o_mask._width<=3 || o_mask(w3,k)>=8?-2:-3, + rpad = mask(0,k)>=8?0: + mask._width<=2 || mask(1,k)>=8?-1: + mask._width<=3 || mask(2,k)>=8?-2:-3; + left_padding = std::max(left_padding,lpad + rpad); + } + } + } + } + left_paddings[i] = left_padding; + } + x+=left_padding + font[ch]._width + padding_x; + o_ch = ch; + } } } - 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); + if (x!=0 || ch=='\n') { if (x>w) w = x; y+=font[0]._height; } + if (is_empty()) assign(x0 + w,y0 + y,1,is_native_font?1:font[0]._spectrum,(T)0); } - int x = x0, y = y0; + // Draw font characters on image. + x = x0; y = y0; for (unsigned int i = 0; i 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; c10) y+=font[10]._height; else y+=font[0]._height; + x = x0; + break; + case '\t' : + case ' ' : { + const unsigned int + lw = (ch=='\t'?4:1)*font[font._width>32?32:0]._width, + lh = font[font._width>32?32:0]._height; + if (background_color) draw_rectangle(x,y,x + lw - 1,y + lh - 1,background_color,opacity); + x+=lw; + } break; + default : if (ch letter = font[ch]; + const CImg &mask = ch + 256U& __draw_text(const char *const text, unsigned int &font_size, const int is_down, ...) { + CImg tmp(2048); + std::va_list ap; + va_start(ap,is_down); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + CImg a_label, a_labelmask; + const unsigned char a_labelcolor = 255; + unsigned int ofs = font_size, fs = ofs; + do { // Determine best font size + a_label.assign().draw_text(0,0,"%s",&a_labelcolor,0,1,fs,tmp._data); + if (a_label._width<7*_width/10 && a_label._height>_height/20 && a_label._height<_height/5) { + font_size = fs; break; + } else if ((a_label._width>7*_width/10 || a_label._height>_height/5) && fs>13 && ofs>=fs) { + ofs = fs; fs = std::max(13U,(unsigned int)cimg::round(fs/1.25f)); + } else if (a_label._width<3*_width/10 && a_label._height<_height/20 && fs<64 && ofs<=fs) { + ofs = fs; fs = std::min(64U,(unsigned int)cimg::round(fs*1.25f)); + } else { font_size = fs; break; } + } while (true); + a_label.normalize(0,255); + a_label+=(255 - a_label.get_dilate(3)).normalize(0,80); + a_label.resize(-100,-100,1,3,1); + return draw_image(0,is_down?height() - a_label.height():0,a_label,0.85f); + } + + //! 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 flow Image of 2D vectors used as input data. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. \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). @@ -44737,9 +50788,9 @@ namespace cimg_library_suffixed { return draw_quiver(flow,CImg(color,_spectrum,1,1,1,true),opacity,sampling,factor,is_arrow,pattern); } - //! Draw a 2d vector field, using a field of colors. + //! Draw a 2D vector field, using a field of colors. /** - \param flow Image of 2d vectors used as input data. + \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. @@ -44782,8 +50833,8 @@ namespace cimg_library_suffixed { 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); + if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y)._data,opacity,45,sampling/5.f,pattern); + else draw_arrow(x,y,xx,yy,color._data,opacity,45,sampling/5.f,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), @@ -44809,34 +50860,34 @@ namespace cimg_library_suffixed { CImg& draw_axis(const CImg& 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) { + const bool allow_zero=true, const float round_x=0) { 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 txt(32); - CImg label; - if (siz<=0) { // Degenerated case. + CImg a_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); + cimg_snprintf(txt,txt._width,"%g",round_x?cimg::round((double)*values_x,round_x):(double)*values_x); + a_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; + _xt = (width() - a_label.width())/2, + xt = _xt<3?3:_xt + a_label.width()>=width() - 2?width() - 3 - a_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. + } else { // Regular case if (values_x[0]=width() - 2?width() - 3 - label.width():_xt; + _xt = xi - a_label.width()/2, + xt = _xt<3?3:_xt + a_label.width()>=width() - 2?width() - 3 - a_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); @@ -44859,36 +50910,36 @@ namespace cimg_library_suffixed { CImg& draw_axis(const int x, const CImg& 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) { + const bool allow_zero=true, const float round_y=0) { if (is_empty()) return *this; int siz = (int)values_y.size() - 1; CImg txt(32); - CImg label; - if (siz<=0) { // Degenerated case. + CImg a_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); + cimg_snprintf(txt,txt._width,"%g",round_y?cimg::round((double)*values_y,round_y):(double)*values_y); + a_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(), + _yt = (height() - a_label.height())/2, + yt = _yt<0?0:_yt + a_label.height()>=height()?height() - 1 - a_label.height():_yt, + _xt = x - 2 - a_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. + } else { // Regular case if (values_y[0]=height()?height() - 1-label.height():_yt, - _xt = x - 2 - label.width(), + _yt = yi - a_label.height()/2, + yt = _yt<0?0:_yt + a_label.height()>=height()?height() - 1 - a_label.height():_yt, + _xt = x - 2 - a_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) @@ -44913,7 +50964,8 @@ namespace cimg_library_suffixed { CImg& draw_axes(const CImg& values_x, const CImg& 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) { + const unsigned int font_height=13, const bool allow_zero=true, + const float round_x=0, const float round_y=0) { if (is_empty()) return *this; const CImg nvalues_x(values_x._data,values_x.size(),1,1,1,true); const int sizx = (int)values_x.size() - 1, wm1 = width() - 1; @@ -44921,7 +50973,10 @@ namespace cimg_library_suffixed { 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; } + if (nx*ox<=0) { + draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero,round_y); + break; + } ox = nx; } } @@ -44931,7 +50986,10 @@ namespace cimg_library_suffixed { 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; } + if (ny*oy<=0) { + draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero,round_x); + break; + } oy = ny; } } @@ -44950,22 +51008,22 @@ namespace cimg_library_suffixed { 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; + px = dx<=0?1:precisionx==0?(float)std::pow(10.,(int)std::log10(dx) - 2.):precisionx, + py = dy<=0?1:precisiony==0?(float)std::pow(10.,(int)std::log10(dy) - 2.):precisiony; if (x0!=x1 && y0!=y1) - draw_axes(CImg::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px), - CImg::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py), - color,opacity,pattern_x,pattern_y,font_height,allow_zero); + draw_axes(CImg::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1), + CImg::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1), + color,opacity,pattern_x,pattern_y,font_height,allow_zero,px,py); else if (x0==x1 && y0!=y1) - draw_axis((int)x0,CImg::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1).round(py), - color,opacity,pattern_y,font_height); + draw_axis((int)x0,CImg::sequence(subdivisiony>0?subdivisiony:1-height()/subdivisiony,y0,y1), + color,opacity,pattern_y,font_height,py); else if (x0!=x1 && y0==y1) - draw_axis(CImg::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1).round(px),(int)y0, - color,opacity,pattern_x,font_height); + draw_axis(CImg::sequence(subdivisionx>0?subdivisionx:1-width()/subdivisionx,x0,x1),(int)y0, + color,opacity,pattern_x,font_height,px); return *this; } - //! Draw 2d grid. + //! Draw 2D grid. /** \param values_x X-coordinates of the vertical lines. \param values_y Y-coordinates of the horizontal lines. @@ -44990,7 +51048,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw 2d grid \simplification. + //! Draw 2D grid \simplification. template CImg& draw_grid(const float delta_x, const float delta_y, const float offsetx, const float offsety, @@ -45016,7 +51074,7 @@ namespace cimg_library_suffixed { return draw_grid(seqx,seqy,color,opacity,pattern_x,pattern_y); } - //! Draw 1d graph. + //! 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. @@ -45080,16 +51138,16 @@ namespace cimg_library_suffixed { // Draw graph edges switch (plot_type%4) { case 1 : { // Segments - int oX = 0, oY = (int)((data[0] - m)/ca); + int oX = 0, oY = (int)cimg::round((data[0] - m)/ca); if (siz==1) { - const int Y = (int)((*data - m)/ca); + const int Y = (int)cimg::round((*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 ndata(data._data,siz,1,1,1,true); - int oY = (int)((data[0] - m)/ca); + int oY = (int)cimg::round((data[0] - m)/ca); cimg_forX(*this,x) { - const int Y = (int)((ndata._cubic_atX((float)x*siz1/width1)-m)/ca); + const int Y = (int)cimg::round((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 int Y0 = (int)cimg::round(-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); + X = (int)cimg::round((off + 1)*fx) - 1, + Y = (int)cimg::round((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). @@ -45132,56 +51190,56 @@ namespace cimg_library_suffixed { case 1 : { // Point cimg_foroff(data,off) { const int - X = (int)(off*fx + wb2), - Y = (int)((data[off]-m)/ca); + X = (int)cimg::round(off*fx + wb2), + Y = (int)cimg::round((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); + X = (int)cimg::round(off*fx + wb2), + Y = (int)cimg::round((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); + X = (int)cimg::round(off*fx + wb2), + Y = (int)cimg::round((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); + X = (int)cimg::round(off*fx + wb2), + Y = (int)cimg::round((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); + X = (int)cimg::round(off*fx + wb2), + Y = (int)cimg::round((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); + X = (int)cimg::round(off*fx + wb2), + Y = (int)cimg::round((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); + X = (int)cimg::round(off*fx + wb2), + Y = (int)cimg::round((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). @@ -45196,13 +51254,13 @@ namespace cimg_library_suffixed { bool _draw_fill(const int x, const int y, const int z, const CImg& ref, const float tolerance2) const { const T *ptr1 = data(x,y,z), *ptr2 = ref._data; - const unsigned long off = _width*_height*_depth; + const ulongT 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. + //! 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. @@ -45226,7 +51284,7 @@ namespace cimg_library_suffixed { #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 nopacity = cimg::abs((float)opacity), copacity = 1 - std::max((float)opacity,0.f); const float tolerance2 = cimg::sqr(tolerance); const CImg ref = get_vector_at(x0,y0,z0); CImg stack(256,1,1,3); @@ -45343,7 +51401,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw filled 3d region with the flood fill algorithm \simplification. + //! Draw filled 3D region with the flood fill algorithm \simplification. template CImg& draw_fill(const int x0, const int y0, const int z0, const tc *const color, const float opacity=1, @@ -45352,7 +51410,7 @@ namespace cimg_library_suffixed { return draw_fill(x0,y0,z0,color,opacity,tmp,tolerance,is_high_connexity); } - //! Draw filled 2d region with the flood fill algorithm \simplification. + //! Draw filled 2D region with the flood fill algorithm \simplification. template CImg& draw_fill(const int x0, const int y0, const tc *const color, const float opacity=1, @@ -45372,6 +51430,7 @@ namespace cimg_library_suffixed { if (is_empty()) return *this; const int w = width(), h = height(); const Tfloat m = (Tfloat)cimg::type::min(), M = (Tfloat)cimg::type::max(); + cimg_uint64 rng = (cimg::_rand(),cimg::rng()); cimg_forZC(*this,z,c) { CImg ref = get_shared_slice(z,c); for (int delta = 1<1; delta>>=1) { @@ -45383,7 +51442,7 @@ namespace cimg_library_suffixed { for (int x0 = 0; x0M?M:val); } @@ -45393,7 +51452,7 @@ namespace cimg_library_suffixed { 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)); + r*cimg::rand(-1,1,&rng)); ref(xc,yc) = (T)(valM?M:val); } for (int y0 = 0; y0M?M:val); } for (int y = -delta2; yM?M:val); } } } + cimg::srand(rng); return *this; } - //! Draw a quadratic Mandelbrot or Julia 2d fractal. + //! 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 palette 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. @@ -45454,14 +51514,15 @@ namespace cimg_library_suffixed { 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 float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f), ln2 = (float)std::log(2.); 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)) + cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) + cimg_openmp_if((1 + _x1 - _x0)*(1 + _y1 - _y0)>=(cimg_openmp_sizefactor)*2048)) for (int q = _y0; q<=_y1; ++q) for (int p = _x0; p<=_x1; ++p) { unsigned int iteration = 0; @@ -45507,7 +51568,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a quadratic Mandelbrot or Julia 2d fractal \overloading. + //! Draw a quadratic Mandelbrot or Julia 2D fractal \overloading. template CImg& draw_mandelbrot(const CImg& colormap, const float opacity=1, const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2, @@ -45519,7 +51580,7 @@ namespace cimg_library_suffixed { z0r,z0i,z1r,z1i,iteration_max,is_normalized_iteration,is_julia_set,param_r,param_i); } - //! Draw a 1d gaussian function. + //! Draw a 1D gaussian function. /** \param xc X-coordinate of the gaussian center. \param sigma Standard variation of the gaussian distribution. @@ -45534,7 +51595,7 @@ namespace cimg_library_suffixed { 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 float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); const ulongT whd = (ulongT)_width*_height*_depth; const tc *col = color; cimg_forX(*this,x) { @@ -45547,7 +51608,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a 2d gaussian function. + //! Draw a 2D gaussian function. /** \param xc X-coordinate of the gaussian center. \param yc Y-coordinate of the gaussian center. @@ -45569,9 +51630,9 @@ namespace cimg_library_suffixed { "draw_gaussian(): Specified color is (null).", cimg_instance); typedef typename CImg::Tfloat tfloat; - const CImg invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0); + const CImg invT = tensor.get_invert(), invT2 = (invT*invT)/=-2.; 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 float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); const ulongT whd = (ulongT)_width*_height*_depth; const tc *col = color; float dy = -yc; @@ -45580,8 +51641,8 @@ namespace cimg_library_suffixed { 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; } + if (opacity>=1) cimg_forC(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } col-=_spectrum; ++dx; } @@ -45590,7 +51651,7 @@ namespace cimg_library_suffixed { return *this; } - //! Draw a 2d gaussian function \overloading. + //! Draw a 2D gaussian function \overloading. template CImg& 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) { @@ -45602,14 +51663,14 @@ namespace cimg_library_suffixed { return draw_gaussian(xc,yc,tensor,color,opacity); } - //! Draw a 2d gaussian function \overloading. + //! Draw a 2D gaussian function \overloading. template CImg& 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::diagonal(sigma,sigma),color,opacity); } - //! Draw a 3d gaussian function \overloading. + //! Draw a 3D gaussian function \overloading. template CImg& draw_gaussian(const float xc, const float yc, const float zc, const CImg& tensor, const tc *const color, const float opacity=1) { @@ -45621,9 +51682,9 @@ namespace cimg_library_suffixed { cimg_instance, tensor._width,tensor._height,tensor._depth,tensor._spectrum,tensor._data); - const CImg invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0); + const CImg invT = tensor.get_invert(), invT2 = (invT*invT)/=-2.; 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 float nopacity = cimg::abs(opacity), copacity = 1 - std::max(opacity,0.f); const ulongT whd = (ulongT)_width*_height*_depth; const tc *col = color; cimg_forXYZ(*this,x,y,z) { @@ -45631,26 +51692,26 @@ namespace cimg_library_suffixed { 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; } + if (opacity>=1) cimg_forC(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whd; } + else cimg_forC(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whd; } col-=_spectrum; } return *this; } - //! Draw a 3d gaussian function \overloading. + //! Draw a 3D gaussian function \overloading. template CImg& 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::diagonal(sigma,sigma,sigma),color,opacity); } - //! Draw a 3d object. + //! 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 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 @@ -45662,6 +51723,7 @@ namespace cimg_library_suffixed { \param lightz Z-coordinate of the light \param specular_lightness Amount of specular light. \param specular_shininess Shininess of the object + \param g_opacity Global opacity of the object. **/ template CImg& draw_object3d(const float x0, const float y0, const float z0, @@ -45670,13 +51732,14 @@ namespace cimg_library_suffixed { 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) { + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const float g_opacity=1) { return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type, is_double_sided,focale,lightx,lighty,lightz, - specular_lightness,specular_shininess,CImg::empty()); + specular_lightness,specular_shininess,g_opacity,CImg::empty()); } - //! Draw a 3d object \simplification. + //! Draw a 3D object \simplification. template CImg& draw_object3d(const float x0, const float y0, const float z0, const CImg& vertices, const CImgList& primitives, @@ -45685,10 +51748,10 @@ namespace cimg_library_suffixed { 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& zbuffer) { + const float g_opacity, CImg& 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); + specular_lightness,specular_shininess,g_opacity,1); } #ifdef cimg_use_board @@ -45700,10 +51763,11 @@ namespace cimg_library_suffixed { 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) { + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const float g_opacity=1) { 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::empty()); + specular_lightness,specular_shininess,g_opacity,CImg::empty()); } template @@ -45715,14 +51779,14 @@ namespace cimg_library_suffixed { 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& zbuffer) { + const float g_opacity, CImg& 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); + specular_lightness,specular_shininess,g_opacity,1); } #endif - //! Draw a 3d object \simplification. + //! Draw a 3D object \simplification. template CImg& draw_object3d(const float x0, const float y0, const float z0, const CImg& vertices, const CImgList& primitives, @@ -45730,13 +51794,14 @@ namespace cimg_library_suffixed { 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) { + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const float g_opacity=1) { return draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_type, is_double_sided,focale,lightx,lighty,lightz, - specular_lightness,specular_shininess,CImg::empty()); + specular_lightness,specular_shininess,g_opacity,CImg::empty()); } - //! Draw a 3d object \simplification. + //! Draw a 3D object \simplification. template CImg& draw_object3d(const float x0, const float y0, const float z0, const CImg& vertices, const CImgList& primitives, @@ -45745,10 +51810,10 @@ namespace cimg_library_suffixed { 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& zbuffer) { + const float g_opacity, CImg& 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); + specular_lightness,specular_shininess,g_opacity,1); } #ifdef cimg_use_board @@ -45760,10 +51825,11 @@ namespace cimg_library_suffixed { 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) { + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const float g_opacity=1) { 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::empty()); + specular_lightness,specular_shininess,g_opacity,CImg::empty()); } template @@ -45775,14 +51841,14 @@ namespace cimg_library_suffixed { 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& zbuffer) { + const float g_opacity, CImg& 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); + specular_lightness,specular_shininess,g_opacity,1); } #endif - //! Draw a 3d object \simplification. + //! Draw a 3D object \simplification. template CImg& draw_object3d(const float x0, const float y0, const float z0, const CImg& vertices, const CImgList& primitives, @@ -45790,13 +51856,14 @@ namespace cimg_library_suffixed { 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) { + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const float g_opacity=1) { return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), render_type,is_double_sided,focale,lightx,lighty,lightz, - specular_lightness,specular_shininess,CImg::empty()); + specular_lightness,specular_shininess,g_opacity,CImg::empty()); } - //! Draw a 3d object \simplification. + //! Draw a 3D object \simplification. template CImg& draw_object3d(const float x0, const float y0, const float z0, const CImg& vertices, const CImgList& primitives, @@ -45805,10 +51872,10 @@ namespace cimg_library_suffixed { 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& zbuffer) { + const float g_opacity, CImg& zbuffer) { return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), render_type,is_double_sided,focale,lightx,lighty,lightz, - specular_lightness,specular_shininess,zbuffer); + specular_lightness,specular_shininess,g_opacity,zbuffer); } #ifdef cimg_use_board @@ -45820,10 +51887,11 @@ namespace cimg_library_suffixed { 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) { + const float specular_lightness=0.2f, const float specular_shininess=0.1f, + const float g_opacity=1) { return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), render_type,is_double_sided,focale,lightx,lighty,lightz, - specular_lightness,specular_shininess,CImg::empty()); + specular_lightness,specular_shininess,g_opacity,CImg::empty()); } template @@ -45835,10 +51903,10 @@ namespace cimg_library_suffixed { 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& zbuffer) { + const float g_opacity, CImg& zbuffer) { return draw_object3d(x0,y0,z0,vertices,primitives,colors,CImg::const_empty(), render_type,is_double_sided,focale,lightx,lighty,lightz, - specular_lightness,specular_shininess,zbuffer); + specular_lightness,specular_shininess,g_opacity,zbuffer); } #endif @@ -45847,23 +51915,23 @@ namespace cimg_library_suffixed { 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; + return 1.f; } template static float __draw_object3d(const CImg& opacities, const unsigned int n_primitive, CImg& opacity) { opacity.assign(); - return n_primitive>=opacities._width?1.0f:(float)opacities[n_primitive]; + return n_primitive>=opacities._width?1.f:(float)opacities[n_primitive]; } template static float ___draw_object3d(const CImgList& opacities, const unsigned int n_primitive) { - return n_primitive static float ___draw_object3d(const CImg& opacities, const unsigned int n_primitive) { - return n_primitive @@ -45877,23 +51945,23 @@ namespace cimg_library_suffixed { 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) { + const float g_opacity, const float sprite_scale) { typedef typename cimg::superset2::type tpfloat; typedef typename to::value_type _to; if (is_empty() || !vertices || !primitives) return *this; CImg 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).", + "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. + 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), + nspec = 1 - (specular_lightness<0.f?0.f:(specular_lightness>1.f?1.f:specular_lightness)), + nspec2 = 1 + (specular_shininess<0.f?0.f:specular_shininess), nsl1 = (nspec2 - 1)/cimg::sqr(nspec - 1), nsl2 = 1 - 2*nsl1*nspec, nsl3 = nspec2 - nsl1 - nsl2; @@ -45904,7 +51972,7 @@ namespace cimg_library_suffixed { if (colors._width>primitives._width) { static CImg default_light_texture; static const tc *lptr = 0; - static tc ref_values[64] = { 0 }; + static tc ref_values[64] = {}; const CImg& img = colors.back(); bool is_same_texture = (lptr==img._data); if (is_same_texture) @@ -45936,10 +52004,10 @@ namespace cimg_library_suffixed { 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); + default_light_texture.draw_gaussian(nlx,nly,default_light_texture._width/3.f,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); + if (factor>nspec) default_light_texture(x,y) = std::min(2.f,nsl1*factor*factor + nsl2*factor + nsl3); } default_light_texture.resize(-100,-100,1,_spectrum); olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shininess = specular_shininess; @@ -45948,12 +52016,12 @@ namespace cimg_library_suffixed { } } - // Compute 3d to 2d projection. + // Compute 3D to 2D projection. CImg projections(vertices._width,2); tpfloat parallzmin = cimg::type::max(); const float absfocale = focale?cimg::abs(focale):0; if (absfocale) { - cimg_pragma_openmp(parallel for cimg_openmp_if(projections.size()>4096)) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(projections.size(),4096)) cimg_forX(projections,l) { // Perspective projection const tpfloat x = (tpfloat)vertices(l,0), @@ -45964,7 +52032,7 @@ namespace cimg_library_suffixed { projections(l,0) = X + absfocale*x/projectedz; } } else { - cimg_pragma_openmp(parallel for cimg_openmp_if(projections.size()>4096)) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(projections.size(),4096)) cimg_forX(projections,l) { // Parallel projection const tpfloat x = (tpfloat)vertices(l,0), @@ -45986,7 +52054,7 @@ namespace cimg_library_suffixed { const tpfloat zmin = absfocale?(tpfloat)(1.5f - absfocale):cimg::type::min(); bool is_forward = zbuffer?true:false; - cimg_pragma_openmp(parallel for cimg_openmp_if(primitives.size()>4096)) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(primitives.size(),4096)) cimglist_for(primitives,l) { const CImg& primitive = primitives[l]; switch (primitive.size()) { @@ -46132,7 +52200,7 @@ namespace cimg_library_suffixed { switch (render_type) { case 3 : { // Flat Shading lightprops.assign(nb_visibles); - cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096)) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) cimg_forX(lightprops,l) { const CImg& primitive = primitives(visibles(permutations(l))); const unsigned int psize = (unsigned int)primitive.size(); @@ -46164,8 +52232,8 @@ namespace cimg_library_suffixed { case 4 : // Gouraud Shading case 5 : { // Phong-Shading CImg 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& primitive = primitives[visibles(l)]; const unsigned int psize = (unsigned int)primitive.size(); const bool @@ -46215,7 +52283,7 @@ namespace cimg_library_suffixed { if (render_type==4) { lightprops.assign(vertices._width); - cimg_pragma_openmp(parallel for cimg_openmp_if(nb_visibles>4096)) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) cimg_forX(lightprops,l) { const tpfloat nx = vertices_normals(l,0), @@ -46234,7 +52302,7 @@ namespace cimg_library_suffixed { 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_pragma_openmp(parallel for cimg_openmp_if_size(nb_visibles,4096)) cimg_forX(lightprops,l) { const tpfloat nx = vertices_normals(l,0), @@ -46263,7 +52331,8 @@ namespace cimg_library_suffixed { __color.get_resize(-100,-100,-100,_spectrum,0):CImg(), &color = _color?_color:(__color?__color:default_color); const tc *const pcolor = color._data; - const float opacity = __draw_object3d(opacities,n_primitive,_opacity); + float opacity = __draw_object3d(opacities,n_primitive,_opacity); + if (_opacity.is_empty()) opacity*=g_opacity; #ifdef cimg_use_board LibBoard::Board &board = *(LibBoard::Board*)pboard; @@ -46272,19 +52341,20 @@ namespace cimg_library_suffixed { 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); + const int x0 = cimg::uiround(projections(n0,0)), y0 = cimg::uiround(projections(n0,1)); - if (_opacity.is_empty()) { // Scalar opacity. + if (_opacity.is_empty()) { // Scalar opacity - if (color.size()==_spectrum) { // Colored point. + 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. + } 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 @@ -46299,6 +52369,7 @@ namespace cimg_library_suffixed { color.get_resize(sw,sh,1,-100,render_type<=3?1:3):CImg(), &sprite = _sprite?_sprite:color; draw_image(nx0,ny0,sprite,opacity); + #ifdef cimg_use_board if (pboard) { board.setPenColorRGBi(128,128,128); @@ -46308,7 +52379,7 @@ namespace cimg_library_suffixed { #endif } } - } else { // Opacity mask. + } 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 @@ -46326,7 +52397,8 @@ namespace cimg_library_suffixed { _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); + draw_image(nx0,ny0,sprite,nopacity,g_opacity); + #ifdef cimg_use_board if (pboard) { board.setPenColorRGBi(128,128,128); @@ -46342,14 +52414,15 @@ namespace cimg_library_suffixed { 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); + x0 = cimg::uiround(projections(n0,0)), y0 = cimg::uiround(projections(n0,1)), + x1 = cimg::uiround(projections(n1,0)), y1 = cimg::uiround(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)); @@ -46358,6 +52431,7 @@ namespace cimg_library_suffixed { #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)); @@ -46371,20 +52445,35 @@ namespace cimg_library_suffixed { 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), + is_wireframe = (unsigned int)primitive[2], + is_radius = (unsigned int)primitive[3]; + float Xc,Yc,Zc,radius; + if (is_radius) { + Xc = (float)vertices(n0,0); + Yc = (float)vertices(n0,1); + Zc = (float)vertices(n0,2); + radius = cimg::hypot(vertices(n1,0) - vertices(n0,0), + vertices(n1,1) - vertices(n0,1), + vertices(n1,2) - vertices(n0,2)); + } else { + 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)); 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); + vertices(n1,2) - vertices(n0,2)); + } + const float + zc = Z + Zc + _focale, + af = absfocale?absfocale/zc:1, + xc = X + Xc*af, + yc = Y + Yc*af; + radius*=af; + 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)); @@ -46394,6 +52483,7 @@ namespace cimg_library_suffixed { 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)); @@ -46405,6 +52495,7 @@ namespace cimg_library_suffixed { 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)); @@ -46431,14 +52522,15 @@ namespace cimg_library_suffixed { 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); + x0 = cimg::uiround(projections(n0,0)), y0 = cimg::uiround(projections(n0,1)), + x1 = cimg::uiround(projections(n1,0)), y1 = cimg::uiround(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); + else draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opacity); + #ifdef cimg_use_board if (pboard) { board.setPenColorRGBi(128,128,128,(unsigned char)(opacity*255)); @@ -46450,6 +52542,7 @@ namespace cimg_library_suffixed { 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)); @@ -46465,9 +52558,9 @@ namespace cimg_library_suffixed { 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); + x0 = cimg::uiround(projections(n0,0)), y0 = cimg::uiround(projections(n0,1)), + x1 = cimg::uiround(projections(n1,0)), y1 = cimg::uiround(projections(n1,1)), + x2 = cimg::uiround(projections(n2,0)), y2 = cimg::uiround(projections(n2,1)); const float z0 = vertices(n0,2) + Z + _focale, z1 = vertices(n1,2) + Z + _focale, @@ -46475,6 +52568,7 @@ namespace cimg_library_suffixed { 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)); @@ -46491,6 +52585,7 @@ namespace cimg_library_suffixed { 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)); @@ -46503,6 +52598,7 @@ namespace cimg_library_suffixed { 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)); @@ -46515,9 +52611,10 @@ namespace cimg_library_suffixed { 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); + const float lp = std::min(lightprops(l),1.f); board.setPenColorRGBi((unsigned char)(color[0]*lp), (unsigned char)(color[1]*lp), (unsigned char)(color[2]*lp), @@ -46533,6 +52630,7 @@ namespace cimg_library_suffixed { 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]), @@ -46547,12 +52645,13 @@ namespace cimg_library_suffixed { 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); + lx0 = (unsigned int)cimg::uiround(lightprops(n0,0)), ly0 = (unsigned int)cimg::uiround(lightprops(n0,1)), + lx1 = (unsigned int)cimg::uiround(lightprops(n1,0)), ly1 = (unsigned int)cimg::uiround(lightprops(n1,1)), + lx2 = (unsigned int)cimg::uiround(lightprops(n2,0)), ly2 = (unsigned int)cimg::uiround(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 @@ -46581,10 +52680,10 @@ namespace cimg_library_suffixed { 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), + x0 = cimg::uiround(projections(n0,0)), y0 = cimg::uiround(projections(n0,1)), + x1 = cimg::uiround(projections(n1,0)), y1 = cimg::uiround(projections(n1,1)), + x2 = cimg::uiround(projections(n2,0)), y2 = cimg::uiround(projections(n2,1)), + x3 = cimg::uiround(projections(n3,0)), y3 = cimg::uiround(projections(n3,1)), xc = (x0 + x1 + x2 + x3)/4, yc = (y0 + y1 + y2 + y3)/4; const float z0 = vertices(n0,2) + Z + _focale, @@ -46597,6 +52696,7 @@ namespace cimg_library_suffixed { 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)); @@ -46614,6 +52714,7 @@ namespace cimg_library_suffixed { 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)); @@ -46630,6 +52731,7 @@ namespace cimg_library_suffixed { 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)); @@ -46649,9 +52751,10 @@ namespace cimg_library_suffixed { 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); + const float lp = std::min(lightprops(l),1.f); board.setPenColorRGBi((unsigned char)(color[0]*lp), (unsigned char)(color[1]*lp), (unsigned char)(color[2]*lp),(unsigned char)(opacity*255)); @@ -46697,10 +52800,10 @@ namespace cimg_library_suffixed { } 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), + lx0 = (unsigned int)cimg::uiround(lightprops(n0,0)), ly0 = (unsigned int)cimg::uiround(lightprops(n0,1)), + lx1 = (unsigned int)cimg::uiround(lightprops(n1,0)), ly1 = (unsigned int)cimg::uiround(lightprops(n1,1)), + lx2 = (unsigned int)cimg::uiround(lightprops(n2,0)), ly2 = (unsigned int)cimg::uiround(lightprops(n2,1)), + lx3 = (unsigned int)cimg::uiround(lightprops(n3,0)), ly3 = (unsigned int)cimg::uiround(lightprops(n3,1)), lxc = (lx0 + lx1 + lx2 + lx3)/4, lyc = (ly0 + ly1 + ly2 + ly3)/4; if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,xc,yc,zc,pcolor,light_texture,lx0,ly0,lx1,ly1,lxc,lyc,opacity). @@ -46750,9 +52853,9 @@ namespace cimg_library_suffixed { 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); + x0 = cimg::uiround(projections(n0,0)), y0 = cimg::uiround(projections(n0,1)), + x1 = cimg::uiround(projections(n1,0)), y1 = cimg::uiround(projections(n1,1)), + x2 = cimg::uiround(projections(n2,0)), y2 = cimg::uiround(projections(n2,1)); const float z0 = vertices(n0,2) + Z + _focale, z1 = vertices(n1,2) + Z + _focale, @@ -46783,6 +52886,7 @@ namespace cimg_library_suffixed { 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)); @@ -46795,6 +52899,7 @@ namespace cimg_library_suffixed { 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)); @@ -46808,9 +52913,10 @@ namespace cimg_library_suffixed { 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); + const float lp = std::min(lightprops(l),1.f); board.setPenColorRGBi((unsigned char)(128*lp), (unsigned char)(128*lp), (unsigned char)(128*lp), @@ -46828,6 +52934,7 @@ namespace cimg_library_suffixed { 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)); @@ -46850,6 +52957,7 @@ namespace cimg_library_suffixed { (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 @@ -46885,18 +52993,15 @@ namespace cimg_library_suffixed { tx1 = (int)primitive[6], ty1 = (int)primitive[7], tx2 = (int)primitive[8], ty2 = (int)primitive[9], tx3 = (int)primitive[10], ty3 = (int)primitive[11], - txc = (tx0 + tx1 + tx2 + tx3)/4, tyc = (ty0 + ty1 + ty2 + ty3)/4, - 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), - xc = (x0 + x1 + x2 + x3)/4, yc = (y0 + y1 + y2 + y3)/4; + x0 = cimg::uiround(projections(n0,0)), y0 = cimg::uiround(projections(n0,1)), + x1 = cimg::uiround(projections(n1,0)), y1 = cimg::uiround(projections(n1,1)), + x2 = cimg::uiround(projections(n2,0)), y2 = cimg::uiround(projections(n2,1)), + x3 = cimg::uiround(projections(n3,0)), y3 = cimg::uiround(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, - zc = (z0 + z1 + z2 + z3)/4; + z3 = vertices(n3,2) + Z + _focale; switch (render_type) { case 0 : @@ -46908,6 +53013,7 @@ namespace cimg_library_suffixed { 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)); @@ -46929,6 +53035,7 @@ namespace cimg_library_suffixed { 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)); @@ -46946,6 +53053,7 @@ namespace cimg_library_suffixed { 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)); @@ -46965,9 +53073,10 @@ namespace cimg_library_suffixed { 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); + const float lp = std::min(lightprops(l),1.f); board.setPenColorRGBi((unsigned char)(128*lp), (unsigned char)(128*lp), (unsigned char)(128*lp), @@ -46984,26 +53093,18 @@ namespace cimg_library_suffixed { case 4 : { const float lightprop0 = lightprops(n0), lightprop1 = lightprops(n1), - lightprop2 = lightprops(n2), lightprop3 = lightprops(n3), - lightpropc = (lightprop0 + lightprop1 + lightprop2 + lightprop3)/4; + lightprop2 = lightprops(n2), lightprop3 = lightprops(n3); if (zbuffer) - draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,xc,yc,zc,color,tx0,ty0,tx1,ty1,txc,tyc, - lightprop0,lightprop1,lightpropc,opacity). - draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,color,tx1,ty1,tx2,ty2,txc,tyc, - lightprop1,lightprop2,lightpropc,opacity). - draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,color,tx2,ty2,tx3,ty3,txc,tyc, - lightprop2,lightprop3,lightpropc,opacity). - draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,color,tx3,ty3,tx0,ty0,txc,tyc, - lightprop3,lightprop0,lightpropc,opacity); + 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,xc,yc,zc,color,tx0,ty0,tx1,ty1,txc,tyc, - lightprop0,lightprop1,lightpropc,opacity). - draw_triangle(x1,y1,z1,x2,y2,z2,xc,yc,zc,color,tx1,ty1,tx2,ty2,txc,tyc, - lightprop1,lightprop2,lightpropc,opacity). - draw_triangle(x2,y2,z2,x3,y3,z3,xc,yc,zc,color,tx2,ty2,tx3,ty3,txc,tyc, - lightprop2,lightprop3,lightpropc,opacity). - draw_triangle(x3,y3,z3,x0,y0,z0,xc,yc,zc,color,tx3,ty3,tx0,ty0,txc,tyc, - lightprop3,lightprop0,lightpropc,opacity); + 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)); @@ -47018,29 +53119,20 @@ namespace cimg_library_suffixed { } 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), - lxc = (lx0 + lx1 + lx2 + lx3)/4, lyc = (ly0 + ly1 + ly2 + ly3)/4; + lx0 = (unsigned int)cimg::uiround(lightprops(n0,0)), ly0 = (unsigned int)cimg::uiround(lightprops(n0,1)), + lx1 = (unsigned int)cimg::uiround(lightprops(n1,0)), ly1 = (unsigned int)cimg::uiround(lightprops(n1,1)), + lx2 = (unsigned int)cimg::uiround(lightprops(n2,0)), ly2 = (unsigned int)cimg::uiround(lightprops(n2,1)), + lx3 = (unsigned int)cimg::uiround(lightprops(n3,0)), ly3 = (unsigned int)cimg::uiround(lightprops(n3,1)); if (zbuffer) - draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,xc,yc,zc,color,tx0,ty0,tx1,ty1,txc,tyc, - light_texture,lx0,ly0,lx1,ly1,lxc,lyc,opacity). - draw_triangle(zbuffer,x1,y1,z1,x2,y2,z2,xc,yc,zc,color,tx1,ty1,tx2,ty2,txc,tyc, - light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). - draw_triangle(zbuffer,x2,y2,z2,x3,y3,z3,xc,yc,zc,color,tx2,ty2,tx3,ty3,txc,tyc, - light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). - draw_triangle(zbuffer,x3,y3,z3,x0,y0,z0,xc,yc,zc,color,tx3,ty3,tx0,ty0,txc,tyc, - light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + 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,xc,yc,zc,color,tx0,ty0,tx1,ty1,txc,tyc, - light_texture,lx0,ly0,lx1,ly1,lxc,lyc,opacity). - draw_triangle(x1,y1,z1,x2,y2,z2,xc,yc,zc,color,tx1,ty1,tx2,ty2,txc,tyc, - light_texture,lx1,ly1,lx2,ly2,lxc,lyc,opacity). - draw_triangle(x2,y2,z2,x3,y3,z3,xc,yc,zc,color,tx2,ty2,tx3,ty3,txc,tyc, - light_texture,lx2,ly2,lx3,ly3,lxc,lyc,opacity). - draw_triangle(x3,y3,z3,x0,y0,z0,xc,yc,zc,color,tx3,ty3,tx0,ty0,txc,tyc, - light_texture,lx3,ly3,lx0,ly0,lxc,lyc,opacity); + 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 @@ -47081,7 +53173,7 @@ namespace cimg_library_suffixed { \param exit_on_anykey Exit function when any key is pressed. **/ CImg& select(CImgDisplay &disp, - const unsigned int feature_type=2, unsigned int *const XYZ=0, + const unsigned int feature_type=2, unsigned int *const XYZ=0, const bool exit_on_anykey=false, const bool is_deep_selection_default=false) { return get_select(disp,feature_type,XYZ,exit_on_anykey,is_deep_selection_default).move_to(*this); @@ -47089,7 +53181,7 @@ namespace cimg_library_suffixed { //! Simple interface to select a shape from an image \overloading. CImg& select(const char *const title, - const unsigned int feature_type=2, unsigned int *const XYZ=0, + const unsigned int feature_type=2, unsigned int *const XYZ=0, const bool exit_on_anykey=false, const bool is_deep_selection_default=false) { return get_select(title,feature_type,XYZ,exit_on_anykey,is_deep_selection_default).move_to(*this); @@ -47097,7 +53189,7 @@ namespace cimg_library_suffixed { //! Simple interface to select a shape from an image \newinstance. CImg get_select(CImgDisplay &disp, - const unsigned int feature_type=2, unsigned int *const XYZ=0, + const unsigned int feature_type=2, unsigned int *const XYZ=0, const bool exit_on_anykey=false, const bool is_deep_selection_default=false) const { return _select(disp,0,feature_type,XYZ,0,0,0,exit_on_anykey,true,false,is_deep_selection_default); @@ -47105,7 +53197,7 @@ namespace cimg_library_suffixed { //! Simple interface to select a shape from an image \newinstance. CImg get_select(const char *const title, - const unsigned int feature_type=2, unsigned int *const XYZ=0, + const unsigned int feature_type=2, unsigned int *const XYZ=0, const bool exit_on_anykey=false, const bool is_deep_selection_default=false) const { CImgDisplay disp; @@ -47123,7 +53215,10 @@ namespace cimg_library_suffixed { 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); + } else { + if (title) disp.set_title("%s",title); + disp.move_inside_screen(); + } CImg thumb; if (width()>disp.screen_width() || height()>disp.screen_height()) @@ -47135,17 +53230,16 @@ namespace cimg_library_suffixed { 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, area_started = 0, area_clicked = 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), + X0 = (int)((XYZ?XYZ[0]:_width/2)%_width), + Y0 = (int)((XYZ?XYZ[1]:_height/2)%_height), + Z0 = (int)((XYZ?XYZ[2]:_depth/2)%_depth), X1 =-1, Y1 = -1, Z1 = -1, X3d = -1, Y3d = -1, oX3d = X3d, oY3d = -1, omx = -1, omy = -1; float X = -1, Y = -1, Z = -1; - unsigned int key = 0; + unsigned int key = 0, font_size = 32; bool is_deep_selection = is_deep_selection_default, shape_selected = false, text_down = false, visible_cursor = true; @@ -47166,8 +53260,8 @@ namespace cimg_library_suffixed { 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(); + mX = mx<0?-1.f:(float)mx*(width() + (depth()>1?depth():0))/disp.width(), + mY = my<0?-1.f:(float)my*(height() + (depth()>1?depth():0))/disp.height(); area = 0; if (mX>=0 && mY>=0 && mX1 && (X1!=(int)X || Y1!=(int)Y || Z1!=(int)Z)) visu0.assign(); X1 = (int)X; Y1 = (int)Y; Z1 = (int)Z; } - if (!b1 && phase==2 && area_clicked!=area) { // When selection is at 2nd step (for volumes). + if (!b1 && phase==2 && area_clicked!=area) { // When selection is at 2nd step (for volumes) switch (area_started) { 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 (b2 && area_clicked==area) { // When moving through the image/volume. + if (b2 && area_clicked==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; @@ -47281,7 +53380,7 @@ namespace cimg_library_suffixed { X = (float)X0; Y = (float)Y0; Z = (float)Z0; phase = area = area_clicked = area_started = 0; visu0.assign(); } - if (disp.wheel()) { // When moving through the slices of the volume (with mouse wheel). + 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()) { switch (area) { @@ -47319,14 +53418,14 @@ namespace cimg_library_suffixed { } } break; - case 4 : // When mouse is over the 3d view. + 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. + 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, @@ -47338,10 +53437,10 @@ namespace cimg_library_suffixed { 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)), + nw0 = (float)std::sqrt(std::max(0.f,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)), + nw1 = (float)std::sqrt(std::max(0.f,R2 - nu1*nu1 - nv1*nv1)), u = nv0*nw1 - nw0*nv1, v = nw0*nu1 - nu0*nw1, w = nv0*nu1 - nu0*nv1, @@ -47349,13 +53448,13 @@ namespace cimg_library_suffixed { alpha = (float)std::asin(n/R2)*180/cimg::PI; pose3d.draw_image(CImg::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. + } else if (disp.button()&2 && pose3d && oY3d!=Y3d) { // Right button: zoom pose3d(3,2)+=(Y3d - oY3d)*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. + 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; @@ -47388,15 +53487,15 @@ namespace cimg_library_suffixed { // 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); + 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. + 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), @@ -47464,12 +53563,12 @@ namespace cimg_library_suffixed { 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); + 2,true,500,0,0,0,0,0,1,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); + 2,true,500,0,0,0,0,0,1,zbuffer3d); visu0.draw_image(x3d,y3d,view3d); } visu = visu0; @@ -47486,16 +53585,16 @@ namespace cimg_library_suffixed { 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), + _xn = (int)((_vX + 1.f)*w/W - 1), xn = _xn + ((int)((_xn + 1.f)*W/w)!=_vX + 1), + _yn = (int)((_vY + 1.f)*h/H - 1), yn = _yn + ((int)((_yn + 1.f)*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()), + _zxn = (int)((_vZ + width() + 1.f)*w/W - 1), + zxn = _zxn + ((int)((_zxn + 1.f)*W/w)!=_vZ + width() + 1), + _zyn = (int)((_vZ + height() + 1.f)*h/H - 1), + zyn = _zyn + ((int)((_zyn + 1.f)*H/h)!=_vZ + height() + 1), + _xM = (int)(width()*(float)w/W - 1), xM = _xM + ((int)((_xM + 1.f)*W/w)!=width()), + _yM = (int)(height()*(float)h/H - 1), yM = _yM + ((int)((_yM + 1.f)*H/h)!=height()), xc = (xp + xn)/2, yc = (yp + yn)/2, zxc = (zxp + zxn)/2, @@ -47505,7 +53604,7 @@ namespace cimg_library_suffixed { zxf = (int)((Z + width())*w/W), zyf = (int)((Z + height())*h/H); - if (is_axes) { // Draw axes. + 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). @@ -47538,55 +53637,58 @@ namespace cimg_library_suffixed { 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), + _xn0 = (int)((X0 + 1.f)*w/W - 1), xn0 = _xn0 + ((int)((_xn0 + 1.f)*W/w)!=X0 + 1), + _yn0 = (int)((Y0 + 1.f)*h/H - 1), yn0 = _yn0 + ((int)((_yn0 + 1.f)*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), + _zxn0 = (int)((Z0 + width() + 1.f)*w/W - 1), + zxn0 = _zxn0 + ((int)((_zxn0 + 1.f)*W/w)!=Z0 + width() + 1), + _zyn0 = (int)((Z0 + height() + 1.f)*h/H - 1), + zyn0 = _zyn0 + ((int)((_zyn0 + 1.f)*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); + case 1 : { // Vector + visu.draw_arrow(xc0,yc0,xc,yc,background_color,0.9f,30,5,0x33333333). + draw_arrow(xc0,yc0,xc,yc,foreground_color,0.9f,30,5,0xCCCCCCCC); 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); + visu.draw_arrow(zxc0,yc0,zxc,yc,background_color,0.9f,30,5,0x33333333). + draw_arrow(zxc0,yc0,zxc,yc,foreground_color,0.9f,30,5,0xCCCCCCCC). + draw_arrow(xc0,zyc0,xc,zyc,background_color,0.9f,30,5,0x33333333). + draw_arrow(xc0,zyc0,xc,zyc,foreground_color,0.9f,30,5,0xCCCCCCCC); } } break; - case 2 : { + case 2 : { // Box visu.draw_rectangle(X0 values = get_vector_at((int)X,(int)Y,(int)Z); - const bool is_large_spectrum = values._height>16; + const bool is_large_spectrum = values._height>8; if (is_large_spectrum) - values.draw_image(0,8,values.get_rows(values._height - 8,values._height - 1)).resize(1,16,1,1,0); + values.draw_image(0,4,values.get_rows(values._height - 4,values._height - 1)).resize(1,8,1,1,0); char *ctext = text._data + std::strlen(text), *const ltext = text._data + 512; for (unsigned int c = 0; c::format_s(), cimg::type::format(values[c])); ctext += std::strlen(ctext); - if (c==7 && is_large_spectrum) { - cimg_snprintf(ctext,24," (...)"); + if (c==3 && is_large_spectrum) { + cimg_snprintf(ctext,24," ..."); ctext += std::strlen(ctext); } *(ctext++) = ' '; *ctext = 0; @@ -47641,37 +53743,47 @@ namespace cimg_library_suffixed { if (_depth>1 || force_display_z_coord) cimg_snprintf(text,text._width," Vect (%d,%d,%d)-(%d,%d,%d), Length = %g ", origX + X0,origY + Y0,origZ + Z0,origX + X1,origY + Y1,origZ + Z1,length); - else cimg_snprintf(text,text._width," Vect (%d,%d)-(%d,%d), Length = %g, Angle = %g\260 ", - origX + X0,origY + Y0,origX + X1,origY + Y1,length, - cimg::round(cimg::mod(180*std::atan2(-dY,-dX)/cimg::PI,360.),0.1)); + else if (_width!=1 && _height!=1) + cimg_snprintf(text,text._width," Vect (%d,%d)-(%d,%d), Length = %g, Angle = %g\260 ", + origX + X0,origY + Y0,origX + X1,origY + Y1,length, + cimg::round(cimg::mod(180*std::atan2(-dY,-dX)/cimg::PI,360.),0.1)); + else + cimg_snprintf(text,text._width," Vect (%d,%d)-(%d,%d), Length = %g ", + origX + X0,origY + Y0,origX + X1,origY + Y1,length); } break; case 2 : { const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), length = cimg::round(cimg::hypot(dX,dY,dZ),0.1); if (_depth>1 || force_display_z_coord) cimg_snprintf(text,text._width, - " Box (%d,%d,%d)-(%d,%d,%d), Size = (%d,%d,%d), Length = %g ", + " Box ( %d,%d,%d ) - ( %d,%d,%d )\n Size = ( %d,%d,%d ), Length = %g ", origX + (X01 || force_display_z_coord) - cimg_snprintf(text,text._width," Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d) ", + 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) ", + 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); + if (phase || (mx>=0 && my>=0)) visu.__draw_text("%s",font_size,(int)text_down,text._data); } disp.display(visu); @@ -47699,15 +53811,15 @@ namespace cimg_library_suffixed { 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; + 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); res[0] = X0; res[1] = Y0; res[2] = Z0; break; - default : res[0] = X0; res[1] = Y0; res[2] = Z0; - } + 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(); @@ -47717,9 +53829,9 @@ namespace cimg_library_suffixed { return res; } - // Return a visualizable uchar8 image for display routines. - CImg __get_select(const CImgDisplay& disp, const int normalization, - const int x, const int y, const int z) const { + // Return a visualizable 'uchar8' image for display routines. + CImg _get_select(const CImgDisplay& disp, const int normalization, + const int x, const int y, const int z) const { if (is_empty()) return CImg(1,1,1,1,0); const CImg crop = get_shared_channels(0,std::min(2,spectrum() - 1)); CImg img2d; @@ -47741,21 +53853,22 @@ namespace cimg_library_suffixed { Tint m0 = (Tint)cimg::type::max(), M0 = (Tint)cimg::type::min(); if (!normalization) { m0 = 0; M0 = 255; } else if (normalization==2) { m0 = (Tint)disp._min; M0 = (Tint)disp._max; } - else + else { cimg_for(img2d,ptr,Tuchar) if (!cimg::type::is_inf(*ptr) && !cimg::type::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); + val_minf = (T)(normalization==1 || normalization==3?m0 - cimg::abs(m0):m0), + val_pinf = (T)(normalization==1 || normalization==3?M0 + cimg::abs(M0):M0); if (is_nan) cimg_for(img2d,ptr,Tuchar) - if (cimg::type::is_nan(*ptr)) *ptr = val_minf; // Replace NaN values. + if (cimg::type::is_nan(*ptr)) *ptr = val_minf; // Replace NaN values if (is_inf) cimg_for(img2d,ptr,Tuchar) - if (cimg::type::is_inf(*ptr)) *ptr = (float)*ptr<0?val_minf:val_pinf; // Replace +-inf values. + if (cimg::type::is_inf(*ptr)) *ptr = (float)*ptr<0?val_minf:val_pinf; // Replace +-inf values } } @@ -47763,13 +53876,15 @@ namespace cimg_library_suffixed { case 1 : img2d.normalize((ucharT)0,(ucharT)255); break; case 2 : { const float m = disp._min, M = disp._max; - (img2d-=m)*=255.0f/(M - m>0?M - m:1); + (img2d-=m)*=255.f/(M - m>0?M - m:1); } break; case 3 : if (cimg::type::is_float()) img2d.normalize((ucharT)0,(ucharT)255); else { - const float m = (float)cimg::type::min(), M = (float)cimg::type::max(); - (img2d-=m)*=255.0f/(M - m>0?M - m:1); + const float + m = (float)cimg::type::min(), + M = (float)cimg::type::max(); + (img2d-=m)*=255.f/(M - m>0?M - m:1); } break; } if (img2d.spectrum()==2) img2d.channels(0,2); @@ -47795,27 +53910,34 @@ namespace cimg_library_suffixed { 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; } + if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.; } 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 colormap; - if (odimv!=_spectrum) { - odimv = _spectrum; - colormap = CImg(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 colormap(3,_spectrum); + 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; } + if (_spectrum>3) { colormap(0,3) = 220; colormap(1,3) = 220; colormap(2,3) = 10; } + if (_spectrum>4) { colormap(0,4) = 220; colormap(1,4) = 10; colormap(2,4) = 220; } + if (_spectrum>5) { colormap(0,5) = 10; colormap(1,5) = 220; colormap(2,5) = 220; } + if (_spectrum>6) { + cimg_uint64 rng = 10; + cimg_for_inY(colormap,6,colormap.height()-1,k) { + colormap(0,k) = (unsigned char)(120 + cimg::rand(-100.f,100.f,&rng)); + colormap(1,k) = (unsigned char)(120 + cimg::rand(-100.f,100.f,&rng)); + colormap(2,k) = (unsigned char)(120 + cimg::rand(-100.f,100.f,&rng)); + } } } CImg 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; + unsigned int okey = 0, obutton = 0, font_size = 32; CImg message(1024); CImg_3x3(I,unsigned char); @@ -47840,19 +53962,19 @@ namespace cimg_library_suffixed { 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); + px = (float)std::pow(10.,(int)std::log10(dx?dx:1) - 2.), + py = (float)std::pow(10.,(int)std::log10(dy?dy:1) - 2.); const CImg seqx = dx<=0?CImg::vector(nxmin): - CImg::sequence(1 + gdimx/60,nxmin,one?nxmax:nxmin + (nxmax - nxmin)*(siz + 1)/siz).round(px), - seqy = CImg::sequence(1 + gdimy/60,nymax,nymin).round(py); + CImg::sequence(1 + gdimx/60,nxmin,one?nxmax:nxmin + (nxmax - nxmin)*(siz + 1)/siz), + seqy = CImg::sequence(1 + gdimy/60,nymax,nymin); 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); + axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero,px,py); + if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero,px); + if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero,px); + if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero,py); + if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero,py); cimg_for3x3(axes,x,y,0,0,I,unsigned char) if (Icc) { @@ -47863,8 +53985,8 @@ namespace cimg_library_suffixed { 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); + 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); @@ -47895,7 +54017,7 @@ namespace cimg_library_suffixed { if (mouse_x>=16 && mouse_y>=16 && mouse_x=7) cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx, @@ -47904,27 +54026,31 @@ namespace cimg_library_suffixed { (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),")"); + unsigned int len = (unsigned int)std::strlen(message); + cimg_forC(*this,c) + cimg_snprintf(message._data + len,message._width - len,"%g ",(double)(*this)(x,0,0,c)); + len = (unsigned int)std::strlen(message); + cimg_snprintf(message._data + len,message._width - len,")"); } - if (x0>=0 && x1>=0) { - const unsigned int + 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 + ny1 = (unsigned int)(y0<=y1?y1:y0), + len = (unsigned int)std::strlen(message); + 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 )", + if (y0>=0 && y1>=0) + cimg_snprintf(message._data + len,message._width - len," - 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 ]", + else + cimg_snprintf(message._data + len,message._width - len," - 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); } @@ -47966,12 +54092,18 @@ namespace cimg_library_suffixed { CImg &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); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u." +#ifdef cimg_use_png + "png", +#else + "bmp", +#endif + snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); - (+screen).draw_text(0,0," Saving snapshot... ",black,gray,1,13).display(disp); + (+screen).__draw_text(" Saving snapshot... ",font_size,0).display(disp); screen.save(filename); - (+screen).draw_text(0,0," Snapshot '%s' saved. ",black,gray,1,13,filename._data).display(disp); + (+screen).__draw_text(" Snapshot '%s' saved. ",font_size,0,filename._data).display(disp); } disp.set_key(key,false); okey = 0; } break; @@ -47981,22 +54113,23 @@ namespace cimg_library_suffixed { CImg &screen = visu?visu:visu0; std::FILE *file; do { + #ifdef cimg_use_zlib - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimgz",snap_number++); #else - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimg",snap_number++); #endif - if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); - (+screen).draw_text(0,0," Saving instance... ",black,gray,1,13).display(disp); + (+screen).__draw_text(" Saving instance... ",font_size,0).display(disp); save(filename); - (+screen).draw_text(0,0," Instance '%s' saved. ",black,gray,1,13,filename._data).display(disp); + (+screen).__draw_text(" Instance '%s' saved. ",font_size,0,filename._data).display(disp); } disp.set_key(key,false); okey = 0; } break; } - // Handle mouse motion and mouse buttons + // 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) { @@ -48005,10 +54138,10 @@ namespace cimg_library_suffixed { 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 (button&1) { if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; } } - else if (button&2) { + 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; } @@ -48082,12 +54215,14 @@ namespace cimg_library_suffixed { #ifdef cimg_load_plugin8 cimg_load_plugin8(filename); #endif - // Ascii formats + // Text formats if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename); - else if (!cimg::strcasecmp(ext,"dlm") || + else if (!cimg::strcasecmp(ext,"csv") || + !cimg::strcasecmp(ext,"dlm") || !cimg::strcasecmp(ext,"txt")) load_dlm(filename); + else if (!cimg::strcasecmp(ext,"pdf")) load_pdf_external(filename); - // 2d binary formats + // 2D binary formats else if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename); else if (!cimg::strcasecmp(ext,"jpg") || !cimg::strcasecmp(ext,"jpeg") || @@ -48104,9 +54239,11 @@ namespace cimg_library_suffixed { 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") || + else if (!cimg::strcasecmp(ext,"arw") || + !cimg::strcasecmp(ext,"cr2") || !cimg::strcasecmp(ext,"crw") || !cimg::strcasecmp(ext,"dcr") || + !cimg::strcasecmp(ext,"dng") || !cimg::strcasecmp(ext,"mrw") || !cimg::strcasecmp(ext,"nef") || !cimg::strcasecmp(ext,"orf") || @@ -48115,8 +54252,10 @@ namespace cimg_library_suffixed { !cimg::strcasecmp(ext,"raf") || !cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename); else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename); + else if (!cimg::strcasecmp(ext,"heic") || + !cimg::strcasecmp(ext,"avif")) load_heif(filename); - // 3d binary formats + // 3D binary formats else if (!cimg::strcasecmp(ext,"dcm") || !cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename); else if (!cimg::strcasecmp(ext,"hdr") || @@ -48154,6 +54293,7 @@ namespace cimg_library_suffixed { !cimg::strcasecmp(ext,"qt") || !cimg::strcasecmp(ext,"rm") || !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"webm") || !cimg::strcasecmp(ext,"wmv") || !cimg::strcasecmp(ext,"xvid") || !cimg::strcasecmp(ext,"mpeg")) load_video(filename); @@ -48162,7 +54302,7 @@ namespace cimg_library_suffixed { // If nothing loaded, try to guess file format from magic number in file. if (!is_loaded) { - std::FILE *file = std_fopen(filename,"rb"); + std::FILE *file = cimg::std_fopen(filename,"rb"); if (!file) { cimg::exception_mode(omode); throw CImgIOException(_cimg_instance @@ -48172,7 +54312,7 @@ namespace cimg_library_suffixed { } const char *const f_type = cimg::ftype(file,filename); - std::fclose(file); + cimg::fclose(file); is_loaded = true; try { if (!cimg::strcasecmp(f_type,"pnm")) load_pnm(filename); @@ -48359,6 +54499,7 @@ namespace cimg_library_suffixed { "load_bmp(): Specified filename is (null).", cimg_instance); + const ulongT fsiz = (ulongT)(file?cimg::fsize(file):cimg::fsize(filename)); std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); CImg header(54); cimg::fread(header._data,54,nfile); @@ -48381,35 +54522,60 @@ namespace cimg_library_suffixed { 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); + if ((ulongT)file_size!=fsiz) + throw CImgIOException(_cimg_instance + "load_bmp(): Invalid file_size %d specified in filename '%s' (expected %lu).", + cimg_instance, + file_size,filename?filename:"(FILE*)",fsiz); + if (header_size<0 || header_size>=file_size) + throw CImgIOException(_cimg_instance + "load_bmp(): Invalid header size %d specified in filename '%s'.", + cimg_instance, + header_size,filename?filename:"(FILE*)"); + + if (offset<0 || offset>=file_size) + throw CImgIOException(_cimg_instance + "load_bmp(): Invalid offset %d specified in filename '%s'.", + cimg_instance, + offset,filename?filename:"(FILE*)"); + + 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)):(int)((longT)dx*bpp/8)), align_bytes = (4 - dx_bytes%4)%4; const ulongT cimg_iobuffer = (ulongT)24*1024*1024, - buf_size = std::min((ulongT)cimg::abs(dy)*(dx_bytes + align_bytes),(ulongT)file_size - offset); + buf_size = (ulongT)cimg::abs(dy)*(dx_bytes + align_bytes); + + if (buf_size>=fsiz) + throw CImgIOException(_cimg_instance + "load_bmp(): File size %lu for filename '%s' does not match " + "encoded image dimensions (%d,%d).", + cimg_instance, + (long)fsiz,filename?filename:"(FILE*)",dx,dy); CImg colormap; if (bpp<16) { if (!nb_colors) nb_colors = 1<0) cimg::fseek(nfile,xoffset,SEEK_CUR); + if (xoffset<0 || xoffset>=file_size) + throw CImgIOException(_cimg_instance + "load_bmp(): Malformed header in filename '%s'.", + cimg_instance, + filename?filename:"(FILE*)"); + cimg::fseek(nfile,xoffset,SEEK_CUR); CImg buffer; if (buf_size=0; --y) { if (buf_size>=cimg_iobuffer) { if (!cimg::fread(ptrs=buffer._data,dx_bytes,nfile)) break; @@ -48483,10 +54649,10 @@ namespace cimg_library_suffixed { } 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); + const unsigned short col = (unsigned short)c2<<8 | c1; + (*this)(x,y,2) = (T)((col&0x1F)<<3); + (*this)(x,y,1) = (T)(((col>>5)&0x3F)<<3); + (*this)(x,y,0) = (T)(((col>>11)&0x1F)<<3); } ptrs+=align_bytes; } @@ -48562,7 +54728,7 @@ namespace cimg_library_suffixed { 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. + jpeg_destroy(cinfo); // Clean memory and temp files longjmp(c_err->setjmp_buffer,1); } #endif @@ -48661,6 +54827,7 @@ namespace cimg_library_suffixed { 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(); @@ -48728,36 +54895,36 @@ namespace cimg_library_suffixed { //! Load image from a PNG file. /** \param filename Filename, as a C-string. - \param[out] bits_per_pixel Number of bits per pixels used to store pixel values in the image file. + \param[out] bits_per_value Number of bits used to store a scalar value in the image file. **/ - CImg& load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { - return _load_png(0,filename,bits_per_pixel); + CImg& load_png(const char *const filename, unsigned int *const bits_per_value=0) { + return _load_png(0,filename,bits_per_value); } //! Load image from a PNG file \newinstance. - static CImg get_load_png(const char *const filename, unsigned int *const bits_per_pixel=0) { - return CImg().load_png(filename,bits_per_pixel); + static CImg get_load_png(const char *const filename, unsigned int *const bits_per_value=0) { + return CImg().load_png(filename,bits_per_value); } //! Load image from a PNG file \overloading. - CImg& load_png(std::FILE *const file, unsigned int *const bits_per_pixel=0) { - return _load_png(file,0,bits_per_pixel); + CImg& load_png(std::FILE *const file, unsigned int *const bits_per_value=0) { + return _load_png(file,0,bits_per_value); } //! Load image from a PNG file \newinstance. - static CImg get_load_png(std::FILE *const file, unsigned int *const bits_per_pixel=0) { - return CImg().load_png(file,bits_per_pixel); + static CImg get_load_png(std::FILE *const file, unsigned int *const bits_per_value=0) { + return CImg().load_png(file,bits_per_value); } // (Note: Most of this function has been written by Eric Fausett) - CImg& _load_png(std::FILE *const file, const char *const filename, unsigned int *const bits_per_pixel) { + CImg& _load_png(std::FILE *const file, const char *const filename, unsigned int *const bits_per_value) { if (!file && !filename) throw CImgArgumentException(_cimg_instance "load_png(): Specified filename is (null).", cimg_instance); #ifndef cimg_use_png - cimg::unused(bits_per_pixel); + cimg::unused(bits_per_value); if (file) throw CImgIOException(_cimg_instance "load_png(): Unable to load data from '(FILE*)' unless libpng is enabled.", @@ -48767,13 +54934,13 @@ namespace cimg_library_suffixed { #else // Open file and check for PNG validity #if defined __GNUC__ - const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning. + 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 }; + unsigned char pngCheck[8] = {}; cimg::fread(pngCheck,8,(std::FILE*)nfile); if (png_sig_cmp(pngCheck,0,8)) { if (!file) cimg::fclose(nfile); @@ -48831,7 +54998,8 @@ namespace cimg_library_suffixed { 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; + png_set_interlace_handling(png_ptr); + if (bits_per_value) *bits_per_value = (unsigned int)bit_depth; // Transforms to unify image data if (color_type==PNG_COLOR_TYPE_PALETTE) { @@ -48867,7 +55035,7 @@ namespace cimg_library_suffixed { } const int byte_depth = bit_depth>>3; - // Allocate Memory for Image Read + // Allocate memory for image reading png_bytep *const imgData = new png_bytep[H]; for (unsigned int row = 0; rowsiz) + throw CImgIOException(_cimg_instance + "load_pnm(): Specified image dimensions in file '%s' exceed file size.", + cimg_instance, + filename); + } + switch (ppm_type) { - case 1 : { // 2d b&w ascii. + 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. + 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. + 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) { @@ -49007,7 +55184,7 @@ namespace cimg_library_suffixed { } else break; } } break; - case 4 : { // 2d b&w binary (support 3D PINK extension). + case 4 : { // 2D b&w binary (support 3D PINK extension) CImg raw; assign(W,H,D,1); T *ptrd = data(0,0,0,0); @@ -49025,8 +55202,8 @@ namespace cimg_library_suffixed { } } } break; - case 5 : case 7 : { // 2d/3d grey binary (support 3D PINK extension). - if (colormax<256) { // 8 bits. + case 5 : case 7 : { // 2D/3D grey binary (support 3D PINK extension) + if (colormax<256) { // 8 bits CImg raw; assign(W,H,D,1); T *ptrd = data(0,0,0,0); @@ -49037,22 +55214,22 @@ namespace cimg_library_suffixed { const unsigned char *ptrs = raw._data; for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); } - } else { // 16 bits. + } else { // 16 bits CImg 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); + 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. + case 6 : { // 2D color binary + if (colormax<256) { // 8 bits CImg raw; assign(W,H,1,3); T @@ -49070,7 +55247,7 @@ namespace cimg_library_suffixed { *(ptr_b++) = (T)*(ptrs++); } } - } else { // 16 bits. + } else { // 16 bits CImg raw; assign(W,H,1,3); T @@ -49091,7 +55268,7 @@ namespace cimg_library_suffixed { } } } break; - case 8 : { // 2d/3d grey binary with int32 integers (PINK extension). + case 8 : { // 2D/3D grey binary with int32 integers (PINK extension) CImg raw; assign(W,H,D,1); T *ptrd = data(0,0,0,0); @@ -49103,7 +55280,7 @@ namespace cimg_library_suffixed { for (ulongT off = (ulongT)raw._width; off; --off) *(ptrd++) = (T)*(ptrs++); } } break; - case 9 : { // 2d/3d grey binary with float values (PINK extension). + case 9 : { // 2D/3D grey binary with float values (PINK extension) CImg raw; assign(W,H,D,1); T *ptrd = data(0,0,0,0); @@ -49219,7 +55396,7 @@ namespace cimg_library_suffixed { } } if (!file) cimg::fclose(nfile); - return mirror('y'); // Most of the .pfm files are flipped along the y-axis. + return mirror('y'); // Most of the .pfm files are flipped along the y-axis } //! Load image from a RGB file. @@ -49342,34 +55519,34 @@ namespace cimg_library_suffixed { \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. + \param[out] bits_per_value Number of bits used to store a scalar value in the image file. \param[out] voxel_size Voxel size, as stored in the filename. \param[out] description Description, as stored in the filename. \note - libtiff support is enabled by defining the precompilation - directive \c cimg_use_tif. + directive \c cimg_use_tiff. - When libtiff is enabled, 2D and 3D (multipage) several channel per pixel are supported for char,uchar,short,ushort,float and \c double pixel types. - - If \c cimg_use_tif is not defined at compile time the + - If \c cimg_use_tiff is not defined at compile time the function uses CImg& load_other(const char*). **/ CImg& 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 *const description=0) { + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, unsigned int *const bits_per_value=0, + float *const voxel_size=0, CImg *const description=0) { if (!filename) throw CImgArgumentException(_cimg_instance "load_tiff(): Specified filename is (null).", cimg_instance); const unsigned int - nfirst_frame = first_frame1) throw CImgArgumentException(_cimg_instance "load_tiff(): Unable to read sub-images from file '%s' unless libtiff is enabled.", @@ -49396,7 +55573,7 @@ namespace cimg_library_suffixed { TIFFSetDirectory(tif,0); CImg frame; for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) { - frame._load_tiff(tif,l,voxel_size,description); + frame._load_tiff(tif,l,bits_per_value,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) @@ -49416,18 +55593,18 @@ namespace cimg_library_suffixed { //! Load image from a TIFF file \newinstance. static CImg 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 *const description=0) { - return CImg().load_tiff(filename,first_frame,last_frame,step_frame,voxel_size,description); + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, unsigned int *const bits_per_value=0, + float *const voxel_size=0, CImg *const description=0) { + return CImg().load_tiff(filename,first_frame,last_frame,step_frame,bits_per_value,voxel_size,description); } // (Original contribution by Jerome Boulanger). #ifdef cimg_use_tiff template - void _load_tiff_tiled_contig(TIFF *const tif, const uint16 samplesperpixel, - const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) { + void _load_tiff_tiled_contig(TIFF *const tif, const cimg_uint16 samplesperpixel, + const cimg_uint32 nx, const cimg_uint32 ny, + const cimg_uint32 tw, const cimg_uint32 th) { t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif)); if (buf) { for (unsigned int row = 0; row - void _load_tiff_tiled_separate(TIFF *const tif, const uint16 samplesperpixel, - const uint32 nx, const uint32 ny, const uint32 tw, const uint32 th) { + void _load_tiff_tiled_separate(TIFF *const tif, const cimg_uint16 samplesperpixel, + const cimg_uint32 nx, const cimg_uint32 ny, + const cimg_uint32 tw, const cimg_uint32 th) { t *const buf = (t*)_TIFFmalloc(TIFFTileSize(tif)); if (buf) { for (unsigned int vv = 0; vv - void _load_tiff_contig(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { + void _load_tiff_contig(TIFF *const tif, const cimg_uint16 samplesperpixel, + const cimg_uint32 nx, const cimg_uint32 ny) { t *const buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); if (buf) { - uint32 row, rowsperstrip = (uint32)-1; + cimg_uint32 row, rowsperstrip = (cimg_uint32)-1; TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); for (row = 0; rowny?ny - row:rowsperstrip); + cimg_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); @@ -49499,14 +55678,15 @@ namespace cimg_library_suffixed { } template - void _load_tiff_separate(TIFF *const tif, const uint16 samplesperpixel, const uint32 nx, const uint32 ny) { + void _load_tiff_separate(TIFF *const tif, const cimg_uint16 samplesperpixel, + const cimg_uint32 nx, const cimg_uint32 ny) { t *buf = (t*)_TIFFmalloc(TIFFStripSize(tif)); if (buf) { - uint32 row, rowsperstrip = (uint32)-1; + cimg_uint32 row, rowsperstrip = (cimg_uint32)-1; TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip); for (unsigned int vv = 0; vvny?ny - row:rowsperstrip); + cimg_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); @@ -49524,12 +55704,12 @@ namespace cimg_library_suffixed { } } - CImg& _load_tiff(TIFF *const tif, const unsigned int directory, + CImg& _load_tiff(TIFF *const tif, const unsigned int directory, unsigned int *const bits_per_value, float *const voxel_size, CImg *const description) { if (!TIFFSetDirectory(tif,directory)) return assign(); - uint16 samplesperpixel = 1, bitspersample = 8, photo = 0; - uint16 sampleformat = 1; - uint32 nx = 1, ny = 1; + cimg_uint16 samplesperpixel = 1, bitspersample = 8, photo = 0; + cimg_uint16 sampleformat = 1; + cimg_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); @@ -49537,23 +55717,24 @@ namespace cimg_library_suffixed { TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleformat); TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample); TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo); + if (bits_per_value) *bits_per_value = (unsigned int)bitspersample; 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. + 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. + 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]; + voxel_size[0] = 1.f/voxel_size[0]; + voxel_size[1] = 1.f/voxel_size[1]; } if (description) { const char *s_description = 0; @@ -49568,13 +55749,13 @@ namespace cimg_library_suffixed { (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)); + cimg_uint32 *const raster = (cimg_uint32*)_TIFFmalloc(nx*ny*sizeof(cimg_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); + cimg::strbuffersize(nx*ny*sizeof(cimg_uint32)),filename); } TIFFReadRGBAImage(tif,nx,ny,raster,0); switch (spectrum) { @@ -49584,9 +55765,9 @@ namespace cimg_library_suffixed { 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]); + (*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 : @@ -49599,11 +55780,11 @@ namespace cimg_library_suffixed { break; } _TIFFfree(raster); - } else { // Other cases. - uint16 config; + } else { // Other cases + cimg_uint16 config; TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config); if (TIFFIsTiled(tif)) { - uint32 tw = 1, th = 1; + cimg_uint32 tw = 1, th = 1; TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw); TIFFGetField(tif,TIFFTAG_TILELENGTH,&th); if (config==PLANARCONFIG_CONTIG) switch (bitspersample) { @@ -49723,11 +55904,11 @@ namespace cimg_library_suffixed { rdr.ndim(2)?rdr.ndim(2):1, rdr.ndim(3)?rdr.ndim(3):1, rdr.ndim(4)?rdr.ndim(4):1); - if (cimg::type::string()==cimg::type::string()) + if (pixel_type()==cimg::type::string()) rdr.setup_read_byte(); - else if (cimg::type::string()==cimg::type::string()) + else if (pixel_type()==cimg::type::string()) rdr.setup_read_int(); - else if (cimg::type::string()==cimg::type::string()) + else if (pixel_type()==cimg::type::string()) rdr.setup_read_double(); else rdr.setup_read_float(); @@ -49775,16 +55956,17 @@ namespace cimg_library_suffixed { if (!file) { CImg body(1024); const char *const ext = cimg::split_filename(filename,body); - if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file. + const unsigned int len = (unsigned int)std::strlen(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"); + cimg_snprintf(body._data + len,body._width - len,".img"); nfile = cimg::fopen(body,"rb"); - } else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file. + } 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"); + cimg_snprintf(body._data + len,body._width - len,".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. + } 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'.", @@ -50038,12 +56220,12 @@ namespace cimg_library_suffixed { case 0 : break; case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; - std::strncpy(tmp1,tmp2,tmp1._width - 1); // fallthrough + std::strncpy(tmp1,tmp2,tmp1._width - 1); // Fallthrough 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; // fallthrough + if (out[4]>=0) break; // Fallthrough default : throw CImgIOException("CImg<%s>::load_inr(): Invalid pixel type '%s' defined in header.", pixel_type(), @@ -50137,6 +56319,7 @@ namespace cimg_library_suffixed { *(ptr_b++) = (T)pixels[y][x].b; *(ptr_a++) = (T)pixels[y][x].a; } + return *this; #elif defined(cimg_use_tinyexr) float *res; const char *err = 0; @@ -50145,12 +56328,12 @@ namespace cimg_library_suffixed { if (ret) throw CImgIOException(_cimg_instance "load_exr(): Unable to load EXR file '%s'.", cimg_instance,filename); - CImg(out,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this); + CImg(res,4,width,height,1,true).get_permute_axes("yzcx").move_to(*this); std::free(res); + return *this; #else return load_other(filename); #endif - return *this; } //! Load image from a EXR file \newinstance. @@ -50185,6 +56368,13 @@ namespace cimg_library_suffixed { #define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \ cimg::fread(dims,nbdim,nfile); \ if (endian) cimg::invert_endianness(dims,nbdim); \ + if ((ulongT)nwidth*nheight*ndepth*ndim>fsiz) \ + throw CImgIOException(_cimg_instance \ + "load_pandore(): File size %lu for filename '%s' does not match "\ + "encoded image dimensions (%d,%d,%d,%d).",\ + cimg_instance,\ + (long)fsiz,filename?filename:"(FILE*)",\ + (int)nwidth,(int)nheight,(int)ndepth,(int)ndim); \ assign(nwidth,nheight,ndepth,ndim); \ const size_t siz = size(); \ stype *buffer = new stype[siz]; \ @@ -50208,6 +56398,7 @@ namespace cimg_library_suffixed { "load_pandore(): Specified filename is (null).", cimg_instance); + const ulongT fsiz = file?(ulongT)cimg_max_buf_size:(ulongT)cimg::fsize(filename); std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); CImg header(32); cimg::fread(header._data,12,nfile); @@ -50218,8 +56409,8 @@ namespace cimg_library_suffixed { cimg_instance, filename?filename:"(FILE*)"); } - unsigned int imageid, dims[8] = { 0 }; - int ptbuf[4] = { 0 }; + unsigned int imageid, dims[8] = {}; + int ptbuf[4] = {}; cimg::fread(&imageid,1,nfile); const bool endian = imageid>255; if (endian) cimg::invert_endianness(imageid); @@ -50235,7 +56426,7 @@ namespace cimg_library_suffixed { 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 + case 11 : { // Region 1D cimg::fread(dims,3,nfile); if (endian) cimg::invert_endianness(dims,3); assign(dims[1],1,1,1); @@ -50268,7 +56459,7 @@ namespace cimg_library_suffixed { } } break; - case 12 : { // Region 2d + case 12 : { // Region 2D cimg::fread(dims,4,nfile); if (endian) cimg::invert_endianness(dims,4); assign(dims[2],dims[1],1,1); @@ -50301,7 +56492,7 @@ namespace cimg_library_suffixed { } } break; - case 13 : { // Region 3d + case 13 : { // Region 3D cimg::fread(dims,5,nfile); if (endian) cimg::invert_endianness(dims,5); assign(dims[3],dims[2],dims[1],1); @@ -50354,17 +56545,17 @@ namespace cimg_library_suffixed { 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 + 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 + 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 + 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]; @@ -50445,9 +56636,9 @@ namespace cimg_library_suffixed { } CImg& _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 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 @@ -50457,7 +56648,7 @@ namespace cimg_library_suffixed { throw CImgArgumentException(_cimg_instance "load_raw(): Specified filename '%s' is a directory.", cimg_instance,filename); - + const bool is_bool = pixel_type()==cimg::type::string(); ulongT siz = (ulongT)size_x*size_y*size_z*size_c; unsigned int _size_x = size_x, @@ -50465,28 +56656,37 @@ namespace cimg_library_suffixed { _size_z = size_z, _size_c = size_c; std::FILE *const nfile = file?file:cimg::fopen(filename,"rb"); - if (!siz) { // Retrieve file size. + 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; + siz = (ulongT)cimg::ftell(nfile); + if (!is_bool) { siz/=sizeof(T); _size_y = (unsigned int)siz; } + else _size_y = (unsigned int)(siz*8); _size_x = _size_z = _size_c = 1; cimg::fseek(nfile,fpos,SEEK_SET); } - cimg::fseek(nfile,offset,SEEK_SET); + cimg::fseek(nfile,(longT)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 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 (is_bool) { // Boolean data (bitwise) + unsigned char *const buf = new unsigned char[siz]; + cimg::fread(buf,siz,nfile); + _uchar2bool(buf,siz,is_multiplexed); + delete[] buf; + } else { // Non-boolean data + if (siz && (!is_multiplexed || size_c==1)) { // Non-multiplexed + cimg::fread(_data,siz,nfile); + if (invert_endianness) cimg::invert_endianness(_data,siz); + } else if (siz) { // Multiplexed + CImg 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); @@ -50529,7 +56729,7 @@ namespace cimg_library_suffixed { const unsigned int size_x, const unsigned int size_y=1, const unsigned int chroma_subsampling=444, const unsigned int first_frame=0, const unsigned int last_frame=~0U, - const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') { + const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') { return get_load_yuv(file,size_x,size_y,chroma_subsampling, first_frame,last_frame,step_frame,yuv2rgb,axis).move_to(*this); } @@ -50539,15 +56739,15 @@ namespace cimg_library_suffixed { const unsigned int size_x, const unsigned int size_y=1, const unsigned int chroma_subsampling=444, const unsigned int first_frame=0, const unsigned int last_frame=~0U, - const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') { + const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z') { return CImgList().load_yuv(file,size_x,size_y,chroma_subsampling, first_frame,last_frame,step_frame,yuv2rgb).get_append(axis); } - //! Load 3d object from a .OFF file. + //! 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[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 @@ -50555,19 +56755,19 @@ namespace cimg_library_suffixed { return _load_off(primitives,colors,0,filename); } - //! Load 3d object from a .OFF file \newinstance. + //! Load 3D object from a .OFF file \newinstance. template static CImg get_load_off(CImgList& primitives, CImgList& colors, const char *const filename) { return CImg().load_off(primitives,colors,filename); } - //! Load 3d object from a .OFF file \overloading. + //! Load 3D object from a .OFF file \overloading. template CImg& load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { return _load_off(primitives,colors,file,0); } - //! Load 3d object from a .OFF file \newinstance. + //! Load 3D object from a .OFF file \newinstance. template static CImg get_load_off(CImgList& primitives, CImgList& colors, std::FILE *const file) { return CImg().load_off(primitives,colors,file); @@ -50779,7 +56979,7 @@ namespace cimg_library_suffixed { \param last_frame Index of the last frame to read. \param step_frame Step value for frame reading. \param axis Alignment axis. - \param align Apending alignment. + \param align Appending alignment. **/ CImg& load_video(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U, @@ -50828,6 +57028,66 @@ namespace cimg_library_suffixed { return CImgList().load_gif_external(filename).get_append(axis,align); } + //! Load image from a HEIC file. + /** + \param filename Filename, as a C-string. + **/ + CImg& load_heif(const char *const filename) { + return _load_heif(filename); + } + + //! Load image from a HEIC file \newinstance. + static CImg get_load_heif(const char *const filename) { + return CImg().load_heif(filename); + } + + CImg& _load_heif(const char *const filename) { +#ifndef cimg_use_heif + return load_other(filename); +#else + try { + heif::Context ctx; + ctx.read_from_file(filename); + + heif::ImageHandle handle = ctx.get_primary_image_handle(); + const heif::Image image = + handle.decode_image(heif_colorspace_RGB,handle.has_alpha_channel()?heif_chroma_interleaved_RGBA: + heif_chroma_interleaved_RGB); + const int + W = image.get_width(heif_channel_interleaved), + H = image.get_height(heif_channel_interleaved), + S = handle.has_alpha_channel()?4:3; + assign(W,H,1,S); + + int stride; + const unsigned char *const buffer = image.get_plane(heif_channel_interleaved,&stride); + T *ptr_r = _data, *ptr_g = data(0,0,0,1), *ptr_b = data(0,0,0,2), *ptr_a = S>3?data(0,0,0,3):0; + cimg_forY(*this,y) { + const unsigned char *ptrs = buffer + y*stride; + if (ptr_a) cimg_forX(*this,x) { // RGBA + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + *(ptr_a++) = (T)*(ptrs++); + } + else cimg_forX(*this,x) { // RGB + *(ptr_r++) = (T)*(ptrs++); + *(ptr_g++) = (T)*(ptrs++); + *(ptr_b++) = (T)*(ptrs++); + } + } + } catch (const heif::Error& e) { + throw CImgInstanceException(_cimg_instance + "load_heif(): Unable to decode image: %s", + cimg_instance, + e.get_message().c_str()); + } catch (...) { + throw; + } + return *this; +#endif + } + //! Load image using GraphicsMagick's external tool 'gm'. /** \param filename Filename, as a C-string. @@ -50837,19 +57097,32 @@ namespace cimg_library_suffixed { throw CImgArgumentException(_cimg_instance "load_graphicsmagick_external(): Specified filename is (null).", cimg_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists CImg command(1024), filename_tmp(256); std::FILE *file = 0; const CImg s_filename = CImg::string(filename)._system_strescape(); #if cimg_OS==1 if (!cimg::system("which gm")) { - cimg_snprintf(command,command._width,"%s convert \"%s\" pnm:-", - cimg::graphicsmagick_path(),s_filename.data()); + cimg_snprintf(command,command._width,"%s convert \"%s\" %s:-", + cimg::graphicsmagick_path(), + s_filename.data(), +#ifdef cimg_use_png + "png" +#else + "pnm" +#endif + ); file = popen(command,"r"); if (file) { const unsigned int omode = cimg::exception_mode(); cimg::exception_mode(0); - try { load_pnm(file); } catch (...) { + try { +#ifdef cimg_use_png + load_png(file); +#else + load_pnm(file); +#endif + } catch (...) { pclose(file); cimg::exception_mode(omode); throw CImgIOException(_cimg_instance @@ -50864,15 +57137,24 @@ namespace cimg_library_suffixed { } #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); + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(), + cimg_file_separator, + cimg::filenamerand(), +#ifdef cimg_use_png + "png" +#else + "pnm" +#endif + ); + if ((file=cimg::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_snprintf(command,command._width,"\"%s\" convert \"%s\" \"%s\"", + cimg::graphicsmagick_path(), + s_filename.data(), CImg::string(filename_tmp)._system_strescape().data()); cimg::system(command,cimg::graphicsmagick_path()); - if (!(file = std_fopen(filename_tmp,"rb"))) { + if (!(file=cimg::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'.", @@ -50880,7 +57162,11 @@ namespace cimg_library_suffixed { filename); } else cimg::fclose(file); +#ifdef cimg_use_png + load_png(filename_tmp); +#else load_pnm(filename_tmp); +#endif std::remove(filename_tmp); return *this; } @@ -50899,7 +57185,7 @@ namespace cimg_library_suffixed { throw CImgIOException(_cimg_instance "load_gzip_external(): Specified filename is (null).", cimg_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists CImg command(1024), filename_tmp(256), body(256); const char *const ext = cimg::split_filename(filename,body), @@ -50918,14 +57204,14 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); } while (file); - cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"", + cimg_snprintf(command,command._width,"\"%s\" -c \"%s\" > \"%s\"", cimg::gunzip_path(), CImg::string(filename)._system_strescape().data(), CImg::string(filename_tmp)._system_strescape().data()); cimg::system(command); - if (!(file = std_fopen(filename_tmp,"rb"))) { + if (!(file=cimg::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'.", @@ -50952,21 +57238,33 @@ namespace cimg_library_suffixed { throw CImgArgumentException(_cimg_instance "load_imagemagick_external(): Specified filename is (null).", cimg_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists CImg command(1024), filename_tmp(256); std::FILE *file = 0; const CImg s_filename = CImg::string(filename)._system_strescape(); #if cimg_OS==1 if (!cimg::system("which convert")) { - cimg_snprintf(command,command._width,"%s%s \"%s\" pnm:-", + cimg_snprintf(command,command._width,"%s%s \"%s\" %s:-", cimg::imagemagick_path(), !cimg::strcasecmp(cimg::split_filename(filename),"pdf")?" -density 400x400":"", - s_filename.data()); + s_filename.data(), +#ifdef cimg_use_png + "png" +#else + "pnm" +#endif + ); file = popen(command,"r"); if (file) { const unsigned int omode = cimg::exception_mode(); cimg::exception_mode(0); - try { load_pnm(file); } catch (...) { + try { +#ifdef cimg_use_png + load_png(file); +#else + load_pnm(file); +#endif + } catch (...) { pclose(file); cimg::exception_mode(omode); throw CImgIOException(_cimg_instance @@ -50981,16 +57279,25 @@ namespace cimg_library_suffixed { } #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); + cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s", + cimg::temporary_path(), + cimg_file_separator, + cimg::filenamerand(), +#ifdef cimg_use_png + "png" +#else + "pnm" +#endif + ); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); } while (file); - cimg_snprintf(command,command._width,"%s%s \"%s\" \"%s\"", + 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::string(filename_tmp)._system_strescape().data()); + s_filename.data(), + CImg::string(filename_tmp)._system_strescape().data()); cimg::system(command,cimg::imagemagick_path()); - if (!(file = std_fopen(filename_tmp,"rb"))) { + if (!(file=cimg::std_fopen(filename_tmp,"rb"))) { cimg::fclose(cimg::fopen(filename,"r")); throw CImgIOException(_cimg_instance "load_imagemagick_external(): Failed to load file '%s' with " @@ -50999,7 +57306,11 @@ namespace cimg_library_suffixed { filename); } else cimg::fclose(file); +#ifdef cimg_use_png + load_png(filename_tmp); +#else load_pnm(filename_tmp); +#endif std::remove(filename_tmp); return *this; } @@ -51009,7 +57320,7 @@ namespace cimg_library_suffixed { return CImg().load_imagemagick_external(filename); } - //! Load image from a DICOM file, using XMedcon's external tool 'medcon'. + //! Load image from a DICOM file, using Medcon's external tool 'medcon'. /** \param filename Filename, as a C-string. **/ @@ -51018,26 +57329,26 @@ namespace cimg_library_suffixed { throw CImgArgumentException(_cimg_instance "load_medcon_external(): Specified filename is (null).", cimg_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists CImg 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); + if ((file=cimg::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_snprintf(command,command._width,"\"%s\" -w -c anlz -o \"%s\" -f \"%s\"", cimg::medcon_path(), CImg::string(filename_tmp)._system_strescape().data(), CImg::string(filename)._system_strescape().data()); - cimg::system(command); + cimg::system(command,cimg::medcon_path()); cimg::split_filename(filename_tmp,body); cimg_snprintf(command,command._width,"%s.hdr",body._data); - file = std_fopen(command,"rb"); + file = cimg::std_fopen(command,"rb"); if (!file) { cimg_snprintf(command,command._width,"m000-%s.hdr",body._data); - file = std_fopen(command,"rb"); + file = cimg::std_fopen(command,"rb"); if (!file) { throw CImgIOException(_cimg_instance "load_medcon_external(): Failed to load file '%s' with external command 'medcon'.", @@ -51054,11 +57365,68 @@ namespace cimg_library_suffixed { return *this; } - //! Load image from a DICOM file, using XMedcon's external tool 'medcon' \newinstance. + //! Load image from a DICOM file, using Medcon's external tool 'medcon' \newinstance. static CImg get_load_medcon_external(const char *const filename) { return CImg().load_medcon_external(filename); } + //! Load image from a .pdf file. + /** + \param filename Filename, as a C-string. + \param resolution Image resolution. + **/ + CImg& load_pdf_external(const char *const filename, const unsigned int resolution=400) { + if (!filename) + throw CImgArgumentException(_cimg_instance + "load_pdf_external(): Specified filename is (null).", + cimg_instance); + CImg command(1024), filename_tmp(256); + std::FILE *file = 0; + const CImg s_filename = CImg::string(filename)._system_strescape(); +#if cimg_OS==1 + cimg_snprintf(command,command._width,"gs -q -dNOPAUSE -sDEVICE=ppmraw -o - -r%u \"%s\"", + resolution,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_pdf_external(): Failed to load file '%s' with external command 'gs'.", + 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=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); + } while (file); + cimg_snprintf(command,command._width,"gs -q -dNOPAUSE -sDEVICE=ppmraw -o \"%s\" -r%u \"%s\"", + CImg::string(filename_tmp)._system_strescape().data(),resolution,s_filename.data()); + cimg::system(command,"gs"); + if (!(file=cimg::std_fopen(filename_tmp,"rb"))) { + cimg::fclose(cimg::fopen(filename,"r")); + throw CImgIOException(_cimg_instance + "load_pdf_external(): Failed to load file '%s' with external command 'gs'.", + cimg_instance, + filename); + } else cimg::fclose(file); + load_pnm(filename_tmp); + std::remove(filename_tmp); + return *this; + } + + //! Load image from a .pdf file \newinstance. + static CImg get_load_pdf_external(const char *const filename, const unsigned int resolution=400) { + return CImg().load_pdf_external(filename,resolution); + } + //! Load image from a RAW Color Camera file, using external tool 'dcraw'. /** \param filename Filename, as a C-string. @@ -51068,7 +57436,7 @@ namespace cimg_library_suffixed { throw CImgArgumentException(_cimg_instance "load_dcraw_external(): Specified filename is (null).", cimg_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists CImg command(1024), filename_tmp(256); std::FILE *file = 0; const CImg s_filename = CImg::string(filename)._system_strescape(); @@ -51094,12 +57462,12 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); } while (file); - cimg_snprintf(command,command._width,"%s -w -4 -c \"%s\" > \"%s\"", + cimg_snprintf(command,command._width,"\"%s\" -w -4 -c \"%s\" > \"%s\"", cimg::dcraw_path(),s_filename.data(),CImg::string(filename_tmp)._system_strescape().data()); cimg::system(command,cimg::dcraw_path()); - if (!(file = std_fopen(filename_tmp,"rb"))) { + if (!(file=cimg::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'.", @@ -51117,87 +57485,139 @@ namespace cimg_library_suffixed { return CImg().load_dcraw_external(filename); } +#ifdef cimg_use_opencv + + // Convert a continuous cv::Mat to a CImg. + static CImg _cvmat2cimg(const cv::Mat &src) { + if (src.channels()==1) return CImg(src.ptr(),src.cols,src.rows,1,1); + else if (src.channels()==3) { // BGR + CImg res(src.cols,src.rows,1,src.channels()); + const unsigned char *ptrs = src.ptr(); + unsigned char *pR = res.data(), *pG = res.data(0,0,0,1), *pB = res.data(0,0,0,2); + cimg_forXY(res,x,y) { *(pB++) = *(ptrs++); *(pG++) = *(ptrs++); *(pR++) = *(ptrs++); } + return res; + } + return CImg(src.ptr(),src.channels(),src.cols,src.rows,1,true).get_permute_axes("yzcx"); + } + + // Convert a CImg to a cv::Mat. + cv::Mat _cimg2cvmat() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "_cimg2cvmat() : Instance image is empty.", + cimg_instance); + if (_spectrum==2) + throw CImgInstanceException(_cimg_instance + "_cimg2cvmat() : Invalid number of channels (should be '1' or '3+').", + cimg_instance); + if (_depth!=1) + throw CImgInstanceException(_cimg_instance + "_cimg2cvmat() : Invalid number of slices (should be '1').", + cimg_instance); + int mat_type = -1; + if (pixel_type()==cimg::type::string()) mat_type = CV_8UC1; + if (pixel_type()==cimg::type::string()) mat_type = CV_8SC1; + if (pixel_type()==cimg::type::string()) mat_type = CV_16UC1; + if (pixel_type()==cimg::type::string()) mat_type = CV_16SC1; + if (pixel_type()==cimg::type::string()) mat_type = CV_32SC1; + if (pixel_type()==cimg::type::string()) mat_type = CV_32FC1; + if (pixel_type()==cimg::type::string()) mat_type = CV_64FC1; + if (mat_type<0) + throw CImgInstanceException(_cimg_instance + "_cvmat2cimg() : pixel type '%s' is not supported.", + cimg_instance,pixel_type()); + cv::Mat res; + std::vector channels(_spectrum); + if (_spectrum>1) { + cimg_forC(*this,c) + channels[c] = cv::Mat(_height,_width,mat_type,_data + _width*_height*(_spectrum - 1 - c)); + cv::merge(channels,res); + } else res = cv::Mat(_height,_width,mat_type,_data).clone(); + return res; + } + +#endif + //! Load image from a camera stream, using OpenCV. /** - \param camera_index Index of the camera to capture images from. + \param index Index of the camera to capture images from (from 0 to 63). + \param capture_width Width of the desired image ('0' stands for default value). + \param capture_height Height of the desired image ('0' stands for default value). \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. - \param capture_width Width of the desired image. - \param capture_height Height of the desired image. + \param release_camera Tells if the camera resource must be released at the end of the method. **/ - CImg& 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) { + CImg& load_camera(const unsigned int camera_index=0, + const unsigned int capture_width=0, const unsigned int capture_height=0, + const unsigned int skip_frames=0, const bool release_camera=true) { #ifdef cimg_use_opencv - if (camera_index>99) + if (camera_index>=64) 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]; + static cv::VideoCapture *captures[64] = {}; + static unsigned int captures_w[64], captures_h[64]; 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; + if (captures[camera_index]) captures[camera_index]->release(); + delete captures[camera_index]; + captures[camera_index] = 0; + captures_w[camera_index] = captures_h[camera_index] = 0; cimg::mutex(9,0); return *this; } - if (!capture[camera_index]) { + if (!captures[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]) { + captures[camera_index] = new cv::VideoCapture(camera_index); + captures_w[camera_index] = captures_h[camera_index] = 0; + if (!captures[camera_index]->isOpened()) { + delete captures[camera_index]; + captures[camera_index] = 0; + cimg::mutex(9,0); throw CImgIOException(_cimg_instance "load_camera(): Failed to initialize camera #%u.", cimg_instance, camera_index); } + cimg::mutex(9,0); } 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_width!=captures_w[camera_index]) { + captures[camera_index]->set(_cimg_cap_prop_frame_width,capture_width); + captures_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; iwidthStep - 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++); - } + if (capture_height!=captures_h[camera_index]) { + captures[camera_index]->set(_cimg_cap_prop_frame_height,capture_height); + captures_h[camera_index] = capture_height; } + for (unsigned int i = 0; igrab(); + cv::Mat cvimg; + captures[camera_index]->read(cvimg); + if (cvimg.empty()) { + cimg::mutex(9,0); + load_camera(camera_index,0,0,0,true); // Release camera + throw CImgIOException(_cimg_instance + "load_camera(): Failed to retrieve a %ux%u frame from camera #%u.", + cimg_instance, + capture_width,capture_height,camera_index); + } else _cvmat2cimg(cvimg).move_to(*this); 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).", + "load_camera(): This function requires features from the OpenCV library " + "('-Dcimg_use_opencv' must be defined).", cimg_instance); #endif } //! Load image from a camera stream, using OpenCV \newinstance. - static CImg 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().load_camera(camera_index,skip_frames,release_camera,capture_width,capture_height); + static CImg get_load_camera(const unsigned int camera_index=0, + const unsigned int capture_width=0, const unsigned int capture_height=0, + const unsigned int skip_frames=0, const bool release_camera=true) { + return CImg().load_camera(camera_index,capture_width,capture_height,skip_frames,release_camera); } //! Load image using various non-native ways. @@ -51221,7 +57641,7 @@ namespace cimg_library_suffixed { try { load_cimg(filename); } catch (CImgException&) { try { - std::fclose(cimg::fopen(filename,"rb")); + cimg::fclose(cimg::fopen(filename,"rb")); } catch (CImgException&) { cimg::exception_mode(omode); throw CImgIOException(_cimg_instance @@ -51287,15 +57707,15 @@ namespace cimg_library_suffixed { 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]); + std::fprintf(cimg::output(),cimg::type::format_s(),cimg::type::format(_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(), + 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[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]), @@ -51342,8 +57762,8 @@ namespace cimg_library_suffixed { const CImg& _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; + const bool exit_on_singleclick) const { + unsigned int oldw = 0, oldh = 0, _XYZ[3] = {}, key = 0; int x0 = 0, y0 = 0, z0 = 0, x1 = width() - 1, y1 = height() - 1, z1 = depth() - 1, old_mouse_x = -1, old_mouse_y = -1; @@ -51362,12 +57782,14 @@ namespace cimg_library_suffixed { 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; + _XYZ[0] = (unsigned int)(x0 + x1 + 1)/2; + _XYZ[1] = (unsigned int)(y0 + y1 + 1)/2; + _XYZ[2] = (unsigned int)(z0 + z1 + 1)/2; } x0 = 0; y0 = 0; z0 = 0; x1 = width() - 1; y1 = height() - 1; z1 = depth() - 1; + disp.resize(cimg_fitscreen(_width,_height,_depth),false); oldw = disp._width; oldh = disp._height; + resize_disp = true; reset_view = false; } if (!x0 && !y0 && !z0 && x1==width() - 1 && y1==height() - 1 && z1==depth() - 1) { @@ -51379,10 +57801,11 @@ namespace cimg_library_suffixed { 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 float + ttw = (float)tw*disp.width()/oldw, tth = (float)th*disp.height()/oldh, + dM = std::max(ttw,tth), diM = (float)std::max(disp.width(),disp.height()); 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); + imgw = (unsigned int)(ttw*diM/dM), imgh = (unsigned int)(tth*diM/dM); disp.set_fullscreen(false).resize(cimg_fitscreen(imgw,imgh,1),false); resize_disp = false; } @@ -51422,7 +57845,7 @@ namespace cimg_library_suffixed { x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; x0+=sx0; y0+=sy0; z0+=sz0; if ((sx0==sx1 && sy0==sy1) || (_depth>1 && sx0==sx1 && sz0==sz1) || (_depth>1 && sy0==sy1 && sz0==sz1)) { - if (exit_on_simpleclick && (!zoom || is_empty())) break; else reset_view = true; + if (exit_on_singleclick && (!zoom || is_empty())) break; else reset_view = true; } resize_disp = true; } else switch (key = disp.key()) { @@ -51445,7 +57868,7 @@ namespace cimg_library_suffixed { (++_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(); } + if (disp.wheel()) { frame_timing-=disp.wheel()/3.f; disp.set_wheel(); } switch (key = disp.key()) { #if cimg_OS!=2 case cimg::keyCTRLRIGHT : @@ -51580,16 +58003,16 @@ namespace cimg_library_suffixed { return *this; } - //! Display object 3d in an interactive window. + //! 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 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 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. @@ -51597,8 +58020,8 @@ namespace cimg_library_suffixed { \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). + \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). \param exit_on_anykey Exit function when any key is pressed. **/ template @@ -51615,12 +58038,12 @@ namespace cimg_library_suffixed { 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, + render_motion,is_double_sided,focale, light_x,light_y,light_z,specular_lightness,specular_shininess, - display_axes,pose_matrix,exit_on_anykey); + display_axes,pose_matrix,exit_on_anykey); } - //! Display object 3d in an interactive window \simplification. + //! Display object 3D in an interactive window \simplification. template const CImg& display_object3d(const char *const title, const CImg& vertices, @@ -51636,12 +58059,12 @@ namespace cimg_library_suffixed { 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, + render_motion,is_double_sided,focale, light_x,light_y,light_z,specular_lightness,specular_shininess, - display_axes,pose_matrix,exit_on_anykey); + display_axes,pose_matrix,exit_on_anykey); } - //! Display object 3d in an interactive window \simplification. + //! Display object 3D in an interactive window \simplification. template const CImg& display_object3d(CImgDisplay &disp, const CImg& vertices, @@ -51655,15 +58078,15 @@ namespace cimg_library_suffixed { 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(),centering, - render_static,render_motion,is_double_sided,focale, + 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_axes,pose_matrix,exit_on_anykey); } - //! Display object 3d in an interactive window \simplification. + //! Display object 3D in an interactive window \simplification. template const CImg& display_object3d(const char *const title, - const CImg& vertices, + const CImg& vertices, const CImgList& primitives, const CImgList& colors, const bool centering=true, @@ -51679,7 +58102,7 @@ namespace cimg_library_suffixed { display_axes,pose_matrix,exit_on_anykey); } - //! Display object 3d in an interactive window \simplification. + //! Display object 3D in an interactive window \simplification. template const CImg& display_object3d(CImgDisplay &disp, const CImg& vertices, @@ -51698,10 +58121,10 @@ namespace cimg_library_suffixed { } - //! Display object 3d in an interactive window \simplification. + //! Display object 3D in an interactive window \simplification. template const CImg& display_object3d(const char *const title, - const CImg& vertices, + const CImg& vertices, const CImgList& primitives, const bool centering=true, const int render_static=4, const int render_motion=1, @@ -51716,7 +58139,7 @@ namespace cimg_library_suffixed { display_axes,pose_matrix,exit_on_anykey); } - //! Display object 3d in an interactive window \simplification. + //! Display object 3D in an interactive window \simplification. template const CImg& display_object3d(CImgDisplay &disp, const CImg& vertices, @@ -51733,10 +58156,10 @@ namespace cimg_library_suffixed { display_axes,pose_matrix,exit_on_anykey); } - //! Display object 3d in an interactive window \simplification. + //! Display object 3D in an interactive window \simplification. template const CImg& display_object3d(const char *const title, - const CImg& vertices, + const CImg& 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, @@ -51752,54 +58175,52 @@ namespace cimg_library_suffixed { template const CImg& _display_object3d(CImgDisplay& disp, const char *const title, - const CImg& vertices, - const CImgList& primitives, - const CImgList& colors, + const CImg& vertices, + const CImgList& primitives, + const CImgList& colors, const to& opacities, - const bool centering, - const int render_static, const int render_motion, - const bool is_double_sided, const float focale, + 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 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::type tpfloat; // Check input arguments if (is_empty()) { - if (disp) return CImg(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(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); + CImg background; + if (colors && colors[0].size()==1) background.assign(1,2,1,1,64,128); + else background.assign(1,2,1,3,32,64,32,116,64,96); + if (disp) background.resize(disp.width(),disp.height(),1,-100,3); + else background.resize(cimg_fitscreen(CImgDisplay::screen_width()/2, + CImgDisplay::screen_height()/2,1),1,-100,3); + return background._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 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).", + "display_object3d(): Invalid specified 3D object (%u,%u) (%s).", cimg_instance,vertices._width,primitives._width,error_message.data()); if (vertices._width && !primitives) { CImgList 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, + 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_axes,pose_matrix,exit_on_anykey); } if (!disp) { - disp.assign(cimg_fitscreen(_width,_height,_depth),title?title:0,3); + 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 + // Init 3D objects and compute object statistics CImg pose, rotated_vertices(vertices._width,3), @@ -51814,7 +58235,7 @@ namespace cimg_library_suffixed { bool ndisplay_axes = display_axes; const CImg background_color(1,1,1,_spectrum,0), - foreground_color(1,1,1,_spectrum,255); + foreground_color(1,1,1,_spectrum,(T)std::min((int)cimg::type::max(),255)); float Xoff = 0, Yoff = 0, Zoff = 0, sprite_scale = 1, xm = 0, xM = vertices?vertices.get_shared_row(0).max_min(xm):0, @@ -51843,7 +58264,7 @@ namespace cimg_library_suffixed { CImg visu0(*this,false), visu; CImg zbuffer(visu0.width(),visu0.height(),1,1,0); bool init_pose = true, clicked = false, redraw = true; - unsigned int key = 0; + unsigned int key = 0, font_size = 32; int x0 = 0, y0 = 0, x1 = 0, y1 = 0, nrender_static = render_static, @@ -51855,7 +58276,7 @@ namespace cimg_library_suffixed { // Init object pose if (init_pose) { const float - ratio = delta>0?(2.0f*std::min(disp.width(),disp.height())/(3.0f*delta)):1, + ratio = delta>0?(2.f*std::min(disp.width(),disp.height())/(3.f*delta)):1, dx = (xM + xm)/2, dy = (yM + ym)/2, dz = (zM + zm)/2; if (centering) CImg(4,3,1,1, ratio,0.,0.,-ratio*dx, 0.,ratio,0.,-ratio*dy, 0.,0.,ratio,-ratio*dz).move_to(pose); @@ -51871,19 +58292,26 @@ namespace cimg_library_suffixed { redraw = true; } - // Rotate and draw 3d object + // 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)) + if ((clicked && nrender_motion>=0) || (!clicked && nrender_static>=0)) { + const tp *const pv0 = vertices.data(), *const pv1 = vertices.data(0,1), *const pv2 = vertices.data(0,2); + float + *const prv0 = rotated_vertices.data(), + *const prv1 = rotated_vertices.data(0,1), + *const prv2 = rotated_vertices.data(0,2); + cimg_pragma_openmp(parallel for cimg_openmp_if(vertices.width()>(cimg_openmp_sizefactor)*1024)) 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; + const float x = (float)pv0[l], y = (float)pv1[l], z = (float)pv2[l]; + prv0[l] = r00*x + r10*y + r20*z + r30; + prv1[l] = r01*x + r11*y + r21*z + r31; + prv2[l] = 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; @@ -51895,16 +58323,16 @@ namespace cimg_library_suffixed { 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, + visu.draw_object3d(Xoff + visu._width/2.f,Yoff + visu._height/2.f,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, + draw_object3d(Xoff + visu._width/2.f,Yoff + visu._height/2.f,Zoff, rotated_bbox_vertices,bbox_primitives,bbox_colors2,1,false,focale); else visu._draw_object3d((void*)0,render_with_zbuffer?zbuffer.fill(0):CImg::empty(), - Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, + Xoff + visu._width/2.f,Yoff + visu._height/2.f,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); + width()/2.f + light_x,height()/2.f + light_y,light_z + Zoff, + specular_lightness,specular_shininess,1,sprite_scale); // Draw axes if (ndisplay_axes) { const float @@ -51912,7 +58340,7 @@ namespace cimg_library_suffixed { _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; + Xaxes = 25, Yaxes = visu._height - 38.f; cimg_forX(axes_vertices,l) { const float x = axes_vertices(l,0), @@ -51922,9 +58350,9 @@ namespace cimg_library_suffixed { 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; + axes_opacities(0,0) = (rotated_axes_vertices(1,2)>0)?0.5f:1.f; + axes_opacities(1,0) = (rotated_axes_vertices(2,2)>0)?0.5f:1.f; + axes_opacities(2,0) = (rotated_axes_vertices(3,2)>0)?0.5f:1.f; 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)), @@ -51942,12 +58370,13 @@ namespace cimg_library_suffixed { } // Handle user interaction - disp.wait(); + if (!redraw) 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 bool is_keyCTRL = disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT(); + if (disp.button()&1 && !is_keyCTRL) { const float R = 0.45f*std::min(disp.width(),disp.height()), R2 = R*R, @@ -51959,10 +58388,10 @@ namespace cimg_library_suffixed { 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)), + nw0 = (float)std::sqrt(std::max(0.f,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)), + nw1 = (float)std::sqrt(std::max(0.f,R2 - nu1*nu1 - nv1*nv1)), u = nv0*nw1 - nw0*nv1, v = nw0*nu1 - nu0*nw1, w = nv0*nu1 - nu0*nv1, @@ -51971,18 +58400,20 @@ namespace cimg_library_suffixed { (CImg::rotation_matrix(u,v,w,-alpha)*pose).move_to(pose); x0 = x1; y0 = y1; } - if (disp.button()&2) { + if (disp.button()&2 && !is_keyCTRL) { if (focale>0) Zoff-=(y0 - y1)*focale/400; - else { const float s = std::exp((y0 - y1)/400.0f); pose*=s; sprite_scale*=s; } + else { const float s = std::exp((y0 - y1)/400.f); 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; } + else { const float s = std::exp(disp.wheel()/20.f); 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)) { + if (disp.button()&4 || (disp.button()&1 && is_keyCTRL)) { + Xoff+=(x1 - x0); Yoff+=(y1 - y0); x0 = x1; y0 = y1; + } + if ((disp.button()&1) && (disp.button()&2) && !is_keyCTRL) { init_pose = true; disp.set_button(); x0 = x1; y0 = y1; pose = CImg(4,3,1,1, 1,0,0,0, 0,1,0,0, 0,0,1,0); } @@ -52036,23 +58467,23 @@ namespace cimg_library_suffixed { 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. + case cimg::keyX : 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. + 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. + 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. + 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. + 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; @@ -52061,7 +58492,7 @@ namespace cimg_library_suffixed { 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. + 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; @@ -52069,94 +58500,92 @@ namespace cimg_library_suffixed { 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); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u." +#ifdef cimg_use_png + "png", +#else + "bmp", +#endif + snap_number++); + if ((file=cimg::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).__draw_text(" Saving snapshot... ",font_size,0).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); + (+visu).__draw_text(" Snapshot '%s' saved. ",font_size,0,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); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.off",snap_number++); + if ((file=cimg::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); + (+visu).__draw_text(" Saving object... ",font_size,0).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); + (+visu).__draw_text(" Object '%s' saved. ",font_size,0,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++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimgz",snap_number++); #else - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimg",snap_number++); #endif - if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); + if ((file=cimg::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); + (+visu).__draw_text(" Saving object... ",font_size,0).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); + (+visu).__draw_text(" Object '%s' saved. ",font_size,0,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); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.eps",snap_number++); + if ((file=cimg::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); + (+visu).__draw_text(" Saving EPS snapshot... ",font_size,0).display(disp); LibBoard::Board board; (+visu)._draw_object3d(&board,zbuffer.fill(0), - Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, + Xoff + visu._width/2.f,Yoff + visu._height/2.f,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, + visu.width()/2.f + light_x,visu.height()/2.f + light_y,light_z + Zoff, + specular_lightness,specular_shininess,1, 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); + (+visu).__draw_text(" Object '%s' saved. ",font_size,0,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); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.svg",snap_number++); + if ((file=cimg::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); + (+visu).__draw_text(" Saving SVG snapshot... ",font_size,0).display(disp); LibBoard::Board board; (+visu)._draw_object3d(&board,zbuffer.fill(0), - Xoff + visu._width/2.0f,Yoff + visu._height/2.0f,Zoff, + Xoff + visu._width/2.f,Yoff + visu._height/2.f,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, + visu.width()/2.f + light_x,visu.height()/2.f + light_y,light_z + Zoff, + specular_lightness,specular_shininess,1, 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); + (+visu).__draw_text(" Object '%s' saved. ",font_size,0,filename._data).display(disp); disp.set_key(key,false); key = 0; } break; #endif @@ -52179,7 +58608,7 @@ namespace cimg_library_suffixed { return *this; } - //! Display 1d graph in an interactive window. + //! Display 1D graph in an interactive window. /** \param disp Display window. \param plot_type Plot type. Can be { 0=points | 1=segments | 2=splines | 3=bars }. @@ -52200,7 +58629,7 @@ namespace cimg_library_suffixed { 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. + //! Display 1D graph in an interactive window \overloading. const CImg& 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, @@ -52237,11 +58666,11 @@ namespace cimg_library_suffixed { if (y0==y1) { --y0; ++y1; } const CImg selection = zoom.get_select_graph(disp,plot_type,vertex_type, - labelx, + 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(); + const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y(); if (selection[0]>=0) { if (selection[2]<0) reset_view = true; else { @@ -52286,7 +58715,7 @@ namespace cimg_library_suffixed { const double ysiz = y1 - y0, my = (mouse_y - 16)*ysiz/(disp.height() - 32), - cy = y1 - cimg::cut(my,0.0,ysiz); + cy = y1 - cimg::cut(my,0.,ysiz); y0 = cy - 7*(cy - y0)/8; y1 = cy + 7*(y1 - cy)/8; } else y0 = y1 = 0; } @@ -52383,16 +58812,17 @@ namespace cimg_library_suffixed { #ifdef cimg_save_plugin8 cimg_save_plugin8(fn); #endif - // Ascii formats + // Text formats if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn); - else if (!cimg::strcasecmp(ext,"dlm") || + else if (!cimg::strcasecmp(ext,"csv") || + !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 + // 2D binary formats else if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn); else if (!cimg::strcasecmp(ext,"jpg") || !cimg::strcasecmp(ext,"jpeg") || @@ -52411,9 +58841,15 @@ namespace cimg_library_suffixed { 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); + // 3D binary formats + else if (!*ext) { +#ifdef cimg_use_zlib + return save_cimg(fn,true); +#else + return save_cimg(fn,false); +#endif + } else if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true); + else if (!cimg::strcasecmp(ext,"cimg")) 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); @@ -52447,6 +58883,7 @@ namespace cimg_library_suffixed { !cimg::strcasecmp(ext,"qt") || !cimg::strcasecmp(ext,"rm") || !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"webm") || !cimg::strcasecmp(ext,"wmv") || !cimg::strcasecmp(ext,"xvid") || !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); @@ -52461,7 +58898,7 @@ namespace cimg_library_suffixed { return _save_ascii(0,filename); } - //! Save image as an ascii file \overloading. + //! Save image as an Ascii file \overloading. const CImg& save_ascii(std::FILE *const file) const { return _save_ascii(file,0); } @@ -52591,7 +59028,7 @@ namespace cimg_library_suffixed { std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); CImg header(54,1,1,1,0); - unsigned char align_buf[4] = { 0 }; + unsigned char align_buf[4] = {}; const unsigned int align = (4 - (3*_width)%4)%4, buf_size = (3*_width + align)*height(), @@ -52809,7 +59246,7 @@ namespace cimg_library_suffixed { cimg::warn(_cimg_instance "save_magick(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", cimg_instance, - filename,stmin,stmax); + stmin,stmax,filename); Magick::Image image(Magick::Geometry(_width,_height),"black"); image.type(Magick::TrueColorType); @@ -52884,7 +59321,7 @@ namespace cimg_library_suffixed { #else #if defined __GNUC__ - const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning. + 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 @@ -52909,7 +59346,7 @@ namespace cimg_library_suffixed { cimg::warn(_cimg_instance "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.", cimg_instance, - filename,stmin,stmax); + stmin,stmax,filename); // Setup PNG structures for write png_voidp user_error_ptr = 0; @@ -53233,9 +59670,9 @@ namespace cimg_library_suffixed { std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); const T *ptr = data(0,0,0,0); - if (!cimg::type::is_float() && sizeof(T)==1 && _depth<2) // Can be saved as regular PNM file. + if (!cimg::type::is_float() && sizeof(T)==1 && _depth<2) // Can be saved as regular PNM file _save_pnm(file,filename,0); - else if (!cimg::type::is_float() && sizeof(T)==1) { // Save as extended P5 file: Binary byte-valued 3d. + else if (!cimg::type::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 buf((unsigned int)buf_size); for (longT to_write = (longT)width()*height()*depth(); to_write>0; ) { @@ -53245,7 +59682,7 @@ namespace cimg_library_suffixed { cimg::fwrite(buf._data,N,nfile); to_write-=N; } - } else if (!cimg::type::is_float()) { // Save as P8: Binary int32-valued 3d. + } else if (!cimg::type::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 buf((unsigned int)buf_size); @@ -53256,7 +59693,7 @@ namespace cimg_library_suffixed { cimg::fwrite(buf._data,N,nfile); to_write-=N; } - } else { // Save as P9: Binary float-valued 3d. + } 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 buf((unsigned int)buf_size); @@ -53503,19 +59940,20 @@ namespace cimg_library_suffixed { /** \param filename Filename, as a C-string. \param compression_type Type of data compression. Can be { 0=None | 1=LZW | 2=JPEG }. - \param voxel_size Voxel size, to be stored in the filename. - \param description Description, to be stored in the filename. + \param[out] voxel_size Voxel size, to be stored in the filename. + \param[out] description Description, to be stored in the filename. \param use_bigtiff Allow to save big tiff files (>4Gb). \note - libtiff support is enabled by defining the precompilation - directive \c cimg_use_tif. + directive \c cimg_use_tiff. - When libtiff is enabled, 2D and 3D (multipage) several channel per pixel are supported for char,uchar,short,ushort,float and \c double pixel types. - - If \c cimg_use_tif is not defined at compile time the + - If \c cimg_use_tiff is not defined at compile time the function uses CImg&save_other(const char*). **/ const CImg& 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) @@ -53526,7 +59964,7 @@ namespace cimg_library_suffixed { #ifdef cimg_use_tiff const bool - _use_bigtiff = use_bigtiff && sizeof(ulongT)>=8 && size()*sizeof(T)>=1UL<<31; // No bigtiff for small images. + _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); @@ -53544,8 +59982,8 @@ namespace cimg_library_suffixed { #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); } +#define _cimg_save_tiff(types,typed) 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 @@ -53554,8 +59992,8 @@ namespace cimg_library_suffixed { 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; + cimg_uint32 rowsperstrip = (cimg_uint32)-1; + cimg_uint16 spp = _spectrum, bpp = sizeof(t)*8, photometric; if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB; else photometric = PHOTOMETRIC_MINISBLACK; TIFFSetDirectory(tif,directory); @@ -53564,8 +60002,8 @@ namespace cimg_library_suffixed { 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); + TIFFSetField(tif,TIFFTAG_XRESOLUTION,1.f/vx); + TIFFSetField(tif,TIFFTAG_YRESOLUTION,1.f/vy); CImg 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()); @@ -53587,12 +60025,12 @@ namespace cimg_library_suffixed { rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip); TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip); TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB); - TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg"); + TIFFSetField(tif,TIFFTAG_SOFTWARE,cimg_appname); 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); + cimg_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& _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); + _cimg_save_tiff("uint8",cimg_uint8); + _cimg_save_tiff("int8",cimg_int8); + _cimg_save_tiff("uint16",cimg_uint16); + _cimg_save_tiff("int16",cimg_int16); + _cimg_save_tiff("uint32",cimg_uint32); + _cimg_save_tiff("int32",cimg_int32); + _cimg_save_tiff("uint64",cimg_uint32); // 'int64' as 'int32' + _cimg_save_tiff("int64",cimg_int32); + _cimg_save_tiff("float32",cimg_float32); + _cimg_save_tiff("float64",cimg_float32); // 'float64' as 'float32' const char *const filename = TIFFFileName(tif); throw CImgInstanceException(_cimg_instance "save_tiff(): Unsupported pixel type '%s' for file '%s'.", @@ -53662,11 +60099,11 @@ namespace cimg_library_suffixed { 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 (cimg::type::string()==cimg::type::string()) + if (pixel_type()==cimg::type::string()) wtr.setup_write_byte(); - else if (cimg::type::string()==cimg::type::string()) + else if (pixel_type()==cimg::type::string()) wtr.setup_write_int(); - else if (cimg::type::string()==cimg::type::string()) + else if (pixel_type()==cimg::type::string()) wtr.setup_write_double(); else wtr.setup_write_float(); @@ -53698,12 +60135,12 @@ namespace cimg_library_suffixed { 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"); + cimg_snprintf(iname._data + std::strlen(iname) - 3,4,"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"); + cimg_snprintf(hname._data + std::strlen(iname) - 3,4,"hdr"); } if (!cimg::strncasecmp(ext,"nii",3)) { std::strncpy(hname,filename,hname._width - 1); *iname = 0; @@ -53721,17 +60158,17 @@ namespace cimg_library_suffixed { ((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 (!cimg::strcasecmp(pixel_type(),"bool") || + !cimg::strcasecmp(pixel_type(),"uint8") || + !cimg::strcasecmp(pixel_type(),"int8")) datatype = 2; + if (!cimg::strcasecmp(pixel_type(),"uint16") || + !cimg::strcasecmp(pixel_type(),"int16")) datatype = 4; + if (!cimg::strcasecmp(pixel_type(),"uint32") || + !cimg::strcasecmp(pixel_type(),"int32")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"uint64") || + !cimg::strcasecmp(pixel_type(),"int64")) datatype = 8; + if (!cimg::strcasecmp(pixel_type(),"float32")) datatype = 16; + if (!cimg::strcasecmp(pixel_type(),"float64")) datatype = 64; if (datatype<0) throw CImgIOException(_cimg_instance "save_analyze(): Unsupported pixel type '%s' for file '%s'.", @@ -53791,9 +60228,9 @@ namespace cimg_library_suffixed { //! Save image as a sub-image into an existing .cimg file \overloading. const CImg& 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 { + const unsigned int n0, + const unsigned int x0, const unsigned int y0, + const unsigned int z0, const unsigned int c0) const { CImgList(*this,true).save_cimg(file,n0,x0,y0,z0,c0); return *this; } @@ -53807,7 +60244,7 @@ namespace cimg_library_suffixed { \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. + - Use this method to save large images without having to instantiate and allocate them. **/ static void save_empty_cimg(const char *const filename, const unsigned int dx, const unsigned int dy=1, @@ -53849,28 +60286,28 @@ namespace cimg_library_suffixed { int inrpixsize = -1; const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; - if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { + if (!cimg::strcasecmp(pixel_type(),"uint8")) { inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; } - if (!cimg::strcasecmp(pixel_type(),"char")) { + if (!cimg::strcasecmp(pixel_type(),"int8")) { inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; } - if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { + if (!cimg::strcasecmp(pixel_type(),"uint16")) { inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; } - if (!cimg::strcasecmp(pixel_type(),"short")) { + if (!cimg::strcasecmp(pixel_type(),"int16")) { inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; } - if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { + if (!cimg::strcasecmp(pixel_type(),"uint32")) { inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; } - if (!cimg::strcasecmp(pixel_type(),"int")) { + if (!cimg::strcasecmp(pixel_type(),"int32")) { inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; } - if (!cimg::strcasecmp(pixel_type(),"float")) { + if (!cimg::strcasecmp(pixel_type(),"float32")) { inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; } - if (!cimg::strcasecmp(pixel_type(),"double")) { + if (!cimg::strcasecmp(pixel_type(),"float64")) { inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; } if (inrpixsize<=0) @@ -53883,9 +60320,11 @@ namespace cimg_library_suffixed { CImg 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"); + if (voxel_size) + err+=cimg_snprintf(header._data + err,128,"VX=%g\nVY=%g\nVZ=%g\n", + voxel_size[0],voxel_size[1],voxel_size[2]); + err+=cimg_snprintf(header._data + err,128,"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); @@ -53916,14 +60355,16 @@ namespace cimg_library_suffixed { #else Imf::Rgba *const ptrd0 = new Imf::Rgba[(size_t)_width*_height], *ptrd = ptrd0, rgba; switch (_spectrum) { - case 1 : { // Grayscale image. + case 1 : { // Grayscale image for (const T *ptr_r = data(), *const ptr_e = ptr_r + (ulongT)_width*_height; ptr_r ndims(5); \ - for (int d = 0; d<5; ++d) ndims[d] = (unsigned long)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + 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 ndims(5); \ - for (int d = 0; d<5; ++d) ndims[d] = (unsigned int)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + 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 ndims(5); \ - for (int d = 0; d<5; ++d) ndims[d] = (unsigned short)dims[d]; cimg::fwrite(ndims._data,nbdims,nfile); } \ + 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) { \ + 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) { \ + } 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); } \ @@ -54050,7 +60494,7 @@ namespace cimg_library_suffixed { "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) { \ + } 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 \ @@ -54058,7 +60502,7 @@ namespace cimg_library_suffixed { cimg_instance, \ filename?filename:"(FILE*)"); \ } \ - saved = true; \ + saved = true; \ } if (!file && !filename) @@ -54071,95 +60515,95 @@ namespace cimg_library_suffixed { 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 }; + unsigned int nbdims, dims[5] = {}; bool saved = false; - _cimg_save_pandore_case(1,1,1,"unsigned char",2); - _cimg_save_pandore_case(1,1,1,"char",3); - _cimg_save_pandore_case(1,1,1,"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,"uint8",2); + _cimg_save_pandore_case(1,1,1,"int8",3); + _cimg_save_pandore_case(1,1,1,"uint16",3); + _cimg_save_pandore_case(1,1,1,"int16",3); + _cimg_save_pandore_case(1,1,1,"uint32",3); + _cimg_save_pandore_case(1,1,1,"int32",3); + _cimg_save_pandore_case(1,1,1,"uint64",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(1,1,1,"float32",4); + _cimg_save_pandore_case(1,1,1,"float64",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,"uint8",5); + _cimg_save_pandore_case(0,1,1,"int8",6); + _cimg_save_pandore_case(0,1,1,"uint16",6); + _cimg_save_pandore_case(0,1,1,"int16",6); + _cimg_save_pandore_case(0,1,1,"uint32",6); + _cimg_save_pandore_case(0,1,1,"int32",6); + _cimg_save_pandore_case(0,1,1,"uint64",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,1,1,"float32",7); + _cimg_save_pandore_case(0,1,1,"float64",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,"uint8",8); + _cimg_save_pandore_case(0,0,1,"int8",9); + _cimg_save_pandore_case(0,0,1,"uint16",9); + _cimg_save_pandore_case(0,0,1,"int16",9); + _cimg_save_pandore_case(0,0,1,"uint32",9); + _cimg_save_pandore_case(0,0,1,"int32",9); + _cimg_save_pandore_case(0,0,1,"uint64",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,0,1,"float32",10); + _cimg_save_pandore_case(0,0,1,"float64",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,"uint8",16); + _cimg_save_pandore_case(0,1,3,"int8",17); + _cimg_save_pandore_case(0,1,3,"uint16",17); + _cimg_save_pandore_case(0,1,3,"int16",17); + _cimg_save_pandore_case(0,1,3,"uint32",17); + _cimg_save_pandore_case(0,1,3,"int32",17); + _cimg_save_pandore_case(0,1,3,"uint64",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,1,3,"float32",18); + _cimg_save_pandore_case(0,1,3,"float64",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,"uint8",19); + _cimg_save_pandore_case(0,0,3,"int8",20); + _cimg_save_pandore_case(0,0,3,"uint16",20); + _cimg_save_pandore_case(0,0,3,"int16",20); + _cimg_save_pandore_case(0,0,3,"uint32",20); + _cimg_save_pandore_case(0,0,3,"int32",20); + _cimg_save_pandore_case(0,0,3,"uint64",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(0,0,3,"float32",21); + _cimg_save_pandore_case(0,0,3,"float64",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,"uint8",22); + _cimg_save_pandore_case(1,1,0,"int8",23); + _cimg_save_pandore_case(1,1,0,"uint16",23); + _cimg_save_pandore_case(1,1,0,"int16",23); + _cimg_save_pandore_case(1,1,0,"uint32",23); + _cimg_save_pandore_case(1,1,0,"int32",23); + _cimg_save_pandore_case(1,1,0,"uint64",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(1,1,0,"float32",25); + _cimg_save_pandore_case(1,1,0,"float64",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,"uint8",26); + _cimg_save_pandore_case(0,1,0,"int8",27); + _cimg_save_pandore_case(0,1,0,"uint16",27); + _cimg_save_pandore_case(0,1,0,"int16",27); + _cimg_save_pandore_case(0,1,0,"uint32",27); + _cimg_save_pandore_case(0,1,0,"int32",27); + _cimg_save_pandore_case(0,1,0,"uint64",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,1,0,"float32",29); + _cimg_save_pandore_case(0,1,0,"float64",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,"uint8",30); + _cimg_save_pandore_case(0,0,0,"int8",31); + _cimg_save_pandore_case(0,0,0,"uint16",31); + _cimg_save_pandore_case(0,0,0,"int16",31); + _cimg_save_pandore_case(0,0,0,"uint32",31); + _cimg_save_pandore_case(0,0,0,"int32",31); + _cimg_save_pandore_case(0,0,0,"uint64",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); + _cimg_save_pandore_case(0,0,0,"float32",33); + _cimg_save_pandore_case(0,0,0,"float64",33); if (!file) cimg::fclose(nfile); return *this; @@ -54193,18 +60637,65 @@ namespace cimg_library_suffixed { 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 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 (pixel_type()==cimg::type::string()) { // Boolean data (bitwise) + ulongT siz; + const unsigned char *const buf = _bool2uchar(siz,is_multiplexed); + cimg::fwrite(buf,siz,nfile); + delete[] buf; + } else { // Non boolean data + if (!is_multiplexed || _spectrum==1) cimg::fwrite(_data,size(),nfile); // Non-multiplexed + else { // Multiplexed + CImg 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; } + // Return unsigned char buffer that encodes data of a CImg instance bitwise. + // (buffer needs to be deallocated afterwards, with delete[]). + const unsigned char *_bool2uchar(ulongT &siz, const bool is_multiplexed) const { + const ulongT _siz = size(); + siz = _siz/8 + (_siz%8?1:0); + unsigned char *const buf = new unsigned char[siz], *ptrd = buf, val = 0, bit = 0; + + if (!is_multiplexed || _spectrum==1) // Non-multiplexed + cimg_for(*this,ptrs,T) { (val<<=1)|=(*ptrs?1:0); if (++bit==8) { *(ptrd++) = val; val = bit = 0; }} + else // Multiplexed + cimg_forXYZ(*this,x,y,z) cimg_forC(*this,c) { + (val<<=1)|=((*this)(x,y,z,c)?1:0); if (++bit==8) { *(ptrd++) = val; val = bit = 0; } + } + if (bit) *ptrd = val; + return buf; + } + + // Fill CImg instance from bitwise data encoded in an unsigned char buffer. + void _uchar2bool(const unsigned char *buf, const ulongT siz, const bool is_multiplexed) { + const ulongT S = std::min(siz*8,size()); + const unsigned char *ptrs = buf; + unsigned char val = 0, mask = 0; + T *ptrd = _data; + if (S && (!is_multiplexed || _spectrum==1)) // Non-multiplexed + for (ulongT off = 0; off>=1)) { val = *(ptrs++); mask = 128; } + *(ptrd++) = (T)((val&mask)?1:0); + } + else if (S) { // Multiplexed + ulongT off = 0; + for (int z = 0; z>=1)) { val = *(ptrs++); ++off; mask = 128; } + (*this)(x,y,z,c) = (T)((val&mask)?1:0); + } + } + } + //! Save image as a .yuv video file. /** \param filename Filename, as a C-string. @@ -54231,13 +60722,13 @@ namespace cimg_library_suffixed { return *this; } - //! Save 3d object as an Object File Format (.off) file. + //! 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. + \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. + - 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 described here. @@ -54248,7 +60739,7 @@ namespace cimg_library_suffixed { return _save_off(primitives,colors,0,filename); } - //! Save 3d object as an Object File Format (.off) file \overloading. + //! Save 3D object as an Object File Format (.off) file \overloading. /** Same as save_off(const CImgList&,const CImgList&,const char*) const with a file stream argument instead of a filename string. @@ -54276,11 +60767,11 @@ namespace cimg_library_suffixed { CImg 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).", + "save_off(): Invalid specified 3D object, for file '%s' (%s).", cimg_instance, filename?filename:"(FILE*)",error_message.data()); - const CImg default_color(1,3,1,1,200); + const CImg default_color(1,3,1,1,(tc)std::min((int)cimg::type::max(),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; @@ -54290,7 +60781,7 @@ namespace cimg_library_suffixed { cimglist_for(primitives,l) { const CImg& color = l1?color[1]:r)/255.0f, b = (csiz>2?color[2]:g)/255.0f; + const float r = color[0]/255.f, g = (csiz>1?color[1]:r)/255.f, b = (csiz>2?color[2]:g)/255.f; switch (psiz) { case 1 : std::fprintf(nfile,"1 %u %f %f %f\n", (unsigned int)primitives(l,0),r,g,b); break; @@ -54307,18 +60798,18 @@ namespace cimg_library_suffixed { 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; + rt = color.atXY(xt,yt,0)/255.f, + gt = (csiz>1?color.atXY(xt,yt,1):r)/255.f, + bt = (csiz>2?color.atXY(xt,yt,2):g)/255.f; 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; + rt = color.atXY(xt,yt,0)/255.f, + gt = (csiz>1?color.atXY(xt,yt,1):r)/255.f, + bt = (csiz>2?color.atXY(xt,yt,2):g)/255.f; 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); @@ -54326,9 +60817,9 @@ namespace cimg_library_suffixed { 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; + rt = color.atXY(xt,yt,0)/255.f, + gt = (csiz>1?color.atXY(xt,yt,1):r)/255.f, + bt = (csiz>2?color.atXY(xt,yt,2):g)/255.f; 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); @@ -54339,7 +60830,7 @@ namespace cimg_library_suffixed { return *this; } - //! Save volumetric image as a video, using the OpenCV library. + //! Save volumetric image as a video (using the OpenCV library when available). /** \param filename Filename to write data to. \param fps Number of frames per second. @@ -54413,15 +60904,15 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); } while (file); save(filename_tmp); - cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"", + cimg_snprintf(command,command._width,"\"%s\" -c \"%s\" > \"%s\"", cimg::gzip_path(), CImg::string(filename_tmp)._system_strescape().data(), CImg::string(filename)._system_strescape().data()); - cimg::system(command); - file = std_fopen(filename,"rb"); + cimg::system(command,cimg::gzip_path()); + file = cimg::std_fopen(filename,"rb"); if (!file) throw CImgIOException(_cimg_instance "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", @@ -54454,31 +60945,32 @@ namespace cimg_library_suffixed { cimg_instance,filename); #ifdef cimg_use_png -#define _cimg_sge_ext1 "png" -#define _cimg_sge_ext2 "png" +#define _cimg_sge_extension1 "png" +#define _cimg_sge_extension2 "png" #else -#define _cimg_sge_ext1 "pgm" -#define _cimg_sge_ext2 "ppm" +#define _cimg_sge_extension1 "pgm" +#define _cimg_sge_extension2 "ppm" #endif CImg 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); + _spectrum==1?_cimg_sge_extension1:_cimg_sge_extension2); + if ((file=cimg::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_snprintf(command,command._width,"\"%s\" convert -quality %u \"%s\" \"%s\"", cimg::graphicsmagick_path(),quality, CImg::string(filename_tmp)._system_strescape().data(), CImg::string(filename)._system_strescape().data()); - cimg::system(command); - file = std_fopen(filename,"rb"); + cimg::system(command,cimg::graphicsmagick_path()); + file = cimg::std_fopen(filename,"rb"); if (!file) throw CImgIOException(_cimg_instance "save_graphicsmagick_external(): Failed to save file '%s' with external command 'gm'.", @@ -54510,30 +61002,30 @@ namespace cimg_library_suffixed { "ImageMagick only writes the first image slice.", cimg_instance,filename); #ifdef cimg_use_png -#define _cimg_sie_ext1 "png" -#define _cimg_sie_ext2 "png" +#define _cimg_sie_extension1 "png" +#define _cimg_sie_extension2 "png" #else -#define _cimg_sie_ext1 "pgm" -#define _cimg_sie_ext2 "ppm" +#define _cimg_sie_extension1 "pgm" +#define _cimg_sie_extension2 "ppm" #endif CImg 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); + cimg_file_separator,cimg::filenamerand(),_spectrum==1?_cimg_sie_extension1:_cimg_sie_extension2); + if ((file=cimg::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_snprintf(command,command._width,"\"%s\" -quality %u \"%s\" \"%s\"", cimg::imagemagick_path(),quality, CImg::string(filename_tmp)._system_strescape().data(), CImg::string(filename)._system_strescape().data()); - cimg::system(command); - file = std_fopen(filename,"rb"); + cimg::system(command,cimg::imagemagick_path()); + file = cimg::std_fopen(filename,"rb"); if (!file) throw CImgIOException(_cimg_instance "save_imagemagick_external(): Failed to save file '%s' with " @@ -54564,23 +61056,23 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::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_snprintf(command,command._width,"\"%s\" -w -c dicom -o \"%s\" -f \"%s\"", cimg::medcon_path(), CImg::string(filename)._system_strescape().data(), CImg::string(filename_tmp)._system_strescape().data()); - cimg::system(command); + cimg::system(command,cimg::medcon_path()); 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"); + file = cimg::std_fopen(filename,"rb"); if (!file) { cimg_snprintf(command,command._width,"m000-%s",filename); - file = std_fopen(command,"rb"); + file = cimg::std_fopen(command,"rb"); if (!file) { cimg::fclose(cimg::fopen(filename,"r")); throw CImgIOException(_cimg_instance @@ -54646,9 +61138,10 @@ namespace cimg_library_suffixed { /** \param is_compressed tells if zlib compression must be used for serialization (this requires 'cimg_use_zlib' been enabled). + \param header_size Reserve empty bytes as a starting header. **/ - CImg get_serialize(const bool is_compressed=false) const { - return CImgList(*this,true).get_serialize(is_compressed); + CImg get_serialize(const bool is_compressed=false, const unsigned int header_size=0) const { + return CImgList(*this,true).get_serialize(is_compressed,header_size); } // [internal] Return a 40x38 color logo of a 'danger' item. @@ -54664,7 +61157,7 @@ namespace cimg_library_suffixed { } //@} - }; + }; // struct CImg { ... /* #----------------------------------------- @@ -54689,7 +61182,7 @@ namespace cimg_library_suffixed { - The \c CImgList::iterator type is defined as a CImg*. - You may use it like this: \code - CImgList<> list; // Assuming this image list is not empty. + CImgList<> list; // Assuming this image list is not empty for (CImgList<>::iterator it = list.begin(); it_data; \ - va_list ap; \ - va_start(ap,val1); \ - for (ulongT l = 0, s = 0, i = 0; i_data; \ + va_list ap; \ + va_start(ap,val1); \ + for (ulongT l = 0, s = 0, i = 0; i& operator()(const unsigned int pos) { #if cimg_verbosity>=3 @@ -55430,7 +61923,7 @@ namespace cimg_library_suffixed { //! Return a reference to one image of the list. /** - \param pos Indice of the image element. + \param pos Index of the image element. **/ const CImg& operator()(const unsigned int pos) const { return const_cast*>(this)->operator()(pos); @@ -55438,7 +61931,7 @@ namespace cimg_library_suffixed { //! Return a reference to one pixel value of one image of the list. /** - \param pos Indice of the image element. + \param pos Index 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. @@ -55583,7 +62076,7 @@ namespace cimg_library_suffixed { 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"). + - The returned string does not contain any spaces. - If the pixel type \c T does not correspond to a registered type, the string "unknown" is returned. **/ static const char* pixel_type() { @@ -55621,7 +62114,7 @@ namespace cimg_library_suffixed { //! Return pointer to the pos-th image of the list. /** - \param pos Indice of the image element to access. + \param pos Index of the image element to access. \note list.data(n); is equivalent to list.data + n;. **/ #if cimg_verbosity>=3 @@ -55698,7 +62191,7 @@ namespace cimg_library_suffixed { //! Return pos-th image of the list. /** - \param pos Indice of the image element to access. + \param pos Index of the image element to access. **/ CImg& at(const int pos) { if (is_empty()) @@ -55711,7 +62204,7 @@ namespace cimg_library_suffixed { //! Access to pixel value with Dirichlet boundary conditions. /** - \param pos Indice of the image element to access. + \param pos Index 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. @@ -55720,17 +62213,17 @@ namespace cimg_library_suffixed { \note list.atNXYZC(p,x,y,z,c); is equivalent to list[p].atXYZC(x,y,z,c);. **/ 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); + return (pos<0 || pos>=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); + return (pos<0 || pos>=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 pos Index 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. @@ -55766,7 +62259,7 @@ namespace cimg_library_suffixed { //! 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 pos Index 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. @@ -55775,17 +62268,17 @@ namespace cimg_library_suffixed { \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. **/ 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); + return (pos<0 || pos>=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); + return (pos<0 || pos>=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 pos Index 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. @@ -55821,7 +62314,7 @@ namespace cimg_library_suffixed { //! 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 pos Index 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. @@ -55830,17 +62323,17 @@ namespace cimg_library_suffixed { \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. **/ 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); + return (pos<0 || pos>=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); + return (pos<0 || pos>=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 pos Index 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. @@ -55876,7 +62369,7 @@ namespace cimg_library_suffixed { //! 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 pos Index 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. @@ -55885,17 +62378,17 @@ namespace cimg_library_suffixed { \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. **/ 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); + return (pos<0 || pos>=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); + return (pos<0 || pos>=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 pos Index 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. @@ -55931,7 +62424,7 @@ namespace cimg_library_suffixed { //! Access to pixel value with Dirichlet boundary conditions for the coordinate (\c pos). /** - \param pos Indice of the image element to access. + \param pos Index 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. @@ -55940,17 +62433,17 @@ namespace cimg_library_suffixed { \note list.atNXYZ(p,x,y,z,c); is equivalent to list[p].atXYZ(x,y,z,c);. **/ 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); + return (pos<0 || pos>=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 (pos<0 || pos>=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 pos Index 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. @@ -56018,7 +62511,8 @@ namespace cimg_library_suffixed { #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; \ + 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); \ @@ -56027,7 +62521,8 @@ namespace cimg_library_suffixed { #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; \ + 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); \ @@ -56047,11 +62542,15 @@ namespace cimg_library_suffixed { #define _cimglist_def_is_same(axis) \ template bool is_same##axis(const CImg& img) const { \ - bool res = true; for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(img); return res; \ + bool res = true; \ + for (unsigned int l = 0; l<_width && res; ++l) res = _data[l].is_same##axis(img); \ + return res; \ } \ template bool is_same##axis(const CImgList& list) const { \ const unsigned int lmin = std::min(_width,list._width); \ - bool res = true; for (unsigned int l = 0; l bool is_sameN##axis(const unsigned int n, const CImg& img) const { \ return (is_sameN(n) && is_same##axis(img)); \ @@ -56122,17 +62621,17 @@ namespace cimg_library_suffixed { **/ 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() && + return n>=0 && n=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. + //! Test if list contains image with specified index. /** \param n Index of the checked image. **/ bool containsN(const int n) const { if (is_empty()) return false; - return n>=0 && n<(int)_width; + return n>=0 && n_data; T min_value = *ptr_min; cimglist_for(*this,l) { const CImg& img = _data[l]; @@ -56264,11 +62770,18 @@ namespace cimg_library_suffixed { //! Return a reference to the minimum pixel value of the instance list \const. const T& min() const { - if (is_empty()) + bool is_all_empty = true; + T *ptr_min = 0; + cimglist_for(*this,l) if (!_data[l].is_empty()) { + ptr_min = _data[l]._data; + is_all_empty = false; + break; + } + if (is_all_empty) throw CImgInstanceException(_cimglist_instance - "min(): Empty instance.", + "min(): %s.", + _data?"List of empty images":"Empty instance", cimglist_instance); - const T *ptr_min = _data->_data; T min_value = *ptr_min; cimglist_for(*this,l) { const CImg& img = _data[l]; @@ -56281,11 +62794,18 @@ namespace cimg_library_suffixed { /** **/ T& max() { - if (is_empty()) + bool is_all_empty = true; + T *ptr_max = 0; + cimglist_for(*this,l) if (!_data[l].is_empty()) { + ptr_max = _data[l]._data; + is_all_empty = false; + break; + } + if (is_all_empty) throw CImgInstanceException(_cimglist_instance - "max(): Empty instance.", + "max(): %s.", + _data?"List of empty images":"Empty instance", cimglist_instance); - T *ptr_max = _data->_data; T max_value = *ptr_max; cimglist_for(*this,l) { const CImg& img = _data[l]; @@ -56296,11 +62816,18 @@ namespace cimg_library_suffixed { //! Return a reference to the maximum pixel value of the instance list \const. const T& max() const { - if (is_empty()) + bool is_all_empty = true; + T *ptr_max = 0; + cimglist_for(*this,l) if (!_data[l].is_empty()) { + ptr_max = _data[l]._data; + is_all_empty = false; + break; + } + if (is_all_empty) throw CImgInstanceException(_cimglist_instance - "max(): Empty instance.", + "max(): %s.", + _data?"List of empty images":"Empty instance", cimglist_instance); - const T *ptr_max = _data->_data; T max_value = *ptr_max; cimglist_for(*this,l) { const CImg& img = _data[l]; @@ -56315,11 +62842,18 @@ namespace cimg_library_suffixed { **/ template T& min_max(t& max_val) { - if (is_empty()) + bool is_all_empty = true; + T *ptr_min = 0; + cimglist_for(*this,l) if (!_data[l].is_empty()) { + ptr_min = _data[l]._data; + is_all_empty = false; + break; + } + if (is_all_empty) throw CImgInstanceException(_cimglist_instance - "min_max(): Empty instance.", + "min_max(): %s.", + _data?"List of empty images":"Empty instance", cimglist_instance); - T *ptr_min = _data->_data; T min_value = *ptr_min, max_value = min_value; cimglist_for(*this,l) { const CImg& img = _data[l]; @@ -56339,11 +62873,18 @@ namespace cimg_library_suffixed { **/ template const T& min_max(t& max_val) const { - if (is_empty()) + bool is_all_empty = true; + T *ptr_min = 0; + cimglist_for(*this,l) if (!_data[l].is_empty()) { + ptr_min = _data[l]._data; + is_all_empty = false; + break; + } + if (is_all_empty) throw CImgInstanceException(_cimglist_instance - "min_max(): Empty instance.", + "min_max(): %s.", + _data?"List of empty images":"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& img = _data[l]; @@ -56363,11 +62904,18 @@ namespace cimg_library_suffixed { **/ template T& max_min(t& min_val) { - if (is_empty()) + bool is_all_empty = true; + T *ptr_max = 0; + cimglist_for(*this,l) if (!_data[l].is_empty()) { + ptr_max = _data[l]._data; + is_all_empty = false; + break; + } + if (is_all_empty) throw CImgInstanceException(_cimglist_instance - "max_min(): Empty instance.", + "max_min(): %s.", + _data?"List of empty images":"Empty instance", cimglist_instance); - T *ptr_max = _data->_data; T min_value = *ptr_max, max_value = min_value; cimglist_for(*this,l) { const CImg& img = _data[l]; @@ -56384,11 +62932,18 @@ namespace cimg_library_suffixed { //! Return a reference to the minimum pixel value of the instance list and return the minimum value as well \const. template const T& max_min(t& min_val) const { - if (is_empty()) + bool is_all_empty = true; + T *ptr_max = 0; + cimglist_for(*this,l) if (!_data[l].is_empty()) { + ptr_max = _data[l]._data; + is_all_empty = false; + break; + } + if (is_all_empty) throw CImgInstanceException(_cimglist_instance - "max_min(): Empty instance.", + "max_min(): %s.", + _data?"List of empty images":"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& img = _data[l]; @@ -56433,18 +62988,18 @@ namespace cimg_library_suffixed { CImg *const new_data = (++_width>_allocated_width)?new CImg[_allocated_width?(_allocated_width<<=1): (_allocated_width=16)]:0; - if (!_data) { // Insert new element into empty list. + if (!_data) { // Insert new element into empty list _data = new_data; *_data = img; } else { - if (new_data) { // Insert with re-allocation. + if (new_data) { // Insert with re-allocation if (npos) std::memcpy((void*)new_data,(void*)_data,sizeof(CImg)*npos); if (npos!=_width - 1) std::memcpy((void*)(new_data + npos + 1),(void*)(_data + npos),sizeof(CImg)*(_width - 1 - npos)); std::memset((void*)_data,0,sizeof(CImg)*(_width - 1)); delete[] _data; _data = new_data; - } else if (npos!=_width - 1) // Insert without re-allocation. + } else if (npos!=_width - 1) // Insert without re-allocation std::memmove((void*)(_data + npos + 1),(void*)(_data + npos),sizeof(CImg)*(_width - 1 - npos)); _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0; _data[npos]._data = 0; @@ -56464,7 +63019,7 @@ namespace cimg_library_suffixed { img._width,img._height,img._depth,img._spectrum,img._data,npos); CImg *const new_data = (++_width>_allocated_width)?new CImg[_allocated_width?(_allocated_width<<=1): (_allocated_width=16)]:0; - if (!_data) { // Insert new element into empty list. + if (!_data) { // Insert new element into empty list _data = new_data; if (is_shared && img) { _data->_width = img._width; @@ -56476,7 +63031,7 @@ namespace cimg_library_suffixed { } else *_data = img; } else { - if (new_data) { // Insert with re-allocation. + if (new_data) { // Insert with re-allocation if (npos) std::memcpy((void*)new_data,(void*)_data,sizeof(CImg)*npos); if (npos!=_width - 1) std::memcpy((void*)(new_data + npos + 1),(void*)(_data + npos),sizeof(CImg)*(_width - 1 - npos)); @@ -56495,7 +63050,7 @@ namespace cimg_library_suffixed { std::memset((void*)_data,0,sizeof(CImg)*(_width - 1)); delete[] _data; _data = new_data; - } else { // Insert without re-allocation. + } else { // Insert without re-allocation if (npos!=_width - 1) std::memmove((void*)(_data + npos + 1),(void*)(_data + npos),sizeof(CImg)*(_width - 1 - npos)); if (is_shared && img) { @@ -56631,12 +63186,12 @@ namespace cimg_library_suffixed { 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 (_width>(_allocated_width>>4) || _allocated_width<=16) { // Removing items without reallocation if (npos1!=_width) std::memmove((void*)(_data + npos1),(void*)(_data + npos2 + 1),sizeof(CImg)*(_width - npos1)); std::memset((void*)(_data + _width),0,sizeof(CImg)*nb); - } else { // Removing items with reallocation. - _allocated_width>>=2; + } else { // Removing items with reallocation + _allocated_width>>=4; while (_allocated_width>16 && _width<(_allocated_width>>1)) _allocated_width>>=1; CImg *const new_data = new CImg[_allocated_width]; if (npos1) std::memcpy((void*)new_data,(void*)_data,sizeof(CImg)*npos1); @@ -56753,7 +63308,7 @@ namespace cimg_library_suffixed { unsigned int dx = 0, dy = 0, dz = 0, dc = 0, pos = 0; CImg res; switch (cimg::lowercase(axis)) { - case 'x' : { // Along the X-axis. + case 'x' : { // Along the X-axis cimglist_for(*this,l) { const CImg& img = (*this)[l]; if (img) { @@ -56766,15 +63321,21 @@ namespace cimg_library_suffixed { res.assign(dx,dy,dz,dc,(T)0); if (res) cimglist_for(*this,l) { const CImg& 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); + if (img) { + if (img._height==1 && img._depth==1 && img._spectrum==1 && + res._height==1 && res._depth==1 && res._spectrum==1) + std::memcpy(&res[pos],img._data,sizeof(T)*img._width); + else + 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. + case 'y' : { // Along the Y-axis cimglist_for(*this,l) { const CImg& img = (*this)[l]; if (img) { @@ -56787,15 +63348,21 @@ namespace cimg_library_suffixed { res.assign(dx,dy,dz,dc,(T)0); if (res) cimglist_for(*this,l) { const CImg& 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); + if (img) { + if (img._width==1 && img._depth==1 && img._spectrum==1 && + res._width==1 && res._depth==1 && res._spectrum==1) + std::memcpy(&res[pos],img._data,sizeof(T)*img._height); + else + 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. + case 'z' : { // Along the Z-axis cimglist_for(*this,l) { const CImg& img = (*this)[l]; if (img) { @@ -56808,15 +63375,21 @@ namespace cimg_library_suffixed { res.assign(dx,dy,dz,dc,(T)0); if (res) cimglist_for(*this,l) { const CImg& 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); + if (img) { + if (img._width==1 && img._height==1 && img._spectrum==1 && + res._width==1 && res._height==1 && res._spectrum==1) + std::memcpy(&res[pos],img._data,sizeof(T)*img._depth); + else + 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. + default : { // Along the C-axis cimglist_for(*this,l) { const CImg& img = (*this)[l]; if (img) { @@ -56829,11 +63402,17 @@ namespace cimg_library_suffixed { res.assign(dx,dy,dz,dc,(T)0); if (res) cimglist_for(*this,l) { const CImg& 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); + if (img) { + if (img._width==1 && img._height==1 && img._depth==1 && + res._width==1 && res._height==1 && res._depth==1) + std::memcpy(&res[pos],img._data,sizeof(T)*img._spectrum); + else + res.draw_image((int)(align*(dx - img._width)), + (int)(align*(dy - img._height)), + (int)(align*(dz - img._depth)), + pos, + img); + } pos+=img._spectrum; } } @@ -56844,7 +63423,7 @@ namespace cimg_library_suffixed { //! 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. + \param nb Number of split parts for each image. **/ CImgList& split(const char axis, const int nb=-1) { return get_split(axis,nb).move_to(*this); @@ -56983,7 +63562,10 @@ namespace cimg_library_suffixed { 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); + } else { + if (title) disp.set_title("%s",title); + disp.move_inside_screen(); + } 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); @@ -56992,16 +63574,16 @@ namespace cimg_library_suffixed { const unsigned int old_normalization = disp.normalization(); bool old_is_resized = disp.is_resized(); disp._normalization = 0; - disp.show().set_key(0); + disp.show().set_key(0).show_mouse(); static const unsigned char foreground_color[] = { 255,255,255 }, background_color[] = { 0,0,0 }; // Enter event loop. CImg visu0, visu; CImg indices; CImg positions(_width,4,1,1,-1); - int oindice0 = -1, oindice1 = -1, indice0 = -1, indice1 = -1; + int oindex0 = -1, oindex1 = -1, index0 = -1, index1 = -1; bool is_clicked = false, is_selected = false, text_down = false, update_display = true; - unsigned int key = 0; + unsigned int key = 0, font_size = 32; while (!is_selected && !disp.is_closed() && !key) { @@ -57009,10 +63591,10 @@ namespace cimg_library_suffixed { 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; + unsigned int _ind = 0; const CImg onexone(1,1,1,1,(T)0); if (axis=='x') - cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=4)) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width,4)) cimglist_for(*this,ind) { unsigned int x0 = 0; while (x0 &src = _data[ind]?_data[ind]:onexone; CImg res; - src.__get_select(disp,old_normalization,(src._width - 1)/2,(src._height - 1)/2,(src._depth - 1)/2). + src._get_select(disp,old_normalization,src._width/2,src._height/2,src._depth/2). move_to(res); const unsigned int h = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,true); res.resize(x1 - x0,std::max(32U,h*disp._height/max_height),1,res._spectrum==1?3:-100); @@ -57031,7 +63613,7 @@ namespace cimg_library_suffixed { visu0.draw_image(positions(ind,0),positions(ind,1),res); } else - cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=4)) + cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width,4)) cimglist_for(*this,ind) { unsigned int y0 = 0; while (y0 &src = _data[ind]?_data[ind]:onexone; CImg res; - src.__get_select(disp,old_normalization,(src._width - 1)/2,(src._height - 1)/2,(src._depth - 1)/2). + src._get_select(disp,old_normalization,(src._width - 1)/2,(src._height - 1)/2,(src._depth - 1)/2). move_to(res); const unsigned int w = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,false); res.resize(std::max(32U,w*disp._width/max_width),y1 - y0,1,res._spectrum==1?3:-100); @@ -57049,14 +63631,14 @@ namespace cimg_library_suffixed { 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); + if (axis=='x') --positions(_ind,2); else --positions(_ind,3); update_display = true; } - if (!visu || oindice0!=indice0 || oindice1!=indice1) { - if (indice0>=0 && indice1>=0) { + if (!visu || oindex0!=index0 || oindex1!=index1) { + if (index0>=0 && index1>=0) { visu.assign(visu0,false); - const int indm = std::min(indice0,indice1), indM = std::max(indice0,indice1); + const int indm = std::min(index0,index1), indM = std::max(index0,index1); 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); @@ -57065,16 +63647,14 @@ namespace cimg_library_suffixed { 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); + if (is_clicked) visu.__draw_text(" Images #%u - #%u, Size = %u ",font_size,(int)text_down, + orig + indm,orig + indM,indM - indm + 1); + else visu.__draw_text(" Image #%u (%u,%u,%u,%u) ",font_size,(int)text_down, + orig + index0, + _data[index0]._width, + _data[index0]._height, + _data[index0]._depth, + _data[index0]._spectrum); update_display = true; } else visu.assign(); } @@ -57084,27 +63664,27 @@ namespace cimg_library_suffixed { // Manage user events. const int xm = disp.mouse_x(), ym = disp.mouse_y(); - int indice = -1; + int index = -1; if (xm>=0) { - indice = (int)indices(axis=='x'?xm:ym); + index = (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 (!is_clicked) { is_clicked = true; oindex0 = index0; index0 = index; } + oindex1 = index1; index1 = index; if (!feature_type) is_selected = true; } else { - if (!is_clicked) { oindice0 = oindice1 = indice0; indice0 = indice1 = indice; } + if (!is_clicked) { oindex0 = oindex1 = index0; index0 = index1 = index; } 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()&1)) { is_clicked = is_selected = false; index0 = index1 = -1; } + else index1 = -1; + } else index0 = index1 = -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.button()&4) { is_clicked = is_selected = false; index0 = index1 = -1; } + if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; index1 = index0 = -1; } if (disp.wheel() && exit_on_wheel) is_selected = true; CImg filename(32); @@ -57139,15 +63719,19 @@ namespace cimg_library_suffixed { 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); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u." +#ifdef cimg_use_png + "png", +#else + "bmp", +#endif + snap_number++); + if ((file=cimg::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).__draw_text(" Saving snapshot... ",font_size,(int)text_down).display(disp); visu0.save(filename); - (+visu0).draw_text(0,0," Snapshot '%s' saved. ", - foreground_color,background_color,0.7f,13,filename._data).display(disp); + (+visu0).__draw_text(" Snapshot '%s' saved. ",font_size,(int)text_down,filename._data).display(disp); } disp.set_key(key,false).wait(); key = 0; } break; @@ -57157,17 +63741,15 @@ namespace cimg_library_suffixed { std::FILE *file; do { #ifdef cimg_use_zlib - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimgz",snap_number++); #else - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimg",snap_number++); #endif - if ((file=std_fopen(filename,"r"))!=0) cimg::fclose(file); + if ((file=cimg::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); + (+visu0).__draw_text(" Saving instance... ",font_size,(int)text_down).display(disp); save(filename); - (+visu0).draw_text(0,0," Instance '%s' saved. ", - foreground_color,background_color,0.7f,13,filename._data).display(disp); + (+visu0).__draw_text(" Instance '%s' saved. ",font_size,(int)text_down,filename._data).display(disp); disp.set_key(key,false).wait(); key = 0; } break; } @@ -57181,8 +63763,8 @@ namespace cimg_library_suffixed { } CImg 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 (feature_type) res.fill(std::min(index0,index1),std::max(index0,index1)); + else res.fill(index0); } if (!(disp.button()&2)) disp.set_button(); disp._normalization = old_normalization; @@ -57269,6 +63851,7 @@ namespace cimg_library_suffixed { !cimg::strcasecmp(ext,"qt") || !cimg::strcasecmp(ext,"rm") || !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"webm") || !cimg::strcasecmp(ext,"wmv") || !cimg::strcasecmp(ext,"xvid") || !cimg::strcasecmp(ext,"mpeg")) load_video(filename); @@ -57278,7 +63861,7 @@ namespace cimg_library_suffixed { // 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"); + std::FILE *const file = cimg::std_fopen(filename,"rb"); if (!file) { cimg::exception_mode(omode); throw CImgIOException(_cimglist_instance @@ -57288,11 +63871,13 @@ namespace cimg_library_suffixed { } const char *const f_type = cimg::ftype(file,filename); - std::fclose(file); + cimg::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 if (!cimg::strcasecmp(f_type,"tif") && + cimg::strcasecmp(ext,"nef") && + cimg::strcasecmp(ext,"dng")) load_tiff(filename); else is_loaded = false; } catch (CImgIOException&) { is_loaded = false; } } @@ -57349,13 +63934,21 @@ namespace cimg_library_suffixed { #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); \ + cimg::fread(cbuf,(size_t)csiz,nfile); \ + if (is_bool) { \ + CImg raw(W*H*D*C/8); \ + uLongf destlen = (uLongf)raw.size(); \ + uncompress((Bytef*)raw._data,&destlen,cbuf,(uLong)csiz); \ + img.assign(W,H,D,C); \ + img._uchar2bool(raw,raw.size(),false); \ + } else { \ + CImg raw(W,H,D,C); \ + uLongf destlen = (uLongf)(raw.size()*sizeof(Tss)); \ + uncompress((Bytef*)raw._data,&destlen,cbuf,(uLong)csiz); \ + if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ + raw.move_to(img); \ + } \ delete[] cbuf; \ - if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ - raw.move_to(img); \ } #else #define _cimgz_load_cimg_case(Tss) \ @@ -57365,30 +63958,44 @@ namespace cimg_library_suffixed { filename?filename:"(FILE*)"); #endif -#define _cimg_load_cimg_case(Ts,Tss) \ - if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \ +#define _cimg_load_cimg_case(Ts1,Ts2,Ts3,Tss) \ + if (!loaded && ((Ts1 && !cimg::strcasecmp(Ts1,str_pixeltype)) || \ + (Ts2 && !cimg::strcasecmp(Ts2,str_pixeltype)) || \ + (Ts3 && !cimg::strcasecmp(Ts3,str_pixeltype)))) { \ + const bool is_bool = cimg::type::string()==cimg::type::string(); \ for (unsigned int l = 0; l=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) \ + if ((err = cimg_sscanf(tmp,"%u %u %u %u #" cimg_fuint64,&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 raw; \ CImg &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; \ + if (is_bool) { \ + CImg raw; \ + 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); \ + CImg(ptrd,std::min(8*raw._width,(unsigned int)(img.end() - ptrd)),1,1,1,true).\ + _uchar2bool(raw,raw._width,false); \ + to_read-=raw._width; \ + } \ + } else { \ + CImg raw; \ + 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; \ + } \ } \ } \ } \ @@ -57407,41 +64014,37 @@ namespace cimg_library_suffixed { CImg 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; + cimg_uint64 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_ ]", + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z123468_]%*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'.", + "load_cimg(): File or 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); + _cimg_load_cimg_case("bool",0,0,cimg_uint8); + _cimg_load_cimg_case("uint8","unsigned_char","uchar",cimg_uint8); + _cimg_load_cimg_case("int8",0,0,cimg_int8); + _cimg_load_cimg_case("char",0,0,char); + _cimg_load_cimg_case("uint16","unsigned_short","ushort",cimg_uint16); + _cimg_load_cimg_case("int16","short",0,cimg_int16); + _cimg_load_cimg_case("uint32","unsigned_int","uint",cimg_uint32); + _cimg_load_cimg_case("int32","int",0,cimg_int32); + _cimg_load_cimg_case("unsigned_long","ulong",0,cimg_ulong); + _cimg_load_cimg_case("long",0,0,cimg_long); + _cimg_load_cimg_case("uint64","unsigned_int64",0,cimg_uint64); + _cimg_load_cimg_case("int64",0,0,cimg_int64); + _cimg_load_cimg_case("float32","float",0,cimg_float32); + _cimg_load_cimg_case("float64","double",0,cimg_float64); if (!loaded) { if (!file) cimg::fclose(nfile); @@ -57508,13 +64111,15 @@ namespace cimg_library_suffixed { } CImgList& _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 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 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)) { \ +#define _cimg_load_cimg_case2(Ts1,Ts2,Ts3,Tss) \ + if (!loaded && ((Ts1 && !cimg::strcasecmp(Ts1,str_pixeltype)) || \ + (Ts2 && !cimg::strcasecmp(Ts2,str_pixeltype)) || \ + (Ts3 && !cimg::strcasecmp(Ts3,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; \ @@ -57592,7 +64197,7 @@ namespace cimg_library_suffixed { 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_ ]", + err = cimg_sscanf(tmp,"%u%*c%255[A-Za-z123468_]%*c%255[sA-Za-z_ ]", &N,str_pixeltype._data,str_endian._data); if (err<2) { if (!file) cimg::fclose(nfile); @@ -57611,24 +64216,20 @@ namespace cimg_library_suffixed { 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); + _cimg_load_cimg_case2("bool",0,0,cimg_uint8); + _cimg_load_cimg_case2("uint8","unsigned char","uchar",cimg_uint8); + _cimg_load_cimg_case2("int8",0,0,cimg_int8); + _cimg_load_cimg_case2("char",0,0,char); + _cimg_load_cimg_case2("uint16","unsigned_short","ushort",cimg_uint16); + _cimg_load_cimg_case2("int16","short",0,cimg_int16); + _cimg_load_cimg_case2("uint32","unsigned_int","uint",cimg_uint32); + _cimg_load_cimg_case2("int32","int",0,cimg_int32); + _cimg_load_cimg_case2("unsigned_long","ulong",0,cimg_ulong); + _cimg_load_cimg_case2("long",0,0,cimg_long); + _cimg_load_cimg_case2("uint64","unsigned_int64",0,cimg_uint64); + _cimg_load_cimg_case2("int64",0,0,cimg_int64); + _cimg_load_cimg_case2("float32","float",0,cimg_float32); + _cimg_load_cimg_case2("float64","double",0,cimg_float64); if (!loaded) { if (!file) cimg::fclose(nfile); throw CImgIOException(_cimglist_instance @@ -57808,25 +64409,25 @@ namespace cimg_library_suffixed { } CImgList& _load_yuv(std::FILE *const file, const char *const filename, - const unsigned int size_x, const unsigned int size_y, + const unsigned int size_x, const unsigned int size_y, const unsigned int chroma_subsampling, - const unsigned int first_frame, const unsigned int last_frame, - const unsigned int step_frame, const bool yuv2rgb) { + 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 (chroma_subsampling!=420 && chroma_subsampling!=422 && chroma_subsampling!=444) throw CImgArgumentException(_cimglist_instance - "load_yuv(): Specified chroma subsampling '%u' is invalid, for file '%s'.", + "load_yuv(): Specified chroma subsampling %u is invalid, for file '%s'.", cimglist_instance, chroma_subsampling,filename?filename:"(FILE*)"); const unsigned int cfx = chroma_subsampling==420 || chroma_subsampling==422?2:1, cfy = chroma_subsampling==420?2:1, - nfirst_frame = first_frame1) 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).", + "and 'step_frame' requires features from the OpenCV library " + "('-Dcimg_use_opencv' must be defined).", cimglist_instance,filename); return load_ffmpeg_external(filename); #else - static CvCapture *captures[32] = { 0 }; + static cv::VideoCapture *captures[32] = {}; static CImgList filenames(32); static CImg positions(32,1,1,1,0); static int last_used_index = -1; @@ -57962,8 +64563,11 @@ namespace cimg_library_suffixed { 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; + captures[index]->release(); + delete captures[index]; + captures[index] = 0; + positions[index] = 0; + filenames[index].assign(); if (last_used_index==index) last_used_index = -1; index = -1; cimg::mutex(9,0); @@ -57993,69 +64597,56 @@ namespace cimg_library_suffixed { "You have to release some of your previously opened videos.", cimglist_instance,filename); cimg::mutex(9); - captures[index] = cvCaptureFromFile(filename); - CImg::string(filename).move_to(filenames[index]); + captures[index] = new cv::VideoCapture(filename); positions[index] = 0; - cimg::mutex(9,0); - if (!captures[index]) { - filenames[index].assign(); - std::fclose(cimg::fopen(filename,"rb")); // Check file availability. + if (!captures[index]->isOpened()) { + delete captures[index]; + captures[index] = 0; + cimg::mutex(9,0); + cimg::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::string(filename).move_to(filenames[index]); + cimg::mutex(9,0); } cimg::mutex(9); - const unsigned int nb_frames = (unsigned int)std::max(0.,cvGetCaptureProperty(captures[index], - CV_CAP_PROP_FRAME_COUNT)); + const unsigned int nb_frames = (unsigned int)std::max(0.,captures[index]->get(_cimg_cap_prop_frame_count)); cimg::mutex(9,0); assign(); - // Skip frames if necessary. + // Skip frames if requested. bool go_on = true; unsigned int &pos = positions[index]; while (posgrab()) { 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 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; iread(cvimg)) { CImg::_cvmat2cimg(cvimg).move_to(*this); ++pos; } + else go_on = false; + cimg::mutex(9,0); + if (go_on) + for (unsigned int i = 1; go_on && igrab()) go_on = false; + cimg::mutex(9,0); } - cimg::mutex(9,0); - if (!src) break; - } } - if (!src || (nb_frames && pos>=nb_frames)) { // Close video stream when necessary. + if (!go_on || (nb_frames && pos>=nb_frames)) { // Close video stream when necessary cimg::mutex(9); - cvReleaseCapture(&captures[index]); + captures[index]->release(); + delete captures[index]; captures[index] = 0; filenames[index].assign(); positions[index] = 0; @@ -58091,21 +64682,21 @@ namespace cimg_library_suffixed { throw CImgArgumentException(_cimglist_instance "load_ffmpeg_external(): Specified filename is (null).", cimglist_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists CImg 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); + if ((file=cimg::std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file); } while (file); cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%%6d.ppm",filename_tmp._data); - cimg_snprintf(command,command._width,"%s -i \"%s\" \"%s\"", + cimg_snprintf(command,command._width,"\"%s\" -v -8 -i \"%s\" \"%s\"", cimg::ffmpeg_path(), CImg::string(filename)._system_strescape().data(), CImg::string(filename_tmp2)._system_strescape().data()); - cimg::system(command,0); + cimg::system(command,cimg::ffmpeg_path()); const unsigned int omode = cimg::exception_mode(); cimg::exception_mode(0); assign(); @@ -58140,7 +64731,7 @@ namespace cimg_library_suffixed { throw CImgArgumentException(_cimglist_instance "load_gif_external(): Specified filename is (null).", cimglist_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists if (!_load_gif_external(filename,false)) if (!_load_gif_external(filename,true)) try { assign(CImg().load_other(filename)); } catch (CImgException&) { assign(); } @@ -58159,17 +64750,17 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file); } while (file); if (use_graphicsmagick) cimg_snprintf(command,command._width,"%s convert \"%s\" \"%s.png\"", cimg::graphicsmagick_path(), CImg::string(filename)._system_strescape().data(), CImg::string(filename_tmp)._system_strescape().data()); - else cimg_snprintf(command,command._width,"%s \"%s\" \"%s.png\"", + else cimg_snprintf(command,command._width,"\"%s\" -coalesce \"%s\" \"%s.png\"", cimg::imagemagick_path(), CImg::string(filename)._system_strescape().data(), CImg::string(filename_tmp)._system_strescape().data()); - cimg::system(command,0); + cimg::system(command,cimg::imagemagick_path()); const unsigned int omode = cimg::exception_mode(); cimg::exception_mode(0); assign(); @@ -58180,12 +64771,11 @@ namespace cimg_library_suffixed { try { img.load_png(filename_tmp2); } catch (CImgException&) { } if (img) { img.move_to(*this); std::remove(filename_tmp2); } - else { // Try to read animated gif. + 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 img; try { img.load_png(filename_tmp2); } catch (CImgException&) { stop_flag = true; } if (img) { img.move_to(*this); std::remove(filename_tmp2); } @@ -58209,7 +64799,7 @@ namespace cimg_library_suffixed { throw CImgIOException(_cimglist_instance "load_gzip_external(): Specified filename is (null).", cimglist_instance); - std::fclose(cimg::fopen(filename,"rb")); // Check if file exists. + cimg::fclose(cimg::fopen(filename,"rb")); // Check if file exists CImg command(1024), filename_tmp(256), body(256); const char *ext = cimg::split_filename(filename,body), @@ -58227,14 +64817,14 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file); } while (file); - cimg_snprintf(command,command._width,"%s -c \"%s\" > \"%s\"", + cimg_snprintf(command,command._width,"\"%s\" -c \"%s\" > \"%s\"", cimg::gunzip_path(), CImg::string(filename)._system_strescape().data(), CImg::string(filename_tmp)._system_strescape().data()); - cimg::system(command); - if (!(file = std_fopen(filename_tmp,"rb"))) { + cimg::system(command,cimg::gunzip_path()); + if (!(file=cimg::std_fopen(filename_tmp,"rb"))) { cimg::fclose(cimg::fopen(filename,"r")); throw CImgIOException(_cimglist_instance "load_gzip_external(): Failed to open file '%s'.", @@ -58252,46 +64842,26 @@ namespace cimg_library_suffixed { return CImgList().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 - CImgList& load_off(const char *const filename, - CImgList& primitives, CImgList& colors) { - return get_load_off(filename,primitives,colors).move_to(*this); - } - - //! Load a 3d object from a .OFF file \newinstance. - template - static CImgList get_load_off(const char *const filename, - CImgList& primitives, CImgList& colors) { - return CImg().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. + \param[out] bits_per_value Number of bits used to store a scalar value in the image file. \param[out] voxel_size Voxel size, as stored in the filename. \param[out] description Description, as stored in the filename. **/ CImgList& 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 *const description=0) { + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, unsigned int *const bits_per_value=0, + float *const voxel_size=0, CImg *const description=0) { const unsigned int - nfirst_frame = first_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); + cimglist_for(*this,l) + _data[l]._load_tiff(tif,nfirst_frame + l*nstep_frame,bits_per_value,voxel_size,description); TIFFClose(tif); } else throw CImgIOException(_cimglist_instance "load_tiff(): Failed to open file '%s'.", @@ -58331,11 +64902,10 @@ namespace cimg_library_suffixed { //! Load a multi-page TIFF file \newinstance. static CImgList 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 *const description=0) { - return CImgList().load_tiff(filename,first_frame,last_frame,step_frame,voxel_size,description); + const unsigned int first_frame=0, const unsigned int last_frame=~0U, + const unsigned int step_frame=1, unsigned int *const bits_per_value=0, + float *const voxel_size=0, CImg *const description=0) { + return CImgList().load_tiff(filename,first_frame,last_frame,step_frame,bits_per_value,voxel_size,description); } //@} @@ -58367,7 +64937,7 @@ namespace cimg_library_suffixed { if (_data) std::fprintf(cimg::output(),"..%p.\n",(void*)((char*)end() - 1)); else std::fprintf(cimg::output(),".\n"); - char tmp[16] = { 0 }; + char tmp[16] = {}; cimglist_for(*this,ll) { cimg_snprintf(tmp,sizeof(tmp),"[%d]",ll); std::fprintf(cimg::output()," "); @@ -58382,10 +64952,10 @@ namespace cimg_library_suffixed { /** \param disp Reference to an existing CImgDisplay instance, where the current image list will be displayed. \param axis Appending axis. Can be { 'x' | 'y' | 'z' | 'c' }. - \param align Appending alignmenet. + \param align Appending alignment. \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. + Images of the list are appended in a single temporary image for visualization purposes. The function returns immediately. **/ const CImgList& display(CImgDisplay &disp, const char axis='x', const float align=0) const { @@ -58398,12 +64968,12 @@ namespace cimg_library_suffixed { \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. + \param align Appending alignment. \param[in,out] XYZ Contains the XYZ coordinates at start / exit of the function. \param exit_on_anykey Exit function when any key is pressed. \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. + Images of the list are appended in a single temporary image for visualization purposes. The function returns when a key is pressed or the display window is closed by the user. **/ const CImgList& display(CImgDisplay &disp, const bool display_info, @@ -58483,10 +65053,10 @@ namespace cimg_library_suffixed { while (!disp.is_closed() && !is_exit) { const CImg 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 (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. + } else if (disp.wheel()) { // Zoom in/out const int wheel = disp.wheel(); disp.set_wheel(); if (!is_first_call && wheel<0) break; @@ -58520,7 +65090,7 @@ namespace cimg_library_suffixed { CImg __display() const { CImg res, str; cimglist_for(*this,l) { - CImg::string(_data[l]).move_to(str); + CImg::string((char*)_data[l]).move_to(str); if (l!=width() - 1) { str.resize(str._width + 1,1,1,1,0); str[str._width - 2] = ','; @@ -58606,6 +65176,7 @@ namespace cimg_library_suffixed { !cimg::strcasecmp(ext,"qt") || !cimg::strcasecmp(ext,"rm") || !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"webm") || !cimg::strcasecmp(ext,"wmv") || !cimg::strcasecmp(ext,"xvid") || !cimg::strcasecmp(ext,"mpeg")) return save_video(fn); @@ -58654,6 +65225,7 @@ namespace cimg_library_suffixed { !cimg::strcasecmp(ext,"qt") || !cimg::strcasecmp(ext,"rm") || !cimg::strcasecmp(ext,"vob") || + !cimg::strcasecmp(ext,"webm") || !cimg::strcasecmp(ext,"wmv") || !cimg::strcasecmp(ext,"xvid") || !cimg::strcasecmp(ext,"mpeg")) return true; @@ -58673,25 +65245,31 @@ namespace cimg_library_suffixed { std::FILE *file = 0; #ifdef cimg_use_png -#define _cimg_save_gif_ext "png" +#define _cimg_save_gif_extension "png" #else -#define _cimg_save_gif_ext "ppm" +#define _cimg_save_gif_extension "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); + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_extension,filename_tmp._data); + if ((file=cimg::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_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u." _cimg_save_gif_extension,filename_tmp._data,l + 1); CImg::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); + CImg frame; + if (_data[l]._depth>1) _data[l].get_slice(0).move_to(frame); else frame.assign(_data[l],true); + if (frame._spectrum>4) frame.assign(frame.get_channels(0,3),false); + else if (frame._spectrum==1) frame.assign(frame.get_resize(-100,-100,1,3),false); + else if (frame._spectrum==2) + frame.assign(frame.get_resize(-100,-100,1,4).draw_image(0,0,0,2,frame.get_shared_channel(0)),false); + frame.save(filename_tmp2); } - 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_snprintf(command,command._width,"\"%s\" -delay %u -loop %u -dispose previous", + cimg::imagemagick_path(), + (unsigned int)std::max(0.f,cimg::round(100/fps)), + nb_loops); CImg::string(command).move_to(filenames,0); cimg_snprintf(command,command._width,"\"%s\"", CImg::string(filename)._system_strescape().data()); @@ -58700,8 +65278,8 @@ namespace cimg_library_suffixed { cimg_for(_command,p,char) if (!*p) *p = ' '; _command.back() = 0; - cimg::system(_command); - file = std_fopen(filename,"rb"); + cimg::system(_command,cimg::imagemagick_path()); + file = cimg::std_fopen(filename,"rb"); if (!file) throw CImgIOException(_cimglist_instance "save_gif_external(): Failed to save file '%s' with external command 'magick/convert'.", @@ -58804,10 +65382,11 @@ namespace cimg_library_suffixed { 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); + std::FILE *const nfile = file?file:cimg::fopen(filename,"wb"); + const bool is_bool = ptype==cimg::type::string(); + std::fprintf(nfile,"%u %s %s_endian\n",_width,ptype,etype); + cimglist_for(*this,l) { const CImg& img = _data[l]; std::fprintf(nfile,"%u %u %u %u",img._width,img._height,img._depth,img._spectrum); @@ -58818,25 +65397,46 @@ namespace cimg_library_suffixed { 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)) + Bytef *cbuf = 0; + uLongf csiz = 0; + + if (is_bool) { // Boolean data (bitwise) + ulongT siz; + const unsigned char *const buf = ref._bool2uchar(siz,false); + csiz = siz + siz/100 + 16; + cbuf = new Bytef[csiz]; + failed_to_compress = (bool)compress(cbuf,&csiz,(Bytef*)buf,siz); + if (!failed_to_compress) { + std::fprintf(nfile," #%lu\n",csiz); + cimg::fwrite(cbuf,csiz,nfile); + } + delete[] buf; + } else { // Non-boolean data + const ulongT siz = sizeof(T)*ref.size(); + csiz = siz + siz/100 + 16; + cbuf = new Bytef[csiz]; + failed_to_compress = (bool)compress(cbuf,&csiz,(Bytef*)ref._data,siz); + if (!failed_to_compress) { + std::fprintf(nfile," #%lu\n",csiz); + cimg::fwrite(cbuf,csiz,nfile); + } + } + if (failed_to_compress) 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; - } + delete[] cbuf; #endif } - if (failed_to_compress) { // Write in a non-compressed way. + if (failed_to_compress) { // Write non-compressed std::fputc('\n',nfile); - cimg::fwrite(ref._data,ref.size(),nfile); + if (is_bool) { // Boolean data (bitwise) + ulongT siz; + const unsigned char *const buf = ref._bool2uchar(siz,false); + cimg::fwrite(buf,siz,nfile); + delete[] buf; + } else cimg::fwrite(ref._data,ref.size(),nfile); // Non-boolean data } } else std::fputc('\n',nfile); } @@ -58857,8 +65457,10 @@ namespace cimg_library_suffixed { 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)) { \ +#define _cimg_save_cimg_case(Ts1,Ts2,Ts3,Tss) \ + if (!saved && ((Ts1 && !cimg::strcasecmp(Ts1,str_pixeltype)) || \ + (Ts2 && !cimg::strcasecmp(Ts2,str_pixeltype)) || \ + (Ts3 && !cimg::strcasecmp(Ts3,str_pixeltype)))) { \ for (unsigned int l = 0; l=8 && siz*sizeof(T)>=1UL<<31; // No bigtiff for small images. + 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) { @@ -59116,17 +65716,17 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::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_snprintf(command,command._width,"\"%s\" -c \"%s\" > \"%s\"", cimg::gzip_path(), CImg::string(filename_tmp)._system_strescape().data(), CImg::string(filename)._system_strescape().data()); - cimg::system(command); - file = std_fopen(filename,"rb"); + cimg::system(command,cimg::gzip_path()); + file = cimg::std_fopen(filename,"rb"); if (!file) throw CImgIOException(_cimglist_instance "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.", @@ -59138,14 +65738,14 @@ namespace cimg_library_suffixed { CImg nfilename(1024); cimglist_for(*this,l) { cimg::number_filename(body,l,6,nfilename); - if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext); + if (*ext) cimg_snprintf(nfilename._data + std::strlen(nfilename),64,".%s",ext); _data[l].save_gzip_external(nfilename); } } return *this; } - //! Save image sequence, using the OpenCV library. + //! Save image sequence (using the OpenCV library when available). /** \param filename Filename to write data to. \param fps Number of frames per second. @@ -59157,116 +65757,118 @@ namespace cimg_library_suffixed { 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); + if (keep_open) cimg::warn(_cimglist_instance + "save_video(): Cannot output streamed video, as this requires features from the " + "OpenCV library ('-Dcimg_use_opencv') must be defined).", + cimglist_instance); + if (!is_empty()) return save_ffmpeg_external(filename,fps); + return *this; #else - static CvVideoWriter *writers[32] = { 0 }; - static CImgList filenames(32); - static CImg sizes(32,2,1,1,0); - static int last_used_index = -1; + try { + static cv::VideoWriter *writers[32] = {}; + static CImgList filenames(32); + static CImg 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:cimg_OS==2?"mpeg":"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; + // Detect if a video writer already exists for the specified filename. cimg::mutex(9); - writers[index] = cvCreateVideoWriter(filename,CV_FOURCC(codec0,codec1,codec2,codec3), - fps,cvSize(W,H)); - CImg::string(filename).move_to(filenames[index]); - sizes(index,0) = W; sizes(index,1) = H; + 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); - 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 &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 _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++); - } + // 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); + const char + *const _codec = codec && *codec?codec:"h264", + codec0 = cimg::uppercase(_codec[0]), + codec1 = _codec[0]?cimg::uppercase(_codec[1]):0, + codec2 = _codec[1]?cimg::uppercase(_codec[2]):0, + codec3 = _codec[2]?cimg::uppercase(_codec[3]):0; + cimg::mutex(9); + writers[index] = new cv::VideoWriter(filename,_cimg_fourcc(codec0,codec1,codec2,codec3),fps,cv::Size(W,H)); + if (!writers[index]->isOpened()) { + delete writers[index]; + writers[index] = 0; + cimg::mutex(9,0); + 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); } - cvWriteFrame(writers[index],ipl); + CImg::string(filename).move_to(filenames[index]); + sizes(index,0) = W; + sizes(index,1) = H; + cimg::mutex(9,0); } - cvReleaseImage(&ipl); + + if (!is_empty()) { + const unsigned int W = sizes(index,0), H = sizes(index,1); + cimg::mutex(9); + cimglist_for(*this,l) { + CImg &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._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); + cimg_forZ(src,z) { + CImg _src = src._depth>1?src.get_slice(z):src.get_shared(); + if (_src._width==W && _src._height==H && _src._spectrum==3) + writers[index]->write(CImg(_src)._cimg2cvmat()); + else { + CImg __src(_src,false); + __src.channels(0,std::min(__src._spectrum - 1,2U)).resize(W,H); + __src.resize(W,H,1,3,__src._spectrum==1); + writers[index]->write(__src._cimg2cvmat()); + } + } + } + cimg::mutex(9,0); + } + + cimg::mutex(9); + if (!keep_open) { + delete 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); + } catch (CImgIOException &e) { + if (!keep_open) return save_ffmpeg_external(filename,fps); + throw e; } - - 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 } @@ -59288,7 +65890,9 @@ namespace cimg_library_suffixed { const char *const ext = cimg::split_filename(filename), - *const _codec = codec?codec:!cimg::strcasecmp(ext,"flv")?"flv":"mpeg2video"; + *const _codec = codec?codec: + !cimg::strcasecmp(ext,"flv")?"flv": + !cimg::strcasecmp(ext,"mp4")?"h264":"mpeg2video"; CImg command(1024), filename_tmp(256), filename_tmp2(256); CImgList filenames; @@ -59302,21 +65906,31 @@ namespace cimg_library_suffixed { 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); + if ((file=cimg::std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file); } while (file); + unsigned int frame = 1; cimglist_for(*this,l) { - cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,l + 1); - CImg::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); + CImg& src = _data[l]; + cimg_forZ(src,z) { + cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u.ppm",filename_tmp._data,frame); + CImg::string(filename_tmp2).move_to(filenames); + CImg _src = src._depth>1?src.get_slice(z):src.get_shared(); + if (_src._width%2 || _src._height%2) // Force output to have an even number of columns and rows + _src.assign(_src.get_resize(_src._width + (_src._width%2),_src._height + (_src._height%2),1,-100,0),false); + if (_src._spectrum!=3) // Force output to be one slice, in color + _src.assign(_src.get_resize(-100,-100,1,3),false); + _src.save_pnm(filename_tmp2); + ++frame; + } } - cimg_snprintf(command,command._width,"%s -i \"%s_%%6d.ppm\" -vcodec %s -b %uk -r %u -y \"%s\"", + cimg_snprintf(command,command._width, + "\"%s\" -framerate %u -v -8 -y -i \"%s_%%6d.ppm\" -pix_fmt yuv420p -vcodec %s -b %uk -r %u \"%s\"", cimg::ffmpeg_path(), - CImg::string(filename_tmp)._system_strescape().data(), + fps,CImg::string(filename_tmp)._system_strescape().data(), _codec,bitrate,fps, CImg::string(filename)._system_strescape().data()); - cimg::system(command); - file = std_fopen(filename,"rb"); + cimg::system(command,cimg::ffmpeg_path()); + file = cimg::std_fopen(filename,"rb"); if (!file) throw CImgIOException(_cimglist_instance "save_ffmpeg_external(): Failed to save file '%s' with external command 'ffmpeg'.", @@ -59331,8 +65945,9 @@ namespace cimg_library_suffixed { /** \param is_compressed tells if zlib compression must be used for serialization (this requires 'cimg_use_zlib' been enabled). + \param header_size Reserve empty bytes as a starting header. **/ - CImg get_serialize(const bool is_compressed=false) const { + CImg get_serialize(const bool is_compressed=false, const unsigned int header_size=0) const { #ifndef cimg_use_zlib if (is_compressed) cimg::warn(_cimglist_instance @@ -59341,12 +65956,10 @@ namespace cimg_library_suffixed { cimglist_instance); #endif CImgList stream; + if (header_size) CImg(1,header_size,1,1,0).move_to(stream); CImg 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_snprintf(tmpstr,tmpstr._width,"%u %s %s_endian\n",_width,ptype,etype); CImg::string(tmpstr,false).move_to(stream); cimglist_for(*this,l) { const CImg& img = _data[l]; @@ -59375,26 +65988,37 @@ namespace cimg_library_suffixed { } #endif } - if (failed_to_compress) { // Write in a non-compressed way. + if (failed_to_compress) { // Write in a non-compressed way CImg::string("\n",false).move_to(stream); stream.insert(1); - stream.back().assign((unsigned char*)ref._data,ref.size()*sizeof(T),1,1,1,true); + stream.back(). + assign((unsigned char*)ref._data,ref._width,ref._height,ref._depth,ref._spectrum*sizeof(T),true); } } else CImg::string("\n",false).move_to(stream); } - cimglist_apply(stream,unroll)('y'); - return stream>'y'; + + // Determine best serialized image dimensions to store the whole buffer. + ulongT siz = 0; + cimglist_for(stream,l) siz+=stream[l].size(); + const ulongT max_siz = (ulongT)cimg::type::max(); + const unsigned int + nw = (unsigned int)(siz/max_siz + ((siz%max_siz)?1:0)), + nh = (unsigned int)(siz/nw + (siz%nw?1:0)); + CImg res(nw,nh,1,1,0); + unsigned char *ptr = res.data(); + cimglist_for(stream,l) { siz = stream[l].size(); std::memcpy(ptr,stream[l]._data,siz); ptr+=siz; } + return res; } //! Unserialize a CImg serialized buffer into a CImgList list. template - static CImgList get_unserialize(const CImg& buffer) { + static CImgList get_unserialize(const CImg& buffer, const unsigned int header_size=0) { #ifdef cimg_use_zlib #define _cimgz_unserialize_case(Tss) { \ Bytef *cbuf = 0; \ - if (sizeof(t)!=1 || cimg::type::string()==cimg::type::string()) { \ + if (sizeof(t)!=1 || buffer.pixel_type()==cimg::type::string()) { \ cbuf = new Bytef[csiz]; Bytef *_cbuf = cbuf; \ - for (ulongT i = 0; i raw; \ CImg &img = res._data[l]; \ if (err==5) _cimgz_unserialize_case(Tss) \ - else if (sizeof(Tss)==sizeof(t) && cimg::type::is_float()==cimg::type::is_float()) { \ - raw.assign((Tss*)stream,W,H,D,C,true); \ - stream+=raw.size(); \ - } else { \ + else { \ raw.assign(W,H,D,C); \ CImg _raw((unsigned char*)raw._data,W*sizeof(Tss),H,D,C,true); \ - cimg_for(_raw,p,unsigned char) *p = (unsigned char)*(stream++); \ + if (sizeof(t)==1) { std::memcpy(_raw,stream,_raw.size()); stream+=_raw.size(); } \ + else cimg_for(_raw,p,unsigned char) *p = (unsigned char)*(stream++); \ } \ if (endian!=cimg::endianness()) cimg::invert_endianness(raw._data,raw.size()); \ raw.move_to(img); \ @@ -59442,7 +66066,7 @@ namespace cimg_library_suffixed { throw CImgArgumentException("CImgList<%s>::get_unserialize(): Specified serialized buffer is (null).", pixel_type()); CImgList res; - const t *stream = buffer._data, *const estream = buffer._data + buffer.size(); + const t *stream = buffer._data + header_size, *const estream = buffer._data + buffer.size(); bool loaded = false, endian = cimg::endianness(), is_bytef = false; CImg tmp(256), str_pixeltype(256), str_endian(256); *tmp = *str_pixeltype = *str_endian = 0; @@ -59454,7 +66078,7 @@ namespace cimg_library_suffixed { j = 0; while ((i=(int)*stream)!='\n' && stream::get_unserialize(): CImg header not found in serialized buffer.", @@ -59462,21 +66086,18 @@ namespace cimg_library_suffixed { 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); + _cimg_unserialize_case("bool",0,0,cimg_uint8); + _cimg_unserialize_case("uint8","unsigned_char","uchar",cimg_uint8); + _cimg_unserialize_case("int8",0,0,cimg_int8); + _cimg_unserialize_case("char",0,0,char); + _cimg_unserialize_case("uint16","unsigned_short","ushort",cimg_uint16); + _cimg_unserialize_case("int16","short",0,cimg_int16); + _cimg_unserialize_case("uint32","unsigned_int","uint",cimg_uint32); + _cimg_unserialize_case("int32","int",0,cimg_int32); + _cimg_unserialize_case("uint64","unsigned_int64",0,cimg_uint64); + _cimg_unserialize_case("int64",0,0,cimg_int64); + _cimg_unserialize_case("float32","float",0,cimg_float32); + _cimg_unserialize_case("float64","double",0,cimg_float64); if (!loaded) throw CImgArgumentException("CImgList<%s>::get_unserialize(): Unsupported pixel type '%s' defined " "in serialized buffer.", @@ -59491,107 +66112,137 @@ namespace cimg_library_suffixed { //@{ //---------------------------------- - //! Crop font along the X-axis. - /** - **/ - CImgList& crop_font() { - return get_crop_font().move_to(*this); - } - - //! Crop font along the X-axis \newinstance. - /** - **/ - CImgList get_crop_font() const { - CImgList res; - cimglist_for(*this,l) { - const CImg& letter = (*this)[l]; - int xmin = letter.width(), xmax = 0; - cimg_forXY(letter,x,y) if (letter(x,y)) { if (xxmax) xmax = x; } - if (xmin>xmax) CImg(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& font(const unsigned int font_height, const bool is_variable_width=true) { - if (!font_height) return CImgList::const_empty(); + static const CImgList& font(const unsigned int requested_height, const bool is_variable_width=true) { + if (!requested_height) return CImgList::const_empty(); cimg::mutex(11); + static const unsigned char font_resizemap[] = { + 0, 4, 7, 9, 11, 13, 15, 17, 19, 21, 22, 24, 26, 27, 29, 30, + 32, 33, 35, 36, 38, 39, 41, 42, 43, 45, 46, 47, 49, 50, 51, 52, + 54, 55, 56, 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, 69, 71, 72, + 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 123, 124, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 157, 158, 159, 160, 161, 162, 163, 164, 164, 165, + 166, 167, 168, 169, 170, 170, 171, 172, 173, 174, 175, 176, 176, 177, 178, 179, + 180, 181, 181, 182, 183, 184, 185, 186, 186, 187, 188, 189, 190, 191, 191, 192, + 193, 194, 195, 196, 196, 197, 198, 199, 200, 200, 201, 202, 203, 204, 205, 205, + 206, 207, 208, 209, 209, 210, 211, 212, 213, 213, 214, 215, 216, 216, 217, 218, + 219, 220, 220, 221, 222, 223, 224, 224, 225, 226, 227, 227, 228, 229, 230, 231, + 231, 232, 233, 234, 234, 235, 236, 237, 238, 238, 239, 240, 241, 241, 242, 243, + 244, 244, 245, 246, 247, 247, 248, 249, 250, 250, 251, 252, 253, 253, 254, 255 }; + static const char *const *font_data[] = { + cimg::data_font_small, + cimg::data_font_normal, + cimg::data_font_large, + cimg::data_font_huge }; + static const unsigned int + font_width[] = { 10,26,52,104 }, + font_height[] = { 13,32,64,128 }, + font_M[] = { 86,91,91,47 }, + font_chunk[] = { sizeof(cimg::data_font_small)/sizeof(char*), + sizeof(cimg::data_font_normal)/sizeof(char*), + sizeof(cimg::data_font_large)/sizeof(char*), + sizeof(cimg::data_font_huge)/sizeof(char*) }; + static const unsigned char font_is_binary[] = { 1,0,0,1 }; + static CImg font_base[4]; + + unsigned int ind = + requested_height<=font_height[0]?0U: + requested_height<=font_height[1]?1U: + requested_height<=font_height[2]?2U:3U; // 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 base_fonts[4]; - CImg &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(); + CImg &basef = font_base[ind]; + if (!basef) { + basef.assign(256*font_width[ind],font_height[ind]); - // Special case needed for 90x103 to avoid MS compiler limit with big strings. - CImg data90x103; - if (!data_font) { - ((CImg(cimg::_data_font90x103[0], - (unsigned int)std::strlen(cimg::_data_font90x103[0]),1,1,1,true), - CImg(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(); - } + unsigned char *ptrd = basef; + const unsigned char *const ptrde = basef.end(); + + // Recompose font data from several chunks, to deal with MS compiler limit with big strings (64 Kb). + CImg dataf; + for (unsigned int k = 0; k::string(font_data[ind][k],k==font_chunk[ind] - 1,true),'x'); // 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; } - } + const unsigned int M = font_M[ind]; + if (font_is_binary[ind]) + for (const char *ptrs = dataf; *ptrs; ++ptrs) { + const int _n = (int)(*ptrs - M - 32), v = _n>=0?255:0, n = _n>=0?_n:-_n; + if (ptrd + n<=ptrde) { std::memset(ptrd,v,n); ptrd+=n; } + else { std::memset(ptrd,v,ptrde - ptrd); break; } + } + else + for (const char *ptrs = dataf; *ptrs; ++ptrs) { + int n = (int)*ptrs - M - 32, v = 0; + if (n>=0) { v = 85*n; n = 1; } + else { + n = -n; + v = (int)*(++ptrs) - M - 32; + if (v<0) { v = 0; --ptrs; } else v*=85; + } + 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 fonts[16]; - static bool is_variable_widths[16] = { 0 }; - unsigned int ind = ~0U; + static bool is_variable_widths[16] = {}; + 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 (!fonts[i] || (is_variable_widths[i]==is_variable_width && requested_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. + if (ind==~0U) { // No empty slots nor existing font in cache fonts->assign(); - std::memmove(fonts,fonts + 1,15*sizeof(CImgList)); + std::memmove((void*)fonts,(void*)(fonts + 1),15*sizeof(CImgList)); std::memmove(is_variable_widths,is_variable_widths + 1,15*sizeof(bool)); - std::memset((void*)(fonts + (ind=15)),0,sizeof(CImgList)); // Free a slot in cache for new font. + std::memset((void*)(fonts + (ind=15)),0,sizeof(CImgList)); // Free a slot in cache for new font } CImgList &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); + basef.get_split('x',256).move_to(font); + if (requested_height!=font[0]._height) + cimglist_for(font,l) { + font[l].resize(std::max(1U,font[l]._width*requested_height/font[l]._height),requested_height,-100,-100,5); + cimg_for(font[l],ptr,ucharT) *ptr = font_resizemap[*ptr]; + } + if (is_variable_width) { // Crop font + cimglist_for(font,l) { + CImg& letter = font[l]; + int xmin = letter.width(), xmax = 0; + cimg_forX(letter,x) { // Find xmin + cimg_forY(letter,y) if (letter(x,y)) { xmin = x; break; } + if (xmin!=letter.width()) break; + } + cimg_rofX(letter,x) { // Find xmax + cimg_forY(letter,y) if (letter(x,y)) { xmax = x; break; } + if (xmax) break; + } + if (xmin<=xmax) letter.crop(xmin,0,xmax,letter._height - 1); + } + font[(int)' '].resize(font[(int)'f']._width,-100,-100,-100,0); + if (' ' + 256::FFT(_data[0],_data[1],axis,invert); return *this; } @@ -59613,7 +66263,7 @@ namespace cimg_library_suffixed { return CImgList(*this,false).FFT(axis,invert); } - //! Compute a n-d Fast Fourier Transform. + //! Compute n-D Fast Fourier Transform. /** \param invert Tells if the direct (\c false) or inverse transform (\c true) is computed. **/ @@ -59629,12 +66279,12 @@ namespace cimg_library_suffixed { return *this; } - //! Compute a n-d Fast Fourier Transform \newinstance. + //! Compute n-D Fast Fourier Transform \newinstance. CImgList get_FFT(const bool invert=false) const { return CImgList(*this,false).FFT(invert); } - //! Reverse primitives orientations of a 3d object. + //! Reverse primitives orientations of a 3D object. /** **/ CImgList& reverse_object3d() { @@ -59651,1252 +66301,1361 @@ namespace cimg_library_suffixed { return *this; } - //! Reverse primitives orientations of a 3d object \newinstance. + //! Reverse primitives orientations of a 3D object \newinstance. CImgList get_reverse_object3d() const { return (+*this).reverse_object3d(); } //@} - }; // struct CImgList { ... + }; // struct CImgList { ... - /* - #--------------------------------------------- - # - # Completion of previously declared functions - # - #---------------------------------------------- - */ + // Completion of previously declared functions + //-------------------------------------------- + namespace cimg { -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) { + // 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; + 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; + 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) { + inline FILE* _stdout(const bool throw_exception) { #ifndef cimg_use_r - cimg::unused(throw_exception); - return stdout; + 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; + 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) { + inline FILE* _stderr(const bool throw_exception) { #ifndef cimg_use_r - cimg::unused(throw_exception); - return stderr; + 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; + 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) { + // Open a file (similar to std:: fopen(), but with wide character support on Windows). + inline std::FILE *std_fopen(const char *const path, const char *const mode) { + std::FILE *const res = std::fopen(path,mode); + if (res) return res; #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 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 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); + // Try alternative method, with wide-character string. + int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0); + if (err) { + CImg wpath((unsigned int)err); + err = MultiByteToWideChar(CP_UTF8,0,path,-1,wpath,err); + if (err) { // Convert 'mode' to a wide-character string + err = MultiByteToWideChar(CP_UTF8,0,mode,-1,0,0); + if (err) { + CImg wmode((unsigned int)err); + if (MultiByteToWideChar(CP_UTF8,0,mode,-1,wmode,err)) + return _wfopen(wpath,wmode); + } + } + } #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; } \ + return 0; } - static CImg 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 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); + + //! Search path of an executable (Windows only). #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"); + inline bool win_searchpath(const char *const exec_name, char *const res, const unsigned int size_res) { + char *ptr = 0; + DWORD err = SearchPathA(0,exec_name,0,size_res,res,&ptr); + return err!=0; + } #endif - if (!path_found) { + + //! Get the file or directory attributes with support for UTF-8 paths (Windows only). +#if cimg_OS==2 + inline DWORD win_getfileattributes(const char *const path) { + DWORD res = GetFileAttributesA(path); + if (res==INVALID_FILE_ATTRIBUTES) { + // Try alternative method, with wide-character string. + int err = MultiByteToWideChar(CP_UTF8,0,path,-1,0,0); + if (err) { + CImg wpath((unsigned int)err); + if (MultiByteToWideChar(CP_UTF8,0,path,-1,wpath,err)) res = GetFileAttributesW(wpath); + } + } + return res; + } +#endif + + //! Get/set path to the Program Files/ 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* win_programfiles_path(const char *const user_path=0, const bool reinit_path=false) { + static CImg 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; - 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 Program Files/ 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 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). + // 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"); - } + 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"); + std::strcpy(s_path,"C:\\PROGRA~1"); #endif + } + cimg::mutex(7,0); + return s_path; } - 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 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; + //! 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 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(); - for (int l = 0; l<2 && !path_found; ++l) { - const char *const s_exe = l?"convert":"magick"; - cimg_snprintf(s_path,s_path._width,".\\%s.exe",s_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-\\%s.exe",pf_path,k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + if (win_searchpath("curl.exe",s_path,s_path._width)) path_found = true; + if (!path_found) { + std::strcpy(s_path,".\\curl.exe"); + if ((file=cimg::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\\%s.exe",pf_path,k,s_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=cimg::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\\%s.exe",pf_path,k,s_exe); - 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; + } + + //! 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 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 (win_searchpath("dcraw.exe",s_path,s_path._width)) path_found = true; + if (!path_found) { + std::strcpy(s_path,".\\dcraw.exe"); + if ((file=cimg::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=cimg::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 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 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 (win_searchpath("ffmpeg.exe",s_path,s_path._width)) path_found = true; + if (!path_found) { + std::strcpy(s_path,".\\ffmpeg.exe"); + if ((file=cimg::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=cimg::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 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 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 (win_searchpath("gm.exe",s_path,s_path._width)) path_found = true; + const char *const pf_path = win_programfiles_path(); + if (!path_found) { + std::strcpy(s_path,".\\gm.exe"); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k); + if ((file=cimg::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-\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%.2d-\\gm.exe",k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d-Q\\gm.exe",k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d\\gm.exe",k); + if ((file=cimg::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-\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%.2d-\\gm.exe",k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d-Q\\gm.exe",k); + if ((file=cimg::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\\%s.exe",k,s_exe); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + cimg_snprintf(s_path,s_path._width,"D:\\GRAPHI~1.%d\\gm.exe",k); + if ((file=cimg::std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } } - if (!path_found) cimg_snprintf(s_path,s_path._width,"%s.exe",s_exe); - } + 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=cimg::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=cimg::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=cimg::std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + } + if (!path_found) std::strcpy(s_path,"gm.exe"); #else - std::strcpy(s_path,"./magick"); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } - 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"); + if (!path_found) { + std::strcpy(s_path,"./gm"); + if ((file=cimg::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); + winformat_string(s_path); + } + cimg::mutex(7,0); + return 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 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; + //! 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 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"); + if (win_searchpath("gunzip.exe",s_path,s_path._width)) path_found = true; + if (!path_found) { + std::strcpy(s_path,".\\gunzip.exe"); + if ((file=cimg::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,"./gm"); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } - } - if (!path_found) std::strcpy(s_path,"gm"); + if (!path_found) { + std::strcpy(s_path,"./gunzip"); + if ((file=cimg::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); + winformat_string(s_path); + } + cimg::mutex(7,0); + return 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 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; + //! 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 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"); + if (win_searchpath("gzip.exe",s_path,s_path._width)) path_found = true; + if (!path_found) { + std::strcpy(s_path,".\\gzip.exe"); + if ((file=cimg::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,"./medcon"); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } - } - if (!path_found) std::strcpy(s_path,"medcon"); + if (!path_found) { + std::strcpy(s_path,"./gzip"); + if ((file=cimg::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 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 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; } + winformat_string(s_path); } - if (!path_found) std::strcpy(s_path,"ffmpeg.exe"); + cimg::mutex(7,0); + return s_path; + } + + //! 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 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 (win_searchpath("magick.exe",s_path,s_path._width)) path_found = true; + const char *const pf_path = win_programfiles_path(); + for (int l = 0; l<2 && !path_found; ++l) { + const char *const s_exe = l?"convert":"magick"; + cimg_snprintf(s_path,s_path._width,".\\%s.exe",s_exe); + if ((file=cimg::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-\\%s.exe",pf_path,k,s_exe); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); + if ((file=cimg::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\\%s.exe",pf_path,k,s_exe); + if ((file=cimg::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-\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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-\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::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\\%s.exe",k,s_exe); + if ((file=cimg::std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + } + if (!path_found) cimg_snprintf(s_path,s_path._width,"%s.exe",s_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"); + std::strcpy(s_path,"./magick"); + if ((file=cimg::std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + if (!path_found) { + std::strcpy(s_path,"./convert"); + if ((file=cimg::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 \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 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; } + winformat_string(s_path); } - if (!path_found) std::strcpy(s_path,"gzip.exe"); + cimg::mutex(7,0); + return s_path; + } + + //! Get/set path to the Medcon'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 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 (win_searchpath("medcon.exe",s_path,s_path._width)) path_found = true; + const char *const pf_path = win_programfiles_path(); + if (!path_found) { + std::strcpy(s_path,".\\medcon.exe"); + if ((file=cimg::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=cimg::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=cimg::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=cimg::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,"./gzip"); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } - } - if (!path_found) std::strcpy(s_path,"gzip"); + if (!path_found) { + std::strcpy(s_path,"./medcon"); + if ((file=cimg::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 \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 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; } + winformat_string(s_path); } - if (!path_found) std::strcpy(s_path,"gunzip.exe"); + cimg::mutex(7,0); + return s_path; + } + + //! 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=cimg::std_fopen(tmp,"wb"))!=0) { cimg::fclose(file); std::remove(tmp); path_found = true; } \ + } + static CImg 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 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 - 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"); + _cimg_test_temporary_path("/tmp"); + _cimg_test_temporary_path("/var/tmp"); #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 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) { + *s_path = 0; + std::strncpy(tmp,filename_tmp,tmp._width - 1); + if ((file=cimg::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"); + } } - if (!path_found) std::strcpy(s_path,"dcraw.exe"); + 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 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 (win_searchpath("wget.exe",s_path,s_path._width)) path_found = true; + if (!path_found) { + std::strcpy(s_path,".\\wget.exe"); + if ((file=cimg::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,"./dcraw"); - if ((file=std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } - } - if (!path_found) std::strcpy(s_path,"dcraw"); + if (!path_found) { + std::strcpy(s_path,"./wget"); + if ((file=cimg::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); + winformat_string(s_path); + } + cimg::mutex(7,0); + return 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 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; } + //! Get/set path to the \c powershell 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 *powershell_path(const char *const user_path, const bool reinit_path) { + static CImg 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 (win_searchpath("powershell.exe",s_path,s_path._width)) path_found = true; + if (!path_found) { + std::strcpy(s_path,".\\powershell.exe"); + if ((file=cimg::std_fopen(s_path,"r"))!=0) { cimg::fclose(file); path_found = true; } + } + if (!path_found) std::strcpy(s_path,"powershell.exe"); + winformat_string(s_path); } - 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; } - cimg::mutex(7,0); - return s_path; - } +#endif - //! 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 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; + // [internal] Sorting function, used by cimg::files(). + inline int _sort_files(const void* a, const void* b) { + const CImg &sa = *(CImg*)a, &sb = *(CImg*)b; + return std::strcmp(sa._data,sb._data); + } + + //! 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; } + const unsigned int siz = (unsigned int)std::strlen(filename); + CImg format(16), body(siz + 32); + const char *const ext = cimg::split_filename(filename,body); + if (*ext) cimg_snprintf(format,format._width,"%%s_%%.%ud.%%s",digits); + else cimg_snprintf(format,format._width,"%%s_%%.%ud",digits); + cimg_snprintf(str,1024,format._data,body._data,number,ext); + return str; + } + + //! 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 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 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 pattern, _path = CImg::string(path); #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"); + for (char *ps = _path; *ps; ++ps) if (*ps=='\\') *ps='/'; #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 &sa = *(CImg*)a, &sb = *(CImg*)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 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 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 pattern, _path = CImg::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::string(_path).move_to(pattern); - if (bpos) { - _path[bpos - 1] = 0; // End 'path' at last slash. + 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 - } 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::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 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(filename,lf + 1).move_to(res); + // Separate folder path and matching pattern. + if (_is_pattern) { + const unsigned int bpos = (unsigned int)(cimg::basename(_path,'/') - _path.data()); + CImg::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); } - } 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::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 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))) + // 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::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 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 { - if (!_is_pattern || (_is_pattern && !fnmatch(pattern,full_filename,0))) - CImg(filename,lf + 1).move_to(res); + } else CImg(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::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 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(filename,lf + 1).move_to(res); + } } } } - } - closedir(dir); + closedir(dir); #endif - // Sort resulting list by lexicographic order. - if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg),_sort_files); + // Sort resulting list by lexicographic order. + if (res._width>=2) std::qsort(res._data,res._width,sizeof(CImg),_sort_files); - return res; - } + 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 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 _header = header.get_split(CImg::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; } + //! 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 _bmp = "bmp", + *const _cr2 = "cr2", + *const _dcm = "dcm", + *const _gif = "gif", + *const _inr = "inr", + *const _jpg = "jpg", + *const _off = "off", + *const _pan = "pan", + *const _pfm = "pfm", + *const _png = "png", + *const _pnm = "pnm", + *const _tif = "tif"; + + const char *f_type = 0; + CImg 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)) // INRIMAGE + f_type = _inr; + else if (!std::strncmp(header,"PANDORE",7)) // PANDORE + f_type = _pan; + else if (!std::strncmp(header.data() + 128,"DICM",4)) // DICOM + f_type = _dcm; + else if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) // JPEG + f_type = _jpg; + else if (header[0]=='B' && header[1]=='M') // BMP + f_type = _bmp; + else if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' && + (header[4]=='7' || header[4]=='9')) // GIF + f_type = _gif; + else if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 && + uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) // PNG + f_type = _png; + else if (uheader[0]==0x49 && uheader[1]==0x49 && uheader[2]==0x2A && uheader[3]==0x00 && // CR2 + uheader[4]==0x10 && uheader[5]==0x00 && uheader[6]==0x00 && uheader[7]==0x00 && + uheader[8]==0x43 && uheader[9]==0x52) + f_type = _cr2; + else if ((uheader[0]==0x49 && uheader[1]==0x49 && uheader[2]==0x2A && uheader[3]==0x00) || + (uheader[0]==0x4D && uheader[1]==0x4D && uheader[2]==0x00 && uheader[3]==0x2A)) // TIFF + f_type = _tif; + else { // PNM or PFM + CImgList _header = header.get_split(CImg::vector('\n'),0,false); + cimglist_for(_header,l) { + if (_header(l,0)=='#') continue; + if (_header[l]._width==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; } - f_type = 0; break; } - } - } catch (CImgIOException&) { } - cimg::exception_mode(omode); - return f_type; - } + } catch (CImgIOException&) { } + cimg::exception_mode(omode); + return f_type; + } - //! Load file from network as a local temporary file. - /** - \param url URL of the 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. - \param referer Referer used, as a C-string. - \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)."); + //! Load file from network as a local temporary file. + /** + \param url URL of the 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. + \param referer Referer used, as a C-string. + \param user_agent User agent used, as a C-string. + \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, const char *const user_agent) { + if (!url) + throw CImgArgumentException("cimg::load_network(): Specified URL is (null)."); + if (!filename_local) + throw CImgArgumentException("cimg::load_network(): Specified destination string is (null)."); + if (!network_mode()) + throw CImgIOException("cimg::load_network(): Loading files from network is disabled."); - const char *const __ext = cimg::split_filename(url), *const _ext = (*__ext && __ext>url)?__ext - 1:__ext; - CImg ext = CImg::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); + const char *const __ext = cimg::split_filename(url), *const _ext = (*__ext && __ext>url)?__ext - 1:__ext; + CImg ext = CImg::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 = cimg::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); + 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); + curl_easy_setopt(curl,CURLOPT_MAXREDIRS,20L); + 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); + if (user_agent) curl_easy_setopt(curl,CURLOPT_USERAGENT,user_agent); + 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 command((unsigned int)std::strlen(url) + 64); - cimg::unused(try_fallback); + CImg command((unsigned int)std::strlen(url) + 1024), s_referer, s_user_agent, s_timeout; + 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); + // Try with 'curl' first. + if (timeout) cimg_snprintf(s_timeout.assign(64),64,"-m %u ",timeout); + else s_timeout.assign(1,1,1,1,0); + if (referer) cimg_snprintf(s_referer.assign(1024),1024,"-e %s ",referer); + else s_referer.assign(1,1,1,1,0); + if (user_agent) cimg_snprintf(s_user_agent.assign(1024),1024,"-A \"%s\" ",user_agent); + else s_user_agent.assign(1,1,1,1,0); + cimg_snprintf(command,command._width, + "\"%s\" -L --max-redirs 20 %s%s%s-f --silent --compressed -o \"%s\" \"%s\"", + cimg::curl_path(),s_timeout._data,s_referer._data,s_user_agent._data,filename_local, + CImg::string(url)._system_strescape().data()); + cimg::system(command,cimg::curl_path()); - 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); +#if cimg_OS==2 + if (cimg::fsize(filename_local)<=0) { // Try with 'powershell' otherwise. + if (timeout) cimg_snprintf(s_timeout.assign(64),64,"-TimeoutSec %u ",timeout); + else s_timeout.assign(1,1,1,1,0); + if (referer) cimg_snprintf(s_referer.assign(1024),1024,"-Headers @{'Referer'='%s'} ",referer); + else s_referer.assign(1,1,1,1,0); + if (user_agent) cimg_snprintf(s_user_agent.assign(1024),1024,"-UserAgent \"%s\" ",user_agent); + else s_user_agent.assign(1,1,1,1,0); + cimg_snprintf(command,command._width, + "\"%s\" -NonInteractive -Command Invoke-WebRequest %s%s%s-OutFile \"%s\" -Uri \"%s\"", + cimg::powershell_path(),s_timeout._data,s_referer._data,s_user_agent._data,filename_local, + CImg::string(url)._system_strescape().data()); + cimg::system(command,cimg::powershell_path()); } - cimg::system(command); +#endif - 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); + if (cimg::fsize(filename_local)<=0) { // Try with 'wget' otherwise. + if (timeout) cimg_snprintf(s_timeout.assign(64),64,"-T %u ",timeout); + else s_timeout.assign(1,1,1,1,0); + if (referer) cimg_snprintf(s_referer.assign(1024),1024,"--referer=%s ",referer); + else s_referer.assign(1,1,1,1,0); + if (user_agent) cimg_snprintf(s_user_agent.assign(1024),1024,"--user-agent=\"%s\" ",user_agent); + else s_user_agent.assign(1,1,1,1,0); + cimg_snprintf(command,command._width, + "\"%s\" --max-redirect=20 %s%s%s-q -r -l 0 --no-cache -O \"%s\" \"%s\"", + cimg::wget_path(),s_timeout._data,s_referer._data,s_user_agent._data,filename_local, + CImg::string(url)._system_strescape().data()); + cimg::system(command,cimg::wget_path()); - // 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 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::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 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 - 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& 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."); + if (cimg::fsize(filename_local)<=0) + throw CImgIOException("cimg::load_network(): Failed to load file '%s' with external commands " +#if cimg_OS==2 + "'wget', 'curl', or 'powershell'.",url); #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 buttons, cbuttons, sbuttons; - if (button1_label) { CImg().draw_text(0,0,button1_label,black,gray,1,13).move_to(buttons); - if (button2_label) { CImg().draw_text(0,0,button2_label,black,gray,1,13).move_to(buttons); - if (button3_label) { CImg().draw_text(0,0,button3_label,black,gray,1,13).move_to(buttons); - if (button4_label) { CImg().draw_text(0,0,button4_label,black,gray,1,13).move_to(buttons); - if (button5_label) { CImg().draw_text(0,0,button5_label,black,gray,1,13).move_to(buttons); - if (button6_label) { CImg().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 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 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 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(cbutton). - draw_image(1 + (bw -buttons[ll].width())/2,1 + (bh - buttons[ll].height())/2,buttons[ll]). - move_to(cbuttons); - CImg(sbutton). - draw_image((bw - buttons[ll].width())/2,(bh - buttons[ll].height())/2,buttons[ll]). - move_to(sbuttons); - CImg(button). - draw_image((bw - buttons[ll].width())/2,(bh - buttons[ll].height())/2,buttons[ll]). - move_to(buttons[ll]); - } - - CImg canvas; - if (msg) - ((CImg().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(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(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(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp); - else { - if (selected>=0) - CImg(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; + "'wget' or 'curl'.",url); #endif - } + cimg::fclose(file); - //! 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::_logo40x38(),is_centered); - } + // 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,gunzip_path()); + file = cimg::std_fopen(filename_local,"rb"); + if (!file) { + cimg_snprintf(command,command._width,"%s.gz",filename_local); + std::rename(command,filename_local); + file = cimg::std_fopen(filename_local,"rb"); + } + } - //! 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 empty; - return empty.eval(expression,x,y,z,c); - } + if (file) cimg::fclose(file); + return filename_local; + } - template - inline CImg::type> eval(const char *const expression, const CImg& xyzc) { - static const CImg empty; - return empty.eval(expression,xyzc); - } + // Implement a tic/toc mechanism to display elapsed time of algorithms. + inline cimg_uint64 tictoc(const bool is_tic) { + cimg::mutex(2); + static CImg times(64); + static unsigned int pos = 0; + const cimg_uint64 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; + } - // End of cimg:: namespace -} + // Toc + if (!pos) + throw CImgArgumentException("cimg::toc(): No previous call to 'cimg::tic()' has been made."); + const cimg_uint64 + t0 = times[--pos], + dt = t1>=t0?(t1 - t0):cimg::type::max(); + const unsigned int + edays = (unsigned int)(dt/86400000.), + ehours = (unsigned int)((dt - edays*86400000.)/3600000.), + emin = (unsigned int)((dt - edays*86400000. - ehours*3600000.)/60000.), + esec = (unsigned int)((dt - edays*86400000. - ehours*3600000. - emin*60000.)/1000.), + ems = (unsigned int)(dt - edays*86400000. - ehours*3600000. - emin*60000. - esec*1000.); + 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; + } - // End of cimg_library:: namespace -} + // Return a temporary string describing the size of a memory buffer. + inline const char *strbuffersize(const cimg_ulong size) { + static CImg 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.f; cimg_snprintf(res,res._width,"%.1f Kio",nsize); } + else if (size<1024*1024*1024LU) { + const float nsize = size/(1024*1024.f); cimg_snprintf(res,res._width,"%.1f Mio",nsize); + } else { const float nsize = size/(1024*1024*1024.f); 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 Index 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 following) will not appear in the dialog box. + At least one button must be specified. + **/ + template + 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& 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 buttons, cbuttons, sbuttons; + if (button1_label) { + CImg().draw_text(0,0,button1_label,black,gray,1,13).move_to(buttons); + if (button2_label) { + CImg().draw_text(0,0,button2_label,black,gray,1,13).move_to(buttons); + if (button3_label) { + CImg().draw_text(0,0,button3_label,black,gray,1,13).move_to(buttons); + if (button4_label) { + CImg().draw_text(0,0,button4_label,black,gray,1,13).move_to(buttons); + if (button5_label) { + CImg().draw_text(0,0,button5_label,black,gray,1,13).move_to(buttons); + if (button6_label) { + CImg().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 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 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 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(cbutton). + draw_image(1 + (bw -buttons[ll].width())/2,1 + (bh - buttons[ll].height())/2,buttons[ll]). + move_to(cbuttons); + CImg(sbutton). + draw_image((bw - buttons[ll].width())/2,(bh - buttons[ll].height())/2,buttons[ll]). + move_to(sbuttons); + CImg(button). + draw_image((bw - buttons[ll].width())/2,(bh - buttons[ll].height())/2,buttons[ll]). + move_to(buttons[ll]); + } + + CImg canvas; + if (msg) + ((CImg().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(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(w,h,1,3). + draw_rectangle(0,0,w - 1,h - 1,gray). + draw_line(0,0,w - 1,0,white).draw_line(0,h - 1,0,0,white). + draw_line(w - 1,0,w - 1,h - 1,black).draw_line(w - 1,h - 1,0,h - 1,black); + if (logo._data) canvas.draw_image(lx,ly,logo); + + unsigned int xbuttons[6] = {}; + cimglist_for(buttons,lll) { + xbuttons[lll] = bx + (bw + 12)*lll; + canvas.draw_image(xbuttons[lll],by,buttons[lll]); + } + + // Open window and enter events loop + CImgDisplay disp(canvas,title?title:" ",0,false,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(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp); + else { + if (selected>=0) + CImg(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::_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 empty; + return empty.eval(expression,x,y,z,c); + } + + template + inline CImg::type> eval(const char *const expression, const CImg& xyzc) { + static const CImg empty; + return empty.eval(expression,xyzc); + } + + } // namespace cimg { ... +} // namespace cimg_library { ... //! Short alias name. -namespace cil = cimg_library_suffixed; +namespace cil = cimg_library; #ifdef _cimg_redefine_False #define False 0 @@ -60904,6 +67663,12 @@ namespace cil = cimg_library_suffixed; #ifdef _cimg_redefine_True #define True 1 #endif +#ifdef _cimg_redefine_Status +#define Status int +#endif +#ifdef _cimg_redefine_Success +#define Success 0 +#endif #ifdef _cimg_redefine_min #define min(a,b) (((a)<(b))?(a):(b)) #endif @@ -60918,6 +67683,7 @@ namespace cil = cimg_library_suffixed; #endif #endif + // Local Variables: // mode: c++ // End: diff --git a/src/main/cpp/Makefile b/src/Makefile similarity index 92% rename from src/main/cpp/Makefile rename to src/Makefile index 6da40ed..36b782b 100644 --- a/src/main/cpp/Makefile +++ b/src/Makefile @@ -12,7 +12,7 @@ prefix ?= /usr/local exec_prefix ?= $(prefix) bindir ?= $(exec_prefix)/bin -override CXXFLAGS += -std=c++17 -Wall -fexceptions +override CXXFLAGS += -std=c++20 -Wall -fexceptions override LDFLAGS += -pthread all: $(PROGNAME) diff --git a/src/main/java/TerminalImageViewer.java b/src/java/TerminalImageViewer.java similarity index 99% rename from src/main/java/TerminalImageViewer.java rename to src/java/TerminalImageViewer.java index 15ed801..aca7a95 100644 --- a/src/main/java/TerminalImageViewer.java +++ b/src/java/TerminalImageViewer.java @@ -34,7 +34,7 @@ public class TerminalImageViewer { " - Use -w and -h to set the maximum width and height in characters (defaults: 80, 24).\n" + " - Use -256 for 256 color mode, -grayscale for grayscale and -stdin to obtain file names from stdin.\n" + " - When multiple files are supplied, -c sets the number of images per row (default: 4)." + - "NOTE: This version of TerminalImageViewer is not regularly updated. Please use the C++ version instead.\n"); + "NOTE: This version of TerminalImageViewer is deprecated. Please use the C++ version instead.\n"); return; } diff --git a/src/main/cpp/tiv.cpp b/src/main/cpp/tiv.cpp deleted file mode 100644 index 89bf538..0000000 --- a/src/main/cpp/tiv.cpp +++ /dev/null @@ -1,648 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define cimg_display 0 -#include "CImg.h" - -//using namespace std; - -const int FLAG_FG = 1; -const int FLAG_BG = 2; -const int FLAG_MODE_256 = 4; -const int FLAG_24BIT = 8; -const int FLAG_NOOPT = 16; -const int FLAG_TELETEXT = 32; - -const int COLOR_STEP_COUNT = 6; -const int COLOR_STEPS[COLOR_STEP_COUNT] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; - -const int GRAYSCALE_STEP_COUNT = 24; -const int GRAYSCALE_STEPS[GRAYSCALE_STEP_COUNT] = { - 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76, - 0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee}; - -const unsigned int BITMAPS[] = { - 0x00000000, 0x00a0, - - // Block graphics - // 0xffff0000, 0x2580, // upper 1/2; redundant with inverse lower 1/2 - - 0x0000000f, 0x2581, // lower 1/8 - 0x000000ff, 0x2582, // lower 1/4 - 0x00000fff, 0x2583, - 0x0000ffff, 0x2584, // lower 1/2 - 0x000fffff, 0x2585, - 0x00ffffff, 0x2586, // lower 3/4 - 0x0fffffff, 0x2587, -//0xffffffff, 0x2588, // full; redundant with inverse space - - 0xeeeeeeee, 0x258a, // left 3/4 - 0xcccccccc, 0x258c, // left 1/2 - 0x88888888, 0x258e, // left 1/4 - - 0x0000cccc, 0x2596, // quadrant lower left - 0x00003333, 0x2597, // quadrant lower right - 0xcccc0000, 0x2598, // quadrant upper left -//0xccccffff, 0x2599, // 3/4 redundant with inverse 1/4 - 0xcccc3333, 0x259a, // diagonal 1/2 -//0xffffcccc, 0x259b, // 3/4 redundant -//0xffff3333, 0x259c, // 3/4 redundant - 0x33330000, 0x259d, // quadrant upper right -//0x3333cccc, 0x259e, // 3/4 redundant -//0x3333ffff, 0x259f, // 3/4 redundant - -// Line drawing subset: no double lines, no complex light lines - - 0x000ff000, 0x2501, // Heavy horizontal - 0x66666666, 0x2503, // Heavy vertical - - 0x00077666, 0x250f, // Heavy down and right - 0x000ee666, 0x2513, // Heavy down and left - 0x66677000, 0x2517, // Heavy up and right - 0x666ee000, 0x251b, // Heavy up and left - - 0x66677666, 0x2523, // Heavy vertical and right - 0x666ee666, 0x252b, // Heavy vertical and left - 0x000ff666, 0x2533, // Heavy down and horizontal - 0x666ff000, 0x253b, // Heavy up and horizontal - 0x666ff666, 0x254b, // Heavy cross - - 0x000cc000, 0x2578, // Bold horizontal left - 0x00066000, 0x2579, // Bold horizontal up - 0x00033000, 0x257a, // Bold horizontal right - 0x00066000, 0x257b, // Bold horizontal down - - 0x06600660, 0x254f, // Heavy double dash vertical - - 0x000f0000, 0x2500, // Light horizontal - 0x0000f000, 0x2500, // - 0x44444444, 0x2502, // Light vertical - 0x22222222, 0x2502, - - 0x000e0000, 0x2574, // light left - 0x0000e000, 0x2574, // light left - 0x44440000, 0x2575, // light up - 0x22220000, 0x2575, // light up - 0x00030000, 0x2576, // light right - 0x00003000, 0x2576, // light right - 0x00004444, 0x2577, // light down - 0x00002222, 0x2577, // light down - -// Misc technical - - 0x44444444, 0x23a2, // [ extension - 0x22222222, 0x23a5, // ] extension - - 0x0f000000, 0x23ba, // Horizontal scanline 1 - 0x00f00000, 0x23bb, // Horizontal scanline 3 - 0x00000f00, 0x23bc, // Horizontal scanline 7 - 0x000000f0, 0x23bd, // Horizontal scanline 9 - -// Geometrical shapes. Tricky because some of them are too wide. - -//0x00ffff00, 0x25fe, // Black medium small square - 0x00066000, 0x25aa, // Black small square - -//0x11224488, 0x2571, // diagonals -//0x88442211, 0x2572, -//0x99666699, 0x2573, -//0x000137f0, 0x25e2, // Triangles -//0x0008cef0, 0x25e3, -//0x000fec80, 0x25e4, -//0x000f7310, 0x25e5, - - 0, 0, // End marker for "regular" characters - - // Teletext / legacy graphics 3x2 block character codes. - // Using a 3-2-3 pattern consistently, perhaps we should create automatic variations.... - - 0xccc00000, 0xfb00, - 0x33300000, 0xfb01, - 0xfff00000, 0xfb02, - 0x000cc000, 0xfb03, - 0xccccc000, 0xfb04, - 0x333cc000, 0xfb05, - 0xfffcc000, 0xfb06, - 0x00033000, 0xfb07, - 0xccc33000, 0xfb08, - 0x33333000, 0xfb09, - 0xfff33000, 0xfb0a, - 0x000ff000, 0xfb0b, - 0xcccff000, 0xfb0c, - 0x333ff000, 0xfb0d, - 0xfffff000, 0xfb0e, - 0x00000ccc, 0xfb0f, - - 0xccc00ccc, 0xfb10, - 0x33300ccc, 0xfb11, - 0xfff00ccc, 0xfb12, - 0x000ccccc, 0xfb13, - 0x333ccccc, 0xfb14, - 0xfffccccc, 0xfb15, - 0x00033ccc, 0xfb16, - 0xccc33ccc, 0xfb17, - 0x33333ccc, 0xfb18, - 0xfff33ccc, 0xfb19, - 0x000ffccc, 0xfb1a, - 0xcccffccc, 0xfb1b, - 0x333ffccc, 0xfb1c, - 0xfffffccc, 0xfb1d, - 0x00000333, 0xfb1e, - 0xccc00333, 0xfb1f, - - 0x33300333, 0x1b20, - 0xfff00333, 0x1b21, - 0x000cc333, 0x1b22, - 0xccccc333, 0x1b23, - 0x333cc333, 0x1b24, - 0xfffcc333, 0x1b25, - 0x00033333, 0x1b26, - 0xccc33333, 0x1b27, - 0xfff33333, 0x1b28, - 0x000ff333, 0x1b29, - 0xcccff333, 0x1b2a, - 0x333ff333, 0x1b2b, - 0xfffff333, 0x1b2c, - 0x00000fff, 0x1b2d, - 0xccc00fff, 0x1b2e, - 0x33300fff, 0x1b2f, - - 0xfff00fff, 0x1b30, - 0x000ccfff, 0x1b31, - 0xcccccfff, 0x1b32, - 0x333ccfff, 0x1b33, - 0xfffccfff, 0x1b34, - 0x00033fff, 0x1b35, - 0xccc33fff, 0x1b36, - 0x33333fff, 0x1b37, - 0xfff33fff, 0x1b38, - 0x000fffff, 0x1b39, - 0xcccfffff, 0x1b3a, - 0x333fffff, 0x1b3b, - - - 0, 1 // End marker for extended TELETEXT mode. -}; - - -struct CharData { - std::array fgColor = std::array{0, 0, 0}; - std::array bgColor = std::array{0, 0, 0}; - int codePoint; -}; - - -// Return a CharData struct with the given code point and corresponding averag fg and bg colors. -CharData createCharData(const cimg_library::CImg & image, int x0, int y0, int codepoint, int pattern) { - CharData result; - result.codePoint = codepoint; - int fg_count = 0; - int bg_count = 0; - unsigned int mask = 0x80000000; - - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - int* avg; - if (pattern & mask) { - avg = result.fgColor.data(); - fg_count++; - } else { - avg = result.bgColor.data(); - bg_count++; - } - for (int i = 0; i < 3; i++) { - avg[i] += image(x0 + x, y0 + y, 0, i); - } - mask = mask >> 1; - } - } - - // Calculate the average color value for each bucket - for (int i = 0; i < 3; i++) { - if (bg_count != 0) { - result.bgColor[i] /= bg_count; - } - if (fg_count != 0) { - result.fgColor[i] /= fg_count; - } - } - return result; -} - - -// Find the best character and colors for a 4x8 part of the image at the given position -CharData findCharData(const cimg_library::CImg & image, int x0, int y0, int flags) { - int min[3] = {255, 255, 255}; - int max[3] = {0}; - std::map count_per_color; - - // Determine the minimum and maximum value for each color channel - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - long color = 0; - for (int i = 0; i < 3; i++) { - int d = image(x0 + x, y0 + y, 0, i); - min[i] = std::min(min[i], d); - max[i] = std::max(max[i], d); - color = (color << 8) | d; - } - count_per_color[color]++; - } - } - - std::multimap color_per_count; - for (auto i = count_per_color.begin(); i != count_per_color.end(); ++i) { - color_per_count.insert(std::pair(i->second, i->first)); - } - - auto iter = color_per_count.rbegin(); - int count2 = iter->first; - long max_count_color_1 = iter->second; - long max_count_color_2 = max_count_color_1; - if ((++iter) != color_per_count.rend()) { - count2 += iter->first; - max_count_color_2 = iter->second; - } - - unsigned int bits = 0; - bool direct = count2 > (8*4) / 2; - - if (direct) { - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - bits = bits << 1; - int d1 = 0; - int d2 = 0; - for (int i = 0; i < 3; i++) { - int shift = 16 - 8 * i; - int c1 = (max_count_color_1 >> shift) & 255; - int c2 = (max_count_color_2 >> shift) & 255; - int c = image(x0 + x, y0 + y, 0, i); - d1 += (c1-c) * (c1-c); - d2 += (c2-c) * (c2-c); - } - if (d1 > d2) { - bits |= 1; - } - } - } - - } else { - // Determine the color channel with the greatest range. - int splitIndex = 0; - int bestSplit = 0; - for (int i = 0; i < 3; i++) { - if (max[i] - min[i] > bestSplit) { - bestSplit = max[i] - min[i]; - splitIndex = i; - } - } - - // We just split at the middle of the interval instead of computing the median. - int splitValue = min[splitIndex] + bestSplit / 2; - - // Compute a bitmap using the given split and sum the color values for both buckets. - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - bits = bits << 1; - if (image(x0 + x, y0 + y, 0, splitIndex) > splitValue) { - bits |= 1; - } - } - } - } - - // Find the best bitmap match by counting the bits that don't match, - // including the inverted bitmaps. - int best_diff = 8; - unsigned int best_pattern = 0x0000ffff; - int codepoint = 0x2584; - bool inverted = false; - unsigned int end_marker = flags & FLAG_TELETEXT ? 1 : 0; - for (int i = 0; BITMAPS[i + 1] != end_marker; i += 2) { - // Skip all end markers - if (BITMAPS[i + 1] < 32) { - continue; - } - unsigned int pattern = BITMAPS[i]; - for (int j = 0; j < 2; j++) { - int diff = (std::bitset<32>(pattern ^ bits)).count(); - if (diff < best_diff) { - best_pattern = BITMAPS[i]; // pattern might be inverted. - codepoint = BITMAPS[i + 1]; - best_diff = diff; - inverted = best_pattern != pattern; - } - pattern = ~pattern; - } - } - - if (direct) { - CharData result; - if (inverted) { - long tmp = max_count_color_1; - max_count_color_1 = max_count_color_2; - max_count_color_2 = tmp; - } - for (int i = 0; i < 3; i++) { - int shift = 16 - 8 * i; - result.fgColor[i] = (max_count_color_2 >> shift) & 255; - result.bgColor[i] = (max_count_color_1 >> shift) & 255; - result.codePoint = codepoint; - } - return result; - } - return createCharData(image, x0, y0, codepoint, best_pattern); -} - - -int clamp_byte(int value) { - return value < 0 ? 0 : (value > 255 ? 255 : value); -} - - -double sqr(double n) { - return n*n; -} - - -int best_index(int value, const int data[], int count) { - int best_diff = std::abs(data[0] - value); - int result = 0; - for (int i = 1; i < count; i++) { - int diff = std::abs(data[i] - value); - if (diff < best_diff) { - result = i; - best_diff = diff; - } - } - return result; -} - - -void emit_color(int flags, int r, int g, int b) { - r = clamp_byte(r); - g = clamp_byte(g); - b = clamp_byte(b); - - bool bg = (flags & FLAG_BG) != 0; - - if ((flags & FLAG_MODE_256) == 0) { - std::cout << (bg ? "\x1b[48;2;" : "\x1b[38;2;") << r << ';' << g << ';' << b << 'm'; - return; - } - - int ri = best_index(r, COLOR_STEPS, COLOR_STEP_COUNT); - int gi = best_index(g, COLOR_STEPS, COLOR_STEP_COUNT); - int bi = best_index(b, COLOR_STEPS, COLOR_STEP_COUNT); - - int rq = COLOR_STEPS[ri]; - int gq = COLOR_STEPS[gi]; - int bq = COLOR_STEPS[bi]; - - int gray = static_cast(std::round(r * 0.2989f + g * 0.5870f + b * 0.1140f)); - - int gri = best_index(gray, GRAYSCALE_STEPS, GRAYSCALE_STEP_COUNT); - int grq = GRAYSCALE_STEPS[gri]; - - int color_index; - if (0.3 * sqr(rq-r) + 0.59 * sqr(gq-g) + 0.11 * sqr(bq-b) < - 0.3 * sqr(grq-r) + 0.59 * sqr(grq-g) + 0.11 * sqr(grq-b)) { - color_index = 16 + 36 * ri + 6 * gi + bi; - } else { - color_index = 232 + gri; // 1..24 -> 232..255 - } - std::cout << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m"; -} - - -void emitCodepoint(int codepoint) { - if (codepoint < 128) { - std::cout << (char) codepoint; - } else if (codepoint < 0x7ff) { - std::cout << (char) (0xc0 | (codepoint >> 6)); - std::cout << (char) (0x80 | (codepoint & 0x3f)); - } else if (codepoint < 0xffff) { - std::cout << (char) (0xe0 | (codepoint >> 12)); - std::cout << (char) (0x80 | ((codepoint >> 6) & 0x3f)); - std::cout << (char) (0x80 | (codepoint & 0x3f)); - } else if (codepoint < 0x10ffff) { - std::cout << (char) (0xf0 | (codepoint >> 18)); - std::cout << (char) (0x80 | ((codepoint >> 12) & 0x3f)); - std::cout << (char) (0x80 | ((codepoint >> 6) & 0x3f)); - std::cout << (char) (0x80 | (codepoint & 0x3f)); - } else { - std::cerr << "ERROR"; - } -} - - -void emit_image(const cimg_library::CImg & image, int flags) { - CharData lastCharData; - for (int y = 0; y <= image.height() - 8; y += 8) { - for (int x = 0; x <= image.width() - 4; x += 4) { - CharData charData = flags & FLAG_NOOPT - ? createCharData(image, x, y, 0x2584, 0x0000ffff) - : findCharData(image, x, y, flags); - if (x == 0 || charData.bgColor != lastCharData.bgColor) - emit_color(flags | FLAG_BG, charData.bgColor[0], charData.bgColor[1], charData.bgColor[2]); - if (x == 0 || charData.fgColor != lastCharData.fgColor) - emit_color(flags | FLAG_FG, charData.fgColor[0], charData.fgColor[1], charData.fgColor[2]); - emitCodepoint(charData.codePoint); - lastCharData = charData; - } - std::cout << "\x1b[0m" << std::endl; - } -} - - -struct size { - size(unsigned int in_width, unsigned int in_height) : - width(in_width), height(in_height) { - } - size(cimg_library::CImg img) : - width(img.width()), height(img.height()) { - } - unsigned int width; - unsigned int height; - size scaled(double scale) { - return size(width*scale, height*scale); - } - size fitted_within(size container) { - double scale = std::min(container.width / (double) width, container.height / (double) height); - return scaled(scale); - } -}; -std::ostream& operator<<(std::ostream& stream, size sz) { - stream << sz.width << "x" << sz.height; - return stream; -} - - -void emit_usage() { - std::cerr << "Terminal Image Viewer v1.1.1" << std::endl << std::endl; - std::cerr << "usage: tiv [options] [...]" << std::endl << std::endl; - std::cerr << " -0 : No block character adjustment, always use top half block char." << std::endl; - std::cerr << " -256 : Use 256 color mode." << std::endl; - std::cerr << " -c : Number of thumbnail columns in 'dir' mode (3)." << std::endl; - std::cerr << " -d : Force 'dir' mode. Automatially selected for more than one input." << std::endl; - std::cerr << " -f : Force 'full' mode. Automatically selected for one input." << std::endl; - std::cerr << " -help : Display this help text." << std::endl; - std::cerr << " -h : Set the maximum height to lines." << std::endl; - std::cerr << " -w : Set the maximum width to characters." << std::endl; - std::cerr << " -x : Use new Unicode Teletext/legacy charactery (experimental)." << std::endl << std::endl; -} - -enum Mode {AUTO, THUMBNAILS, FULL_SIZE}; - - -/* Wrapper around CImg(const char*) to ensure the result has 3 channels as RGB - */ -cimg_library::CImg load_rgb_CImg(const char * const filename) { - cimg_library::CImg image(filename); - if(image.spectrum() == 1) { - // Greyscale. Just copy greyscale data to all channels - cimg_library::CImg rgb_image(image.width(), image.height(), image.depth(), 3); - for(unsigned int chn = 0; chn < 3; chn++) { - rgb_image.draw_image(0, 0, 0,chn, image); - } - return rgb_image; - } - - return image; -} - -int main(int argc, char* argv[]) { - int maxWidth = 80; - int maxHeight = 24; - bool sizeDetectionSuccessful = true; - struct winsize w; - int ioStatus = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - // If redirecting STDOUT to one file ( col or row == 0, or the previous ioctl call's failed ) - if (ioStatus != 0 || (w.ws_col | w.ws_row) == 0) { - ioStatus = ioctl(STDIN_FILENO, TIOCGWINSZ, &w); - if (ioStatus != 0 || (w.ws_col | w.ws_row) == 0) { - std::cerr << "Warning: failed to determine most reasonable size, defaulting to 80x24" << std::endl; - sizeDetectionSuccessful = false; - } - } - if (sizeDetectionSuccessful) - { - maxWidth = w.ws_col * 4; - maxHeight = w.ws_row * 8; - } - int flags = 0; - Mode mode = AUTO; - int columns = 3; - - std::vector file_names; - int error = 0; - - if (argc <= 1) { - emit_usage(); - return 0; - } - - for (int i = 1; i < argc; i++) { - std::string arg(argv[i]); - if (arg == "-0") { - flags |= FLAG_NOOPT; - } else if (arg == "-c") { - if (i < argc - 1) - columns = std::stoi(argv[++i]); - } else if (arg == "-d") { - mode = THUMBNAILS; - } else if (arg == "-f") { - mode = FULL_SIZE; - } else if (arg == "-w") { - if (i < argc - 1) - maxWidth = 4 * std::stoi(argv[++i]); - } else if (arg == "-h") { - if (i < argc - 1) - maxHeight = 8 * std::stoi(argv[++i]); - } else if (arg == "-256") { - flags |= FLAG_MODE_256; - } else if (arg == "--help" || arg == "-help") { - emit_usage(); - } else if (arg == "-x") { - flags |= FLAG_TELETEXT; - } else if (arg[0] == '-') { - std::cerr << "Unrecognized argument: " << arg << std::endl; - } else { - if (std::filesystem::is_directory(arg)) { - for (auto & p : std::filesystem::directory_iterator(arg)) { - if (std::filesystem::is_regular_file(p.path())) { - file_names.push_back(p.path().string()); - } - } - } else { - std::ifstream fin(arg.c_str()); - if (fin) { - file_names.push_back(arg); - } else { - std::cerr << "Can't open file (permission?): " << arg << std::endl; - } - } - } - } - - - if (mode == FULL_SIZE || (mode == AUTO && file_names.size() == 1)) { - for (unsigned int i = 0; i < file_names.size(); i++) { - try { - cimg_library::CImg image = load_rgb_CImg(file_names[i].c_str()); - - if (image.width() > maxWidth || image.height() > maxHeight) { - size new_size = size(image).fitted_within(size(maxWidth,maxHeight)); - image.resize(new_size.width, new_size.height, -100, -100, 5); - } - emit_image(image, flags); - } catch(cimg_library::CImgIOException & e) { - error = 1; - std::cerr << "File format is not recognized for '" << file_names[i] << "'" << std::endl; - } - } - } else { - // Thumbnail mode - - unsigned int index = 0; - int cw = (((maxWidth / 4) - 2 * (columns - 1)) / columns); - int tw = cw * 4; - cimg_library::CImg image(tw * columns + 2 * 4 * (columns - 1), tw, 1, 3); - size maxThumbSize(tw, tw); - - while (index < file_names.size()) { - image.fill(0); - int count = 0; - std::string sb; - while (index < file_names.size() && count < columns) { - std::string name = file_names[index++]; - try { - cimg_library::CImg original = load_rgb_CImg(name.c_str()); - auto cut = name.find_last_of("/"); - sb += cut == std::string::npos ? name : name.substr(cut + 1); - size newSize = size(original).fitted_within(maxThumbSize); - original.resize(newSize.width, newSize.height, 1, -100, 5); - image.draw_image(count * (tw + 8) + (tw - newSize.width) / 2, (tw - newSize.height) / 2, 0, 0, original); - count++; - unsigned int sl = count * (cw + 2); - sb.resize(sl - 2, ' '); - sb += " "; - } catch (std::exception & e) { - // Probably no image; ignore. - } - } - if (count) emit_image(image, flags); - std::cout << sb << std::endl << std::endl; - } - } - return error; -} diff --git a/src/tiv.cpp b/src/tiv.cpp new file mode 100644 index 0000000..a012a93 --- /dev/null +++ b/src/tiv.cpp @@ -0,0 +1,720 @@ +/* + * Copyright (c) 2023 Aaron Liu + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 2017—2021, Stefan Haustein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// CImg, the superior grafiks library +#define cimg_display 0 +#include "CImg.h" + +// First include for detecting console output size, +// everything else for exit codes +#ifdef _POSIX_VERSION +#include +#include +#endif + +#ifdef _WIN32 +#include + +// Following codes copied from /usr/include/sysexits.h, +// license: https://opensource.org/license/BSD-3-clause/ +#define EX_OK 0 /* successful termination */ +#define EX__BASE 64 /* base value for error messages */ +#define EX_USAGE 64 /* command line usage error */ +#define EX_DATAERR 65 /* data format error */ +#define EX_NOINPUT 66 /* cannot open input */ +#define EX_SOFTWARE 70 /* internal software error */ +#define EX_CANTCREAT 73 /* can't create (user) output file */ +#define EX_IOERR 74 /* input/output error */ +#define EX_TEMPFAIL 75 /* temp failure; user is invited to retry */ +#define EX_NOPERM 77 /* permission denied */ +#define EX_CONFIG 78 /* configuration error */ +#endif + +// using namespace std; // haha nope, bad style +// especially when we're also using the CImg namespace + +// Implementation of flag representation for flags in the main() method +constexpr int FLAG_FG = 1; +constexpr int FLAG_BG = 2; +constexpr int FLAG_MODE_256 = 4; +constexpr int FLAG_24BIT = 8; +constexpr int FLAG_NOOPT = 16; +constexpr int FLAG_TELETEXT = 32; + +// Steps (@TODO: Figure out what exactly they represent) +constexpr int COLOR_STEP_COUNT = 6; +constexpr int COLOR_STEPS[COLOR_STEP_COUNT] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; + +constexpr int GRAYSCALE_STEP_COUNT = 24; +constexpr int GRAYSCALE_STEPS[GRAYSCALE_STEP_COUNT] = { + 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76, + 0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee}; + +constexpr unsigned int BITMAPS[] = { + 0x00000000, 0x00a0, + + // Block graphics + // 0xffff0000, 0x2580, // upper 1/2; redundant with inverse lower 1/2 + + 0x0000000f, 0x2581, // lower 1/8 + 0x000000ff, 0x2582, // lower 1/4 + 0x00000fff, 0x2583, 0x0000ffff, 0x2584, // lower 1/2 + 0x000fffff, 0x2585, 0x00ffffff, 0x2586, // lower 3/4 + 0x0fffffff, 0x2587, + // 0xffffffff, 0x2588, // full; redundant with inverse space + + 0xeeeeeeee, 0x258a, // left 3/4 + 0xcccccccc, 0x258c, // left 1/2 + 0x88888888, 0x258e, // left 1/4 + + 0x0000cccc, 0x2596, // quadrant lower left + 0x00003333, 0x2597, // quadrant lower right + 0xcccc0000, + 0x2598, // quadrant upper left + // 0xccccffff, 0x2599, // 3/4 redundant with inverse 1/4 + 0xcccc3333, 0x259a, // diagonal 1/2 + // 0xffffcccc, 0x259b, // 3/4 redundant + // 0xffff3333, 0x259c, // 3/4 redundant + 0x33330000, 0x259d, // quadrant upper right + // 0x3333cccc, 0x259e, // 3/4 redundant + // 0x3333ffff, 0x259f, // 3/4 redundant + + // Line drawing subset: no double lines, no complex light lines + + 0x000ff000, 0x2501, // Heavy horizontal + 0x66666666, 0x2503, // Heavy vertical + + 0x00077666, 0x250f, // Heavy down and right + 0x000ee666, 0x2513, // Heavy down and left + 0x66677000, 0x2517, // Heavy up and right + 0x666ee000, 0x251b, // Heavy up and left + + 0x66677666, 0x2523, // Heavy vertical and right + 0x666ee666, 0x252b, // Heavy vertical and left + 0x000ff666, 0x2533, // Heavy down and horizontal + 0x666ff000, 0x253b, // Heavy up and horizontal + 0x666ff666, 0x254b, // Heavy cross + + 0x000cc000, 0x2578, // Bold horizontal left + 0x00066000, 0x2579, // Bold horizontal up + 0x00033000, 0x257a, // Bold horizontal right + 0x00066000, 0x257b, // Bold horizontal down + + 0x06600660, 0x254f, // Heavy double dash vertical + + 0x000f0000, 0x2500, // Light horizontal + 0x0000f000, 0x2500, // + 0x44444444, 0x2502, // Light vertical + 0x22222222, 0x2502, + + 0x000e0000, 0x2574, // light left + 0x0000e000, 0x2574, // light left + 0x44440000, 0x2575, // light up + 0x22220000, 0x2575, // light up + 0x00030000, 0x2576, // light right + 0x00003000, 0x2576, // light right + 0x00004444, 0x2577, // light down + 0x00002222, 0x2577, // light down + + // Misc technical + + 0x44444444, 0x23a2, // [ extension + 0x22222222, 0x23a5, // ] extension + + 0x0f000000, 0x23ba, // Horizontal scanline 1 + 0x00f00000, 0x23bb, // Horizontal scanline 3 + 0x00000f00, 0x23bc, // Horizontal scanline 7 + 0x000000f0, 0x23bd, // Horizontal scanline 9 + + // Geometrical shapes. Tricky because some of them are too wide. + + // 0x00ffff00, 0x25fe, // Black medium small square + 0x00066000, 0x25aa, // Black small square + + // 0x11224488, 0x2571, // diagonals + // 0x88442211, 0x2572, + // 0x99666699, 0x2573, + // 0x000137f0, 0x25e2, // Triangles + // 0x0008cef0, 0x25e3, + // 0x000fec80, 0x25e4, + // 0x000f7310, 0x25e5, + + 0, 0, // End marker for "regular" characters + + // Teletext / legacy graphics 3x2 block character codes. + // Using a 3-2-3 pattern consistently, perhaps we should create automatic + // variations.... + + 0xccc00000, 0xfb00, 0x33300000, 0xfb01, 0xfff00000, 0xfb02, 0x000cc000, + 0xfb03, 0xccccc000, 0xfb04, 0x333cc000, 0xfb05, 0xfffcc000, 0xfb06, + 0x00033000, 0xfb07, 0xccc33000, 0xfb08, 0x33333000, 0xfb09, 0xfff33000, + 0xfb0a, 0x000ff000, 0xfb0b, 0xcccff000, 0xfb0c, 0x333ff000, 0xfb0d, + 0xfffff000, 0xfb0e, 0x00000ccc, 0xfb0f, + + 0xccc00ccc, 0xfb10, 0x33300ccc, 0xfb11, 0xfff00ccc, 0xfb12, 0x000ccccc, + 0xfb13, 0x333ccccc, 0xfb14, 0xfffccccc, 0xfb15, 0x00033ccc, 0xfb16, + 0xccc33ccc, 0xfb17, 0x33333ccc, 0xfb18, 0xfff33ccc, 0xfb19, 0x000ffccc, + 0xfb1a, 0xcccffccc, 0xfb1b, 0x333ffccc, 0xfb1c, 0xfffffccc, 0xfb1d, + 0x00000333, 0xfb1e, 0xccc00333, 0xfb1f, + + 0x33300333, 0x1b20, 0xfff00333, 0x1b21, 0x000cc333, 0x1b22, 0xccccc333, + 0x1b23, 0x333cc333, 0x1b24, 0xfffcc333, 0x1b25, 0x00033333, 0x1b26, + 0xccc33333, 0x1b27, 0xfff33333, 0x1b28, 0x000ff333, 0x1b29, 0xcccff333, + 0x1b2a, 0x333ff333, 0x1b2b, 0xfffff333, 0x1b2c, 0x00000fff, 0x1b2d, + 0xccc00fff, 0x1b2e, 0x33300fff, 0x1b2f, + + 0xfff00fff, 0x1b30, 0x000ccfff, 0x1b31, 0xcccccfff, 0x1b32, 0x333ccfff, + 0x1b33, 0xfffccfff, 0x1b34, 0x00033fff, 0x1b35, 0xccc33fff, 0x1b36, + 0x33333fff, 0x1b37, 0xfff33fff, 0x1b38, 0x000fffff, 0x1b39, 0xcccfffff, + 0x1b3a, 0x333fffff, 0x1b3b, + + 0, 1 // End marker for extended TELETEXT mode. +}; + +struct CharData { + std::array fgColor = std::array{0, 0, 0}; + std::array bgColor = std::array{0, 0, 0}; + int codePoint; +}; + +// Return a CharData struct with the given code point and corresponding averag +// fg and bg colors. +CharData createCharData(const cimg_library::CImg &image, int x0, + int y0, int codepoint, int pattern) { + CharData result; + result.codePoint = codepoint; + int fg_count = 0; + int bg_count = 0; + unsigned int mask = 0x80000000; + + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + int *avg; + if (pattern & mask) { + avg = result.fgColor.data(); + fg_count++; + } else { + avg = result.bgColor.data(); + bg_count++; + } + for (int i = 0; i < 3; i++) { + avg[i] += image(x0 + x, y0 + y, 0, i); + } + mask = mask >> 1; + } + } + + // Calculate the average color value for each bucket + for (int i = 0; i < 3; i++) { + if (bg_count != 0) { + result.bgColor[i] /= bg_count; + } + if (fg_count != 0) { + result.fgColor[i] /= fg_count; + } + } + return result; +} + +/** + * @brief Find the best character and colors + * for a 4x8 part of the image at the given position + * + * @param image + * @param x0 + * @param y0 + * @param flags + * @return CharData + */ +CharData findCharData(const cimg_library::CImg &image, int x0, + int y0, const int &flags) { + int min[3] = {255, 255, 255}; + int max[3] = {0}; + std::map count_per_color; + + // Determine the minimum and maximum value for each color channel + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + long color = 0; + for (int i = 0; i < 3; i++) { + int d = image(x0 + x, y0 + y, 0, i); + min[i] = std::min(min[i], d); + max[i] = std::max(max[i], d); + color = (color << 8) | d; + } + count_per_color[color]++; + } + } + + std::multimap color_per_count; + for (auto i = count_per_color.begin(); i != count_per_color.end(); ++i) { + color_per_count.insert(std::pair(i->second, i->first)); + } + + auto iter = color_per_count.rbegin(); + int count2 = iter->first; + long max_count_color_1 = iter->second; + long max_count_color_2 = max_count_color_1; + if ((++iter) != color_per_count.rend()) { + count2 += iter->first; + max_count_color_2 = iter->second; + } + + unsigned int bits = 0; + bool direct = count2 > (8 * 4) / 2; + + if (direct) { + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + bits = bits << 1; + int d1 = 0; + int d2 = 0; + for (int i = 0; i < 3; i++) { + int shift = 16 - 8 * i; + int c1 = (max_count_color_1 >> shift) & 255; + int c2 = (max_count_color_2 >> shift) & 255; + int c = image(x0 + x, y0 + y, 0, i); + d1 += (c1 - c) * (c1 - c); + d2 += (c2 - c) * (c2 - c); + } + if (d1 > d2) { + bits |= 1; + } + } + } + } else { + // Determine the color channel with the greatest range. + int splitIndex = 0; + int bestSplit = 0; + for (int i = 0; i < 3; i++) { + if (max[i] - min[i] > bestSplit) { + bestSplit = max[i] - min[i]; + splitIndex = i; + } + } + + // We just split at the middle of the interval instead of computing the + // median. + int splitValue = min[splitIndex] + bestSplit / 2; + + // Compute a bitmap using the given split and sum the color values for + // both buckets. + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + bits = bits << 1; + if (image(x0 + x, y0 + y, 0, splitIndex) > splitValue) { + bits |= 1; + } + } + } + } + + // Find the best bitmap match by counting the bits that don't match, + // including the inverted bitmaps. + int best_diff = 8; + unsigned int best_pattern = 0x0000ffff; + int codepoint = 0x2584; + bool inverted = false; + unsigned int end_marker = flags & FLAG_TELETEXT ? 1 : 0; + for (int i = 0; BITMAPS[i + 1] != end_marker; i += 2) { + // Skip all end markers + if (BITMAPS[i + 1] < 32) { + continue; + } + unsigned int pattern = BITMAPS[i]; + for (int j = 0; j < 2; j++) { + int diff = (std::bitset<32>(pattern ^ bits)).count(); + if (diff < best_diff) { + best_pattern = BITMAPS[i]; // pattern might be inverted. + codepoint = BITMAPS[i + 1]; + best_diff = diff; + inverted = best_pattern != pattern; + } + pattern = ~pattern; + } + } + + if (direct) { + CharData result; + if (inverted) { + long tmp = max_count_color_1; + max_count_color_1 = max_count_color_2; + max_count_color_2 = tmp; + } + for (int i = 0; i < 3; i++) { + int shift = 16 - 8 * i; + result.fgColor[i] = (max_count_color_2 >> shift) & 255; + result.bgColor[i] = (max_count_color_1 >> shift) & 255; + result.codePoint = codepoint; + } + return result; + } + return createCharData(image, x0, y0, codepoint, best_pattern); +} + +int clamp_byte(int value) { + return value < 0 ? 0 : (value > 255 ? 255 : value); +} + +double sqr(double n) { return n * n; } + +int best_index(int value, const int (&STEPS)[], int count) { + int best_diff = std::abs(STEPS[0] - value); + int result = 0; + for (int i = 1; i < count; i++) { + int diff = std::abs(STEPS[i] - value); + if (diff < best_diff) { + result = i; + best_diff = diff; + } + } + return result; +} + +void emit_color(const int &flags, int r, int g, int b) { + r = clamp_byte(r); + g = clamp_byte(g); + b = clamp_byte(b); + + bool bg = (flags & FLAG_BG) != 0; + + if ((flags & FLAG_MODE_256) == 0) { + std::cout << (bg ? "\x1b[48;2;" : "\x1b[38;2;") << r << ';' << g << ';' + << b << 'm'; + return; + } + + int ri = best_index(r, COLOR_STEPS, COLOR_STEP_COUNT); + int gi = best_index(g, COLOR_STEPS, COLOR_STEP_COUNT); + int bi = best_index(b, COLOR_STEPS, COLOR_STEP_COUNT); + + int rq = COLOR_STEPS[ri]; + int gq = COLOR_STEPS[gi]; + int bq = COLOR_STEPS[bi]; + + int gray = + static_cast(std::round(r * 0.2989f + g * 0.5870f + b * 0.1140f)); + + int gri = best_index(gray, GRAYSCALE_STEPS, GRAYSCALE_STEP_COUNT); + int grq = GRAYSCALE_STEPS[gri]; + + int color_index; + if (0.3 * sqr(rq - r) + 0.59 * sqr(gq - g) + 0.11 * sqr(bq - b) < + 0.3 * sqr(grq - r) + 0.59 * sqr(grq - g) + 0.11 * sqr(grq - b)) { + color_index = 16 + 36 * ri + 6 * gi + bi; + } else { + color_index = 232 + gri; // 1..24 -> 232..255 + } + std::cout << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m"; +} + +void emitCodepoint(int codepoint) { + if (codepoint < 128) { + std::cout << static_cast(codepoint); + } else if (codepoint < 0x7ff) { + std::cout << static_cast(0xc0 | (codepoint >> 6)); + std::cout << static_cast(0x80 | (codepoint & 0x3f)); + } else if (codepoint < 0xffff) { + std::cout << static_cast(0xe0 | (codepoint >> 12)); + std::cout << static_cast(0x80 | ((codepoint >> 6) & 0x3f)); + std::cout << static_cast(0x80 | (codepoint & 0x3f)); + } else if (codepoint < 0x10ffff) { + std::cout << static_cast(0xf0 | (codepoint >> 18)); + std::cout << static_cast(0x80 | ((codepoint >> 12) & 0x3f)); + std::cout << static_cast(0x80 | ((codepoint >> 6) & 0x3f)); + std::cout << static_cast(0x80 | (codepoint & 0x3f)); + } else { + std::cerr << "ERROR"; + } +} + +void emit_image(const cimg_library::CImg &image, + const int &flags) { + CharData lastCharData; + for (int y = 0; y <= image.height() - 8; y += 8) { + for (int x = 0; x <= image.width() - 4; x += 4) { + CharData charData = + flags & FLAG_NOOPT + ? createCharData(image, x, y, 0x2584, 0x0000ffff) + : findCharData(image, x, y, flags); + if (x == 0 || charData.bgColor != lastCharData.bgColor) + emit_color(flags | FLAG_BG, charData.bgColor[0], + charData.bgColor[1], charData.bgColor[2]); + if (x == 0 || charData.fgColor != lastCharData.fgColor) + emit_color(flags | FLAG_FG, charData.fgColor[0], + charData.fgColor[1], charData.fgColor[2]); + emitCodepoint(charData.codePoint); + lastCharData = charData; + } + std::cout << "\x1b[0m" << std::endl; + } +} + +struct size { + size(unsigned int in_width, unsigned int in_height) + : width(in_width), height(in_height) {} + explicit size(cimg_library::CImg img) + : width(img.width()), height(img.height()) {} + unsigned int width; + unsigned int height; + size scaled(double scale) { return size(width * scale, height * scale); } + size fitted_within(size container) { + double scale = std::min(container.width / static_cast(width), + container.height / static_cast(height)); + return scaled(scale); + } +}; +std::ostream &operator<<(std::ostream &stream, size sz) { + stream << sz.width << "x" << sz.height; + return stream; +} + +/** + * @brief Wrapper around CImg(const char*) constructor + * that always returns a CImg image with 3 channels (RGB) + * + * @param filename The file to construct a CImg object on + * @return cimg_library::CImg Constructed CImg RGB image + */ +cimg_library::CImg load_rgb_CImg(const char *const &filename) { + cimg_library::CImg image(filename); + if (image.spectrum() == 1) { + // Greyscale. Just copy greyscale data to all channels + cimg_library::CImg rgb_image( + image.width(), image.height(), image.depth(), 3); + for (unsigned int chn = 0; chn < 3; chn++) { + rgb_image.draw_image(0, 0, 0, chn, image); + } + return rgb_image; + } + return image; +} + +// Implements --help +void emit_usage() { + std::cerr << R"( +Terminal Image Viewer v1.2 +usage: tiv [options] [...] +-0 : No block character adjustment, always use top half block char. +-2, --256 : Use 256-bit colors. Needed to display properly on macOS Terminal. +-c : Number of thumbnail columns in 'dir' mode (3 by default). +-d, --dir : Force 'dir' mode. Automatially selected for more than one input. +-f, --full: Force 'full' mode. Automatically selected for one input. +--help : Display this help text. +-h : Set the maximum output height to lines. +-w : Set the maximum output width to characters. +-x : Use new Unicode Teletext/legacy characters (experimental).)" + << std::endl; +} + +enum Mode { AUTO, THUMBNAILS, FULL_SIZE }; + +int main(int argc, char *argv[]) { + std::ios::sync_with_stdio(false); // apparently makes printing faster + + // Platform-specific implementations for determining console size, better + // implementations are welcome Fallback sizes when unsuccesful + int maxWidth = 80; + int maxHeight = 24; +#ifdef __POSIX_VERSION + struct winsize w; + // If redirecting STDOUT to one file ( col or row == 0, or the previous + // ioctl call's failed ) + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != 0 || + (w.ws_col | w.ws_row) == 0) { + std::cerr << "Warning: failed to determine most reasonable size, " + "defaulting to 80x24" + << std::endl; + } else { + maxWidth = w.ws_col * 4; + maxHeight = w.ws_row * 8; + } +#elif defined _WIN32 + CONSOLE_SCREEN_BUFFER_INFO w; + if (GetConsoleScreenBufferInfo( + GetStdHandle(STD_OUTPUT_HANDLE), + &w)) { // just like powershell, but without the hyphens, hooray + maxWidth = w.dwSize.X * 4; + maxHeight = w.dwSize.Y * 8; + } else { + std::cerr << GetLastError(); + std::cerr << "Warning: failed to determine most reasonable size, " + "defaulting to 80x24" + << std::endl; + } +#else + std::cerr << "Warning: failed to determine most reasonable sie: " + "unrecognized system, defaulting to 80x24" + << std::endl; +#endif + + // Reading input + char flags = 0; // bitwise representation of flags, + // see https://stackoverflow.com/a/14295472 + Mode mode = AUTO; // either THUMBNAIL or FULL_SIZE + int columns = 3; + + std::vector file_names; + int ret = EX_OK; // The return code for the program + + if (argc <= 1) { + emit_usage(); + return 0; + } + + for (int i = 1; i < argc; i++) { + std::string arg(argv[i]); + if (arg == "-0") { + flags |= FLAG_NOOPT; + } else if (arg == "-c") { + if (i < argc - 1) { + columns = std::stoi(argv[++i]); + } else { + std::cerr << "Error: -c requires a number" << std::endl; + ret = EX_USAGE; + } + } else if (arg == "-d" || arg == "--dir") { + mode = THUMBNAILS; + } else if (arg == "-f" || arg == "--full") { + mode = FULL_SIZE; + } else if (arg == "-w") { + if (i < argc - 1) { + maxWidth = 4 * std::stoi(argv[++i]); + } else { + std::cerr << "Error: -w requires a number" << std::endl; + ret = EX_USAGE; + } + } else if (arg == "-h") { + if (i < argc - 1) + maxHeight = 8 * std::stoi(argv[++i]); + else + emit_usage(); + } else if (arg == "--256" || arg == "-2" || arg == "-256") { + flags |= FLAG_MODE_256; + } else if (arg == "--help" || arg == "-help") { + emit_usage(); + } else if (arg == "-x") { + flags |= FLAG_TELETEXT; + } else if (arg[0] == '-') { + std::cerr << "Error: Unrecognized argument: " << arg << std::endl; + ret = EX_USAGE; + } else { + // Arguments that will be displayed + if (std::filesystem::is_directory(arg)) { + for (auto &p : std::filesystem::directory_iterator(arg)) + if (std::filesystem::is_regular_file(p.path())) + file_names.push_back(p.path().string()); + } else { + // Check if file can be opened + std::ifstream fin(arg.c_str()); + if (fin) { + file_names.push_back(arg); + } else { + std::cerr << "Error: Cannot open '" << arg + << "', permission issue?" << std::endl; + ret = EX_NOINPUT; + } + } + } + } + + if (mode == FULL_SIZE || (mode == AUTO && file_names.size() == 1)) { + for (const auto &filename : file_names) { + try { + cimg_library::CImg image = + load_rgb_CImg(filename.c_str()); + if (image.width() > maxWidth || image.height() > maxHeight) { + // scale image down to fit terminal size + size new_size = + size(image).fitted_within(size(maxWidth, maxHeight)); + image.resize(new_size.width, new_size.height, -100, -100, + 5); + } + // the acutal magic which generates the output + emit_image(image, flags); + } catch (cimg_library::CImgIOException &e) { + std::cerr << "Error: '" << filename + << "' has an unrecognized file format" << std::endl; + ret = EX_DATAERR; + } + } + } else { // Thumbnail mode + unsigned int index = 0; + int cw = (((maxWidth / 4) - 2 * (columns - 1)) / columns); + int tw = cw * 4; + cimg_library::CImg image( + tw * columns + 2 * 4 * (columns - 1), tw, 1, 3); + size maxThumbSize(tw, tw); + + while (index < file_names.size()) { + image.fill(0); + int count = 0; + std::string sb; + while (index < file_names.size() && count < columns) { + std::string name = file_names[index++]; + try { + cimg_library::CImg original = + load_rgb_CImg(name.c_str()); + auto cut = name.find_last_of("/"); + sb += + cut == std::string::npos ? name : name.substr(cut + 1); + size newSize = size(original).fitted_within(maxThumbSize); + original.resize(newSize.width, newSize.height, 1, -100, 5); + image.draw_image( + count * (tw + 8) + (tw - newSize.width) / 2, + (tw - newSize.height) / 2, 0, 0, original); + count++; + unsigned int sl = count * (cw + 2); + sb.resize(sl - 2, ' '); + sb += " "; + } catch (std::exception &e) { + // Probably no image; ignore. + } + } + if (count) emit_image(image, flags); + std::cout << sb << std::endl << std::endl; + } + } + return ret; +}

Y E^ /X 0_ %f 1] 'c " + " @ZEZ AY MV" + "CW X *^ +]DU 7ZEZ 5U>U JY ?Y *^ -YEZ 4Y " + " ?Y *^ .ZEZ 5[ ]DU 5Y >Y +^ ,]DU 6ZEZ Y ?Y +_ .ZEZ \"Y Z G[ G\\ @e !f JX !Y " + "LY %d :Y Y Ha /X 0b *j L] D_ " + " +g A[ LY 8Z -ZEZ \"Y 1o )V FX NZ FY " + "%Y ,X NX*Z NW 3WEW H\\ #[ !Z \"[ \"[ \"[ G[7T 8g 0Y " + "@Y +_ ,_FV 7ZEZ 5U>U IY @Y +` .YEZ 3X ?X *` /ZEZ 4[:P 8_FV 4X ?Y +` ._EU 6ZEZ NX @Y *_ .ZEZ #Y ;Y" + " FYEZ ;] GU W ,X " + " FV a \"d -g >d (d +b %b 4f Bg Ie \"e \"h " + " Ge !f IX \"Y LY &e :Y Y Jc /X 0c " + " -n $g I` .j >a ;e HU .U +b Ac 2ZEZ 'b " + " 5o -] Na (c KY .Y #_ 8Y!W'Y\"X.c$X 3XGX Mf -e +d " + ",e ,e ,e \"e=V ;k 1Y BY +XNW .aGV 7ZEZ 5V@V HX AY +XNW .YEZ 3Y AY *WNW /ZEZ 4\\>T 9`GV 3" + "X AY +XNW .`GV 6ZEZ NY AX *XNW /ZEZ $Y :Y FYEZ <_ IU (Q LZ 4Z2Z 1Q " + " &g %Z +XCX MT Y Kd /X 0e 0p " + " (m Lb 1m ,\\ 5~S E~R Ah 'Z :~]+[;Z;Z Ik LW DX DW /i ?Y(Y 4h 5ZEZ" + " ,\\ ,h 7\\ -o .` $f -h NY No %_ %c @_\"X-_\"W0h&W .\\ $\\ \"\\ #\\ #\\ )g 5~a Lm D~S I~S " + "H~R H~R 6Z !Z !Z \"Z :r 8^,Y Bk 2k 2k 2k 2k (kAX+Z(Z#Z(Z$Z(Z$Y'Y&[%[ MZ Im 1X CY *WMX /bHV 7ZEZ 5V@V G" + "X CY *WLW /YEZ 2Y CY *WLW 0ZEZ 3[AW :bHV 3Y BX *WLW 0bHV 6ZEZ MY CX *XMX 0ZEZ $X 9Y FYEZ " + " =a M~i 7U (Q N_ 9_8_ 3R )k 'Z +XCX +X@X 4T >e,X Cl &X IX *X GV " + " GX 5i 0d 2p ;u !^ ?y 2o F~S @n 4j /l N\\ 8x .r Nx 7~R E} >t KZ(Z :Z \"Z 4Z-] KZ 2_'_(^-Z" + " Ep =t 5o Au 1u N~d'Z(Z)Z MZY " + " Le /X 0e 1r +r c 3o -\\ 5~S E~R Dn *Z :~]+[;Z;Z Ko " + " Y EX EY 2m @Y)Y 6l 7ZEZ 0e 2k >e 1o 0c 'j /i X !r (b 'g Eb\"W0c#X0i(W -" + "\\ $] #\\ $] #\\ (f 6~b r F~S I~S H~R H~R 6Z !Z !Z \"Z :w =^,Y Ep 6p 7p 7o 7p ,oDY+Z(Z#Z(Z$Z(Z$Y'Y%Z%Z LZ Kp" + " 1X DX *WKW /WMYJV 6ZEZ 5V@V GY EY *WKX 0YEZ 1Y EY *XKW 1ZEZ 2[EZ :WMZKV 1Y DX *WKX 1WLYKW 6ZEZ L" + "Y EY *WKW 0ZEZ %X 8Y FYEZ >c M~h 7T (S !a Y >X 8f /X 0f 3t -s c " + " 4q /^ 6~S E~R Fr ,Z :~]+[;Z;Z Ms #[ FX F[ 4n @Y*Y 6m 7ZEZ 3k 5l Bk 4o 1f )k 0k #" + "X #u (b (i Fb#X0c#W/k+X .^ %] $^ %] $^ (d 5~b\"v H~S I~S H~R H~R 6Z !Z !Z \"Z :{ A_-Y Gt :t ;t ;s ;t " + " 0sGY*Z(Z#Z(Z$Z(Z$Y'Y$Z'[ LZ Ls 2X FX *WIW 1WJc 6ZEZ 4VBV EY FX *XJW 0YEZ 0X EX )WJW 1ZEZ 1[I^ x %_ ?y 5r F~S Ct :p" + " 6s /e *^ 9| 6z#~ =~R E} B}!Z(Z :Z \"Z 4Z/\\ HZ 2`)`(_.Z Iw @y >w Ez 9z!~d'Z(Z)[ Z;Z0]/Z4Z,Z$[(Z%~^ " + "@e 2X Gf +a MX %Y LY *i :Y Y >Y 9f /X 0g 5v " + " 0u d 6_K_ 0^ 6~S E~R Gu .Z :~]+[;Z;Z w &] GX G] 6U &o ?Y+Y 7X )n 7ZEZ " + "6p 7m Eo 6o 2h *l 1l %X #v (b )k Gb$X/c$X/l,W -^ &_ %^ &_ %^ 'b 4~b$z J~S I~S H~R H~R 6Z !Z " + "!Z \"Z :~ D_-Y Hw =v >w >w >w 4wIX)Z(Z#Z(Z$Z(Z$Y'Y$[)[ KZ Mt 1X HX )WHW 2VHb 6ZEZ 4WDW DX GX )WHW 1YE" + "Z /X GX )WHW 2ZEZ 0[M` ;VHb /X GY *WHW 3VHb 5ZEZ JX GX )WHW 2ZEZ 'Y 7Y FYEZ ?e M~f " + " 7U )U %g Bh@g :W .~T 't +Z +XCX ,X@X 3T Ak1X Er (X JX 'X IV HX 8q" + " =m 7y ?y '` ?y 6s F~S Dv Y >Y " + " :] %X &] 5]C\\ 1v Nc 7\\D\\ 1_ 6~S E~R Iy 0Z :~]+[;Z;Z!y (_ H" + "X H_ 7U 'p ?Y,Y 6X *o 7ZEZ 8t 9YH] Ht 9o 3i *XG[ 1VE[ &Y %x (b *[I[ Hb$W.c%X.VE[-X " + " ._ &_ %_ '_ %_ '` 4~c%} L~S I~S H~R H~R 6Z !Z !Z \"Z :~Q F`.Y Jz @z Az Ay Az 7zKX(Z(Z#Z(Z$Z(Z$Y'Y#[*Z JZ Na" + "J_ 2X IX )WGW 2VG` 5ZEZ 4XFX CX IX )WFW 2YEZ .X IX )WFW 3ZEZ /j 8VG` -X HX *WFW 4VG` 4ZEZ IX IX " + ")WGW 2ZEZ 'X 6Y FYEZ ?XKX M~f 7T )W 'i DiAi ;X 1~V (w -Z " + "+XCX ,X@X 3T AZI[2W Es (X KX &X IV HX 9s >m 7z @z )a ?y 7t F~R Dx >t 9v 8s 2` :~P <~Q&~S" + " A~R E} E~T$Z(Z :Z \"Z 4Z2] FZ 2a+a(`/Z K| C{ C} H| =|!~d'Z(Z(Z!Z9Z1^1Z2[0[!Z+[$~^ @X $X ;Y -e MX 'Y " + "LY +[ +Y Y >Y :[ #X #Z 6\\?[ 2v F\\ " + " 8Z@[ 2` 7~S E~R J{ 1Z :~]+[;Z;Z#} +` HX Ia 8U (q >Y-Y 6X +p 7ZEZ 9bMb ;U@Y JbMb :" + "n 3ZIZ +T@Y 2R>Y 'X %y (XLV +ZEZ IXMW%X.YMW%W-R>Y.W -` '_ &` '_ &` '` 4~c'~R N~S I~S H~R H~R 6Z !Z " + "!Z \"Z :~S Ha/Y K| B| C| D} D| 9|MX'Z(Z#Z(Z$Z(Z$Y'Y\"Z+[ JZ N]B\\ 2X JX *WEW 3UE_ 5ZEZ 3YJY AX JW )WE" + "W 2YEZ -X KX (WFW 3ZEZ .f 5UE_ ,X JX )WFW 4VF_ 4ZEZ HX KX )WEW 3ZEZ (X 5Y FYEZ @YJW M~" + "e 7U *X (j EkCk =Y 3~X )x -Z +XCX ,W?X 3T BYEY3X Ft (X KX %X JV " + " IX 9u ?m 7{ A{ *a ?y 8u F~R Ez @v :v :w 4` :~Q >~S'~U C~R E} G~V$Z(Z :Z \"Z 4Z3] EZ 2a+a(a0Z M~P D" + "| E~P I} ?}!~d'Z(Z'Z\"Z9Z1^1Z1Z0Z [,Z#~^ @X $X ;Y .g MW 'Y LY +Y )Y Y " + " >Y :Z \"X \"Z 7[=Z 3aE[ E[ 9Z>[ 3` 7~S E~R L~ 2Z :~]+[;Z;Z$" + "~P -b IX Jc 9U )r >Y.Y 5X ,]DX 7ZEZ ;\\>\\ \\ 0XDX ,R=Y MX (X %hEW (SG" + "V ,YAY JSHW%W-SGW&X GX/W ,` (a '` (a '` (a 5~d(~S N~S I~S H~R H~R 6Z !Z !Z \"Z :~T Ia/Y L~P F~P F~P F~P F~P" + " <~X&Z(Z#Z(Z$Z(Z$Y'Y\"[-[ IZ \\>Z 1X LX )VCW 4UD] 4ZEZ 2f ?X LX )WDW 3YEZ ,W KX )WDW 4ZEZ -b 2UD] *W" + " KX )WDW 5UD] 3ZEZ GW LX (VCW 4ZEZ )X 4Y FYEZ @XIX M~d 7U *Y *l GmDl ?[ " + " 6~Z *`C\\ -Z +XCX ,W?W 2T CYCY5X E]CZ (X LX $X JV IX 9]E^ @m 7aGb B^Ec ,b ?y " + "9aF[ F~R E_C_ B_E^ ;]E_ ={ 7b ;~R @cBb'~V D~R E} HeBc$Z(Z :Z \"Z 4Z4] DZ 2b-b(a0Z NbCb E} GbCb J~ Aa" + "B_!~d'Z(Z'Z#[9Z2_1Z0Z2[ N[.Z\"~^ @X $X ;Y /i MW (Y LY ,Y (Y Y >Y " + " :Y !X !Y 8[;Z 1\\ 0\\:U D[ ;ZbCh%Z(Z" + "#Z(Z$Z(Z$Y'Y![.Z HZ Z;Z 1X NX )WBV 5VBZ $e >W MX )WBW !X MX )WBW #` /UBZ (W MX )WBW 6UBZ " + " 9X MW (WCW MX 3Y GXHW M~d 8U *[ +m HnFn A] 9~\\ +^=Y" + " -Z +XCX -X@X 2U DXAX5W E\\=V (X LX #X .R@V?Q ,X :\\A\\ @m 7\\>_ CY<_ -c ?y :^=V F~Q E]>^ D]@] " + " j E~R E| Ha8^$Z(Z :Z \"Z 4Z5] CZ 2b-b(b1Z `<_ FZ@d I`=` K[@d C_:Z ~b&Z(Z'Z#Z8Z2`" + "2Z0[4[ LZ/[\"~^ @X #X Y >Y ;Z " + "!X !Y 8Z9Y 6d 4[5R CZ ;Y:Z 5b 8~R D~Q MbAb 8` =~]+[;Z;Z&`=` 1f KX Lg " + " ;U *\\=T =Y0Y 4X ,Z;R 5Z3Y &W !Y3Y 3W@W EW LX *W %jEW KV -X=X @W'X W'X EX1W ,b " + "*b (b )b )b )b 7ZH~R)a:] N~R H~R G~R H~R 6Z !Z !Z \"Z :Z>j Lb0Y N_<` J`<_ J`=` J`=` J`=` @`=e%Z(Z#Z(Z$Z(Z$Y'Y" + " Z/[ HZ !Z9Y 0W X )WAW 6VAW \"d Y >Y ;Y X !Y " + " 8Y8Y 6f 6Z2P BY j BZ(Z+[;Z;Z'_9_ 3h LX Mi <" + "U *[:R V EW KW +W %kEW KV .X;W @W'W NW(X CW2X -c *c )b " + "*c )c +c 7ZHZ 2_5[ NZ !Z Z !Z >Z !Z !Z \"Z :Z7d Mc1Y ^8_ K^8^ L_8^ L_9_ L^8_ B_9b$Z(Z#Z(Z$Z(Z$Y'Y [1[ GZ !Z" + "8Y 0W !W (V?W I` :X !W (V?W X \"X (W@W *d EX !W (W@W 0X \"X (V?W !W 1Y #d ," + "e +d +d ,e #XHW LZ#Z 7U +] -o KqHp C_ X #X " + " Y >Y ;Y X X 9Z7X 6g 7Y" + " #Z =Y8Z 7d 7[ Z )_7_ Bp EZ(Z+[;Z;Z(^5^ 5j MX Nk =U +[7P Z !Z !Z \"Z :Z3a Nc1Y!^5] L]4] N^5^ N^5^ N^5] C^5_#Z(Z#Z(Z$Z(Z$Y'Y N[2Z FZ \"Z7Y /W #W (W>V H^" + " 8X #W (W>V NW \"W (W>W .h EW \"X )W>W 0W #X (V=V \"W 0Y &j 1i 0j 1j 1i &X ` .\\5U -Z +XCX -W?W =r'X>W8X EZ ;X NY !X 1XDVDX 2X " + " &X ;[;[ BWDZ 7T2\\ \"\\ 1XMZ ?Y L\\ 2Z E[7[ G\\9[ >S5[ F`7` ?YNY Y >Y ;Y X Y :Y6Y 7i 9Y \"Y " + " >Y6Y 7YNY 6[ !Z *^3] Dt GZ(Z+[;Z;Z)]2] 6l NX m >U +Z !Y4Z 3X -Y NW(W (W " + " &X)X 8VZ !Z !Z \"Z :Z1` d2Y\"]2] N]2] ]2]!^2]!]2] E]2]\"Z(Z#Z(Z$Z(Z$Y'Y MZ3[ FZ \"Z6X .V $W 'VR4[ G^1^ AZNY Y >Y ;Y X Y :Y6Y 7j :Y \"Y " + " >Y6Z 9YMY 5[ \"Z *]1] Hy IZ(Z+[;Z;Z)\\/\\ 8n X !o ?U ,[ Y5Y 2X -Y W&W )W 'W%W 9V" + "Z " + "!Z !Z \"Z :Z/_!d2Y#]0]!]0]\"]0\\!\\/\\\"]0] F\\0]#Z(Z#Z(Z$Z(Z$Y'Y M[5[ EZ \"Y5X +P " + " %_K[ CY *r 9q 8r 9r 9q *X ;Z%Z >Q JT ,b 0q MsKs Ge " + "C^ *[0R -Z +XCX .X@X @v)X=X:W CY :X Y NX 1[HVH[ 1X 'X ;Z7Z 0Z 7P,[ ![ 3XLZ ?Y M[" + " 1Z EZ4[ I[5Z ?P1Z I^-] BYLY =Z1[ H\\(T'Z-^ JZ MZ *\\$S$Z(Z :Z \"Z 4Z:] >Z 2YMX1XMY(YNZ4Z$].\\ JZ5" + "\\!\\-\\ Z4[ GZ ;Y 9Z(Z%Z'Z4Z5XNX5Z*Z:[ F[6Z [ ;X \"X =Y 5\\C[ #Y LY -Y 'Y 8X >Y " + " >Y ;Y X Y :Y6Y 7k ;Y \"Z @Z5Y 9YLY 5[ #Z +\\.] J| KZ" + "(Z+[;Z;Z*\\-\\ :p !X \"q @U ,Z NY6Y 1X -X W#V *W (W#W :U;V +X DW LW )mEW KV" + " /X9X BW*X LW*X BW3W +YLY -YMY ,YLY -YMY ,YLY -YMZ ;ZFZ 5\\'S NZ !Z Z !Z >Z !Z !Z \"Z :Z-^\"e3Y#\\.]#].\\" + "#\\-\\#\\-\\#\\-\\ H\\.]$Z(Z#Z(Z$Z(Z$Y'Y L[6Z DZ \"Y5Y /[G[ " + " DY +u =u S LU ,c 1q MtLt Hf E] )[.Q " + " -Z +XCX .W?X Bx)X=X;X DZ :X X MY 0ZIVIZ /X 'X ;Z7[ 1Z AZ ![ 4XKZ ?Y MZ 0Z EZ3Z I[5Z " + "Z J])\\ CYLY =Z1[ I\\%R'Z+] KZ MZ +\\\"R$Z(Z :Z \"Z 4Z;] =Z 2YMX1XMY(YNZ4Z$\\,\\ KZ4[\"\\+[ Z4\\ I[ ;Y 9Z(Z$Z" + "(Z4Z5WLW5Z*[<[ DZ7[ !\\ ;X \"X =Y 6\\A[ $Y LY -Y 'Y 8X >Y >Y " + " ;Y X Y :Y6Y 7l Z !Z !Z \"Z :Z,^#YNZ3Y$\\,\\#\\,\\$\\,\\%\\+\\%\\,\\ MP" + " NP N\\-]$Z(Z#Z(Z$Z(Z$Y'Y KZ7[ Dq :Z4X /XC[ EY " + " -x @x >x ?x @x -X :Z'Z ?U MU -e 2q MtLt Ig E[ 'Z,P -Z +XCX .W?W By)" + "XZ0Z" + " J\\#Q'Z*\\ KZ MZ +[ Q$Z(Z :Z \"Z 4Z<] Y 7[>[ %Y LY -Y 'Y 8X >Y >Y ;Y X Y ;Y" + "5Y 7UH_ Z !Z !Z \"Z :Z+]#YMZ4Y%\\*\\%\\*\\&\\*[%[)[%[*\\ R!R [-_%Z(Z#Z" + "(Z$Z(Z$Y'Y K[9[ Ct =Y3X /U@[ \"Q EY .z B{ " + "B{ Az B{ /X :Z'Y >V U -g 4r NvNu Ji *\\ 5X.X 6\\ 7Z1Z M[ '[ 8Z +XCX /X@X C`MTL_)W;" + "WZ0Z " + "J[ 'Z)\\ LZ MZ ,\\ \"Z(Z :Z \"Z 4Z=] ;Z 2YLX3XLY(YMZ5Z%[([ LZ3[$\\)\\\"Z3[ IZ :Y 9Z(Z$Z)Z3Z6XLX6Z(Z>[ B[:Z !" + "\\ 9X !X >Y 8[<[ &Y LY -Y 'Y 8X >Y >Y ;Y X Y ;Y5Y " + "7RB] =\\ $Z BY2Y ;YJY 3[ &Z -[(\\!~U Z(Z+[;Z;Z,\\)\\ ?\\MXL[ $X %\\LXM\\ CU" + " ,Y *Q\"R DY9Y 0X -Y #V=_?V Cm *V LV Z !Z !Z \"Z :Z*]$YMZ4Y%[([%[(['\\)\\'\\)\\'\\)[!T#T\"\\-`&Z(Z#Z(" + "Z$Z(Z$Y'Y J[:Z Bw @Y6[ .Q<[ #S GY /`Da E`C" + "` DaD` C`Da E`C` 0X 9Y(Z ?X !U .h 4r NvNu Kk .c 9X.X 7^ 7Y1Y M[ &Z 7Z +XCX /X@X C\\" + "ITFY)W;W=X BY 9X !X KY +YNVNZ *X (X ;Z4Z 2Z @Z !Z 6YJZ ?Y Z /Z DY2Z JZ1Y ,T T MZ N[ NZ HZJ" + "Y >Z0Z K[ &Z(\\ MZ MZ ,[ !Z(Z :Z \"Z 4Z>] :Z 2YLX3XLY(YLZ6Z&['\\ MZ3[$['[\"Z2Z IZ :Y 9Z(Z#Z*Z2Z7XLX7Z'[@[ @Z;" + "[ ![ 8X !X >Y 9[:[ 'Y LY -Y 'Y 8X >Y >Y ;Y X Y ;Y" + "5Y %\\ =] %Y BY2Z =ZJY 3\\ 'Z .\\'[#cLZLb!Z(Z+[;Z;Z,['[ @\\LXK[ %X &\\KXL\\ " + " DU -Z +S$T EY:Y /X -Z %V?fBU Eo +VEg=V =VZ !Z !Z \"Z :Z)\\$YLZ5Y&\\'['['\\(['['['['['[#V%V#[-a&Z(Z#Z(Z$" + "Z(Z$Y'Y IZ;Z Ay BY9^ G[ %U HY 0]<^ G^=^ F" + "^<] E]<^ G^=^ 1X 9Z)Z @Z \"U .i 5r NvNu Lm 2h ;X.X 7^ 7Y1Y N[ &[ 7Z +XCX /W?X D[GTC" + "V)W;W=W AZ :X \"Y KY *j (X (X ZY .Y3Y 3Z '\\ MZ )Z ;Z 2^ +Y ;Y " + "X Y 6Y /Y5Y $[ =` G^ !Z IZ M\\ #Y2Z =YIZ 3\\ (Z .[%[%aIZI`\"Z(Z+[;Z;Z-[%[ B\\KXJ[" + " &X '\\JXK\\ H\\ 1Z ,U&V EY;Y /X ,Z 'V@jDV Gp +UDj?V >VZ !Z !Z \"Z :Z(\\%YLZ5Y&[&['[&[)\\&[)[%[)" + "[&[$X'X%[-b&Z(Z#Z(Z$Z(Z$Y'Y I[=[ Az CY;` 5\\ $] $\\ \"\\ #\\ $] 8\\/[ 3\\ '\\ #\\ \"[ \"[ \"[ &Z &[ ![" + " #\\ #[ ![ G[@W IYBZ J]8] I\\7\\ H]8] I]8] I\\7\\ 2X 8Y*Z @Z \"U .k 5q N~o Mm 4l =X" + ".X 7^ 7Z3Z NZ %Z 6Z +XCX /W?W D[FT@S)W;W>X AZ :X \"Y JX (f &X )X ;Z3Z 2Z @Z !Z 7" + "XHZ ?Y !Z /Z CY1Y JZ1Z 2Y Y $Z Z HY JYHY ?Z/Y L[ %Z'\\ NZ MZ -[ Z(Z :Z \"Z 4Z@\\ 7Z 2YKX5XKY(YKZ7Z'[" + "$[ NZ2Z%[%[#Z2[ JZ :Y 9Z(Z#[,Z1Z8XJW7Z%ZB[ >[>Z !\\ 7X X ?Y ;[6[ (e 7YE` (e 3aEY 8c 2r 5`DX GYEa (X NX " + "0X1Z 8Y FXD`9` YD` -c 9XD` /aEX :XD] 6g 7t BX0Y LY)Y+X6Z6X)Z/Z NX)Y I} 2Y X Y 9_>W KY5Y #[ =c h >XD` " + "AT#X 5Y 6X0X LY'Y ?RCW ?~Y!X?X?X ;d 'r!~W KZ1Y =YHY 2\\ )Z /[$[%_GZG_#Z(Z+[;Z;Z-[%[ C\\JXI[ 'X (\\IXJ\\ " + " (Y d 5Z -W(X FYV=W +X HX )^ ,Y1Y HnEW KV 0X7W BW-W HW.X M^/X )" + "Y +YHY 2YHZ 1YHY 2ZHY 1YHY 2ZHY ?ZDZ 9[ LZ !Z Z !Z >Z !Z !Z \"Z :Z'[%YKZ6Y'\\%[)[$[*[%[)[%[)[%[%Y)Z&[.d'Z(Z#" + "Z(Z$Z(Z$Y'Y H[>Z @{ DY=b ;f -f -f ,e -f -f Ae7c ;e /b )c *c *c 'Y NX NX X E[ >XD` -c )c *b *c )c '\\ &bDX L" + "X0X GX0X GX0X GX0X KY)X KYE` ?Y*Y 8[4\\ K[3[ J\\4[ I[4\\ K[3[ 3X 8Z+Z AZ !U /m 6q N~o No 6o ?X.X 8_ " + "6Y3Z Z $Z 6Z +XCX 0X@X DZET>Q)W;W>W ?Y :X \"X IY 'b $X )X ;Z2Y 2Z @Z !Z 8YHZ ?Y " + "!Z 0[ CY1Y JZ1Z 5\\ \\ 'Z!Z FY LZHZ @Z/Y L[ %Z&[ NZ MZ .[ NZ(Z :Z \"Z 4ZA\\ 6Z 2YKX6YKY(YKZ7Z'[$[ NZ" + "2Z&[#Z#Z2[ JZ :Y 9Z(Z\"Z,Z1Z8XJX8Z%[D[ ZHY 1\\ *Z /[#['^EZE^$Z(Z+[;Z;Z.[#Z C[IXH[ (X ([HXI[ (" + "Z $k 9Z .Y*Z FY=Y .X ,\\ *UAnCU J^CW -VCmAV ?W>V *X IX (a /Y1Y HnEW KV 0X7W BW.X HW.W La3X " + "(Y ,ZHY 2YGY 2ZHZ 3YGY 1YHZ 3YGY @ZCZ 9[ LZ !Z Z !Z >Z !Z !Z \"Z :Z'\\&YJY6Y'[$[)[$[*[$[+[#[+[$[&[+\\([.e'Z(" + "Z#Z(Z$Z(Z$Y'Y GZ?Z ?| EY>c >l 4l 3l 2l 3l 4l Gl=h @k 5h /h /h /h )Y Y NX Y E[ ?XFd 1g .h /h /h /h )\\ )hHX " + "LY0X HY0X GX0X GX0Y LZ+Y KYGd AY*Y 9[EXD[ M[1[ L[1[ K[1[ M[1[ 4X 8Z+Y A[ !T /n 6q N~o q 8q @X.X 8` 7" + "Y3Y Z $Z 5Z +XCX 0X@X DYDT EW;W?X ?Y :X #Y IY %^ \"X )X k 5}\"~W KY0Z ?YGZ 1[ *Z /Z\"[(]CZD^%Z(Z+[;Z;Z.[#[ CYHXGY 'X 'YGXHY 'Z &o" + " ;Z /[,[ FZ?Y -X +\\ +UBoBU LZ>W -UBnAU >W@W *X JX 'c 1Y1Y HnEW KV /W7W BW.W GW/X Lc5W 'Y ," + "YFY 4ZGY 2YFY 3YGZ 3YFY 3YGZ AZCZ 9Z KZ !Z Z !Z >Z !Z !Z \"Z :Z&[&YJZ7Y'[#[*Z\"Z+[#[+[#[+[#[&[-\\'[/YM[(Z(Z#" + "Z(Z$Z(Z$Y'Y G[A[ ?} FY?] :p 8q 8q 7q 8q 8p LqAl Do 9l 3l 3l 3l +Y Y NX Y #i @XHh 5k 2l 3l 3k 2l +\\ +lKX KY0" + "X HY0X GX0X GX0Y KY,Z KYIh CZ,Z :ZCXC[ [/[ N[.Z MZ.[ [/[ 5X 7Y,Z AZ !U /o 7p M~n s :s AX.X 8` 7Z4Y Y" + " #Z 5Z +XCX 0W?X EYCT EW;W@X >Z ;X #Y HX #Z X *X ;Z1Z 3Z @Z !Z 9XFZ ?Y \"Z /Z " + "BY2Z KZ0[ [/Z 4t =YJj 3q >kJY >o 8r ;kJY GYJk .Y NX 0X5\\ 6Y FY" + "JiBi$YJk 8o ?YJj 9kJX ;YJc Z !Z !Z \"Z :Z&[&YIZ8Y([\"[+[\"[,[\"Z+Z!Z,[\"[%[/\\" + "&Z/YL[(Z(Z#Z(Z$Z(Z$Y'Y F[BZ >Z@d GY@\\ :t ;t t TAU NX;W )P9P =UAWAYAU >XDX )X LX HY 3Y1Y HnEW KV /W7W " + "AP9P 9W0X FW0X ?Y8W &Y -YEZ 5YEY 4ZFZ 5YEY 4ZEY 5YEY BZBZ :[ KZ !Z Z !Z >Z !Z !Z \"Z :Z%['YIZ8Y([!Z+Z![,Z![-" + "[![-[!Z$[1\\&[/XJZ(Z(Z#Z(Z$Z(Z$Y'Y EZCZ =Z;` HYA[ 8u oLX ;YLe ?u VAW?XAU ?ZHY (X MX EX 4Y1Y HnE" + "W KV /W7W AQ:Q :W0W EW1X Z !Z !Z \"Z :Z%['YHZ" + "9Y(Z Z+Z Z-[![-[![-Z [$[3\\%[0XI[)Z(Z#Z(Z$Z(Z$Y'Y E[E[ =Z9^ HYBZ 6v =v >w =w >v =v\"vIt Lt >t ;t ;t ;t /Y Y N" + "X Y *r BXKn qMY GYMp 0Y NX 0X8[ 2Y FYMoIp'YMq ?v BYMp ?qMX ;YMf ?u U@W?XAU >j (X " + " NX CX 5Y1Y HnEW KV /W7W AR;R ;W1X EW1W :XZ " + "!Z !Z \"Z :Z$Z'YHZ9Y)[ [-[ [.[ Z-Z NZ-Z [#[5\\$Z0XH[)Z(Z#Z(Z$Z(Z$Y'Y D[FZ w ?x >x ?w >w#wKv Nu ?v" + " =v =v =v 0Y Y NX Y +s BXLp >u \\ DX.X :c 7Z7Z!Y \"Z 4Z +XCX C~d&XBT DW=XB" + "X :[ >X $Y FY +f &X +X ;Z/Z 4Z AZ !Z ;YDZ ?YFP -Z?Q BZ ?Z5Z JZ/Z 5Z \"[ Gj Ii ;[\"X1Q,W\"YCZ BZ1" + "Z MZ \"Z$[!Z MZ /Z LZ(Z :Z \"Z 4ZH] 0Z 2YHX;XHY(YHZ:Z)Z N[!Z2Z([ NZ%Z2Z I[ ;Y 9Z(Z Z1Z,Z;XGW;Z N[L[ 4[H[ #\\" + " 1X MX AY BZ&Z 8^Ga AYN[H_ " + "YDY *X )b 6UDY%U V9W ,SU@W>W@T =h 'X X AW 5Y1Y HnEW KV /X9X ASZ !Z !Z \"Z :Z$Z'YGZ:Y)[ NZ-[ [.Z N[.Z NZ.[ NZ\"[7\\$[1XFZ)Z(Z#Z(" + "Z$Z(Z$Y'Y CZGZ ;Z6\\ IYCY 4^Ga ?^Ga @_Hb ?^Ga ?^Ga ?^Ga$^GaMaI`!bH\\ @aI` ?aI` ?aI` ?aI` 1Y Y NX Y ,u CXM^Nb" + " @aKa >aJa ?aJa ?aKa =`Ja 1\\ 0`Ic GY0X HY0X GX0X GX0Y IY0Z IYN[H_ FZ0Z X>Y&X#X%YJT9TIY&Y.TJY&X#X 8X 5Y0" + "Z CZ ;P4U 1w 9l J~m#z B[;[ EX.X :d 7Y7Y X )~Q #Z +XCX C~d&XBT DW=XCX 9\\ ?X $Y FY " + "-j (X +X ;Z/Z 4Z AZ \"Z :XCZ ?YM_ 5ZE^ IZ >Y6Z IZ0[ 5Z \"[ Jj Ci ?\\\"X6\\2X#YBY BZ1Z MZ \"Z$[!Z " + "MZ 0[ LZ(Z :Z \"Z 4ZI] /Z 2YHX;XHY(YGZ;Z)Z N[!Z3[([ NZ%Z2Z H[ ^ BcB] >_?W C^CYNY C]A] 4Y /]Bc GYNYD^ 2Y NX 0X;\\ 0Y FYNXC\\KYD](YNYC] A]B^ DcB] C^CYNX ;YNZDQ A\\" + ";V 5Y .Y1Y IY/Y&Y;_;Y\"Z;Z FZ0Y $[ 2Y X Y M];\\ F]E[JX IY9[ LY >ZKf =]=V CYNYC] K`2Z 5^ 9Y1Y!Z\"Z!^JZM^" + " K~Y!Y@X@Y E]C^ CaHl\"~W LY.Z BYBY .\\ 0Z 1Z M[-[>Z>[(Z(Z*Z;Z<[0[ N[$[ W@U =f &X !X @W 5Y1Y HnEW KV /X9X AT=T =W2X DW2W 8W=X $Y .YBY 8ZC" + "Z 7YBY 8ZCZ 7YBY 8ZBY FZ@Z ;Z IZ !Z Z !Z >Z !Z !Z \"Z :Z$[(YGZ:Y)[ NZ-Z MZ.Z N[/[ N[/[ NZ![9\\#[2YFZ)Z(Z#Z(Z" + "$Z(Z$Y'Y C[I[ ;Z5\\ JYCY 4X=^ @X=] @Y=] ?Y>^ @X=^ @X=^%X=l@\\\"_?W A]@\\ @]@\\ @^A\\ @^A\\ 1Y Y NX Y -w DXNY" + "C] A^C^ ?^C^ A^B] @^C^ ?^C^ 2\\ 1^C_ FY0X HY0X GX0X GX0Y IY0Y HcB] FY0Y ;X=X=Y(Y#Y'YJV;VIX&X.VJY(Y#Y 9W 4Z1" + "Z DZ =S4U 2y 9j I~l#{ BZ9Z EX.X :d 7Z8Y!Y *~R #Z +XCX C~d'YBT DX?XBW 7\\ @X $Y FY " + "/ZNVNZ *X ,X :Z/Z 4Z AZ #Z :XBZ ?o 9ZGc MZ =Z8[ HY0\\ 6Z \"[ Li >j C\\\"X8aGVBW$ZBZ CZ2Z LZ \"Z#Z!" + "Z MZ 0[ LZ(Z :Z \"Z 4ZJ] .Z 2YHXY 9Z(Z NZ2Z,Z\\ @^:T C\\?b D\\=\\ 5Y 0\\>a Ga?\\ 2Y NX 0X<\\ /Y Fa@\\MX@[(b@\\ B]?\\ Da?] D\\?a ;b 1Z6" + "S 5Y .Y1Y IZ1Z&Y;_;X![=Z DY1Y #[ 2Y X Y `>` I\\B[KX IY:\\ LY ?ZDa ?\\7R Cb?\\ F[3Y 5_ 9Y1Y\"Z Y!]IYJ] L" + "~Y!Y@X@Y F\\?\\ D^Ai\"~W LY.Z CZBZ .\\ 1Z 1Z LZ.[=Z>[(Z(Z*Z;Z<[0[ N[%\\ XAU V ?W3X CW3X 8X>W #Y /Z" + "BZ 9YAY 8ZBZ 9YAY 8ZBZ 9YAY FZ@Z ;Z IZ !Z Z !Z >Z !Z !Z \"Z :Z$[(YFZ;Y)Z MZ-Z MZ/[ MZ/[ N[/Z M[![;\\\"[3YE[*" + "Z(Z#Z(Z$Z(Z$Y'Y B[JZ :Z4[ JYCX 3U8\\ @U8\\ AV8\\ @U7\\ AU7[ @U8\\%U8h=\\$]9T B\\=\\ B\\=\\ B\\=\\ B\\<[ 2Y Y " + "NX Y .x Da?\\ C]?] A]?] B\\?] B]?] A]?] 3\\ 2]?] FY0X HY0X GX0X GX0Y IZ1Y Ha?] GY1Z ~d W5T 2{ 9i H~k$} DZ7Z FX.X :d 7Z9Z!X )~R #Z 0~d&XBT DX?XCX 6\\ " + " =Y EY 0ZMVMZ +X ,X :Z/Z 4Z B[ %\\ :XBZ ?q ;YHg Z \\ 0Z 6Y.Z CYAZ -\\ 2Z 1Z LZ.[=Z=[)Z(Z*Z;ZW>X@T ;a #X #X =W 6Y1Y GmEW KV .X;X @W@W @W3W BW4X 6W?X #Y /Y@Y :" + "ZAY 8Y@Y 9YAZ 9Y@Y 9YAZ GZ@Z ;Z IZ !Z Z !Z >Z !Z !Z \"Z :Z#Z(YFZ;Y)Z M[/[ MZ/[ MZ/Z LZ/Z M[ [=\\!Z3YD[*Z(Z#Z" + "(Z$Z(Z$Y'Y AZKZ 9Z4[ JYDY 3R3[ AR3[ BS3Z @S4[ AS4[ AR3[&R3e:[&]6R C\\:[ D\\:[ D\\:[ D\\:[ 3Y Y NX Y /_B] E_<" + "[ C[;[ B\\<\\ C\\<\\ C[;\\ C\\<\\ 3\\ 3\\<\\ FY0X HY0X GX0X GX0Y HY2Z H`<[ FY2Y ;X~d#Z6U 3} :h G~k%~P EY5Y FX.X ;ZNY 6Y9Z!X *~R \"Z 0~d&YCT CXAXBW 5] " + " >Y EY 2ZKVKZ -X ,X :Z/Z 4Z BZ &] :XAZ ?s =YJk #[ ;[=[ FZ1\\ 6Z \"[ #j L~d Ki J\\!X:hKVAW%Y@Y CZ5\\ L" + "[ \"Z#Z!Z MZ 0Z KZ(Z :Z \"Z 4ZL] ,Z 2YGX=XGY(YEZ=Z*[ M[\"Z4['Z LZ&Z4[ F` BY 9Z(Z MZ4Z*Z=XEW=Z Jd .ZLZ #\\ .X" + " LX BY JQ1[ D_:[ B\\ ([9_ F[7Z 6Y 1[:_ G^9Z 3Y NX 0X>\\ -Y F^;b;Z)_:Z D[:\\ F_:[ G[9^ ;_ /Y EY .Y1Y " + "HY2Z$Y=a=Y NZ@[ BY3Z %[ 0Y X Y \"eCd L[>YLX HY>^ IY AY=] @Z &_:Z DY4Y 5a :Y1Y\"Z Z$\\GYG\\ EY9Y IY@X@Y G" + "Z9[ G\\;[ 0Y 5Y.Z DZ@Y ,\\ 3Z 1Z LZ.ZUDX!T\"XW>X@U :] !X $X Z !Z !Z \"Z :Z#Z(YEZ~d&^7U 4~ 9f E~i%~R GY4Y FX.X ;ZNZ 7Y9Y!X )~R \"Z NW?W BYCT CYBXCX 6_ ?Y EZ 5ZI" + "VIZ /X ,X :Z.Y 4Z C[ )_ :YAZ ?t >YKn %Z 9\\A\\ EZ1\\ 6Z \"[ &j I~d Hi N\\ W:jLVAW&Z@Z DZ8^ KZ !Z#[\"Z " + " MZ 0Z KZ(Z :Z \"Z 4ZM] +Z 2YGY?XFY(YEZ=Z*Z L[\"Z4['Z LZ&Z4[ Fc EY 9Z(Z MZ5Z)Z>XDW=Z Ic .[NZ #\\ -X KX CY " + " )Z D^8[ D\\ '[8^ FZ5Z 7Y 2[8^ G]8Z 3Y NX 0X?[ +Y F]9`9Y)^9Z E[8[ F^8Z GZ8^ ;^ .Y EY .Y1Y GY3Y#Y=WNX=Y M" + "ZAZ AY3Y %[ /Y X Y #gEf N[W>W?U 7W <~d BX ;W 6Y1Y GmEW KV -X=X ?YBY BW4W AW5X 5W@W !Y 0Y?Z ;Y?Y :Z@Z ;Y?Y :Z?Y ;Y" + "?Y HZ?Z <[ IZ !Z Z !Z >Z !Z !Z \"Z :Z#Z(YEZY D~P JZ !Z#[\"~Q Dy Z K~] :Z \"Z 4ZN] *Z 2YFX?XF" + "Y(YDZ>Z*Z L[\"Z5\\([ LZ&Z5\\ Eg JY 9Z(Z MZ5Z)Z>XDX>Z Ib ,f $\\ ,X KX CY (Y D]6Z D[ '[7^ GZ4Z 7Y 2Z6] " + "G]7Z 4Y NX 0X@[ *Y F]8^8Z*]7Z FZ6[ G]6Z I[7] ;] -X DY .Y1Y GY3Y#Y=WNX=X L[CZ ?Y4Y &[ .X NX Y $iGh Z:XNX" + " GYHg HY CY8\\ CY $]7Z DY6Y 4b ;Y1Y#Z MZ&[EYE[ FY9Y IY@X@Y HZ7[ I[7[ 2Y 5~V DY>Y +\\ 5Z 2Z KZ/[W>W?U K~d CX ;X " + " 6Y1Y FlEW KV -Y?Y ?ZCZ CW5X AW5W 5XAX !Y 0Y>Y Y Y ;Y?Z JZ>~Q3[ I~Q G~Q F~Q G~Q 5Z !Z !Z " + "\"Z :Z#Z(YDZ=Y*[ LZ/Z L[0Z L[0Z LZ0[ LZ L[C\\ N[5X@Z*Z(Z#Z(Z$Z(Z$Y'Y ?e 7Z3[ KYDY @Y Y !Z Y Y Y 4_4Y)[ %Z3" + "Y GZ3Y FZ4Y FZ4Y 4Y Y NX Y 1[8Z F\\7Z F[7[ EZ6[ G[6[ G[6Z EZ6[ Y D~ IZ !Z#[\"~Q Dy![ K~] :Z \"Z 4h )Z 2YFX@YFY(YDZ>Z*Z KZ\"Z5\\([ LZ&Z6\\ Ck Y 9Z(Z LZ6Z(" + "Z?XDX?Z G` *d #[ +X KX CY 'Y E]6[ F[ &Z5] GY2Y 7Y 3Z4\\ G\\6Z 4Y NX 0XA[ )Y F\\7]6Y*\\5Y G[5Z G\\5Z I" + "Z5\\ ;] -X DY .Y1Y GZ5Z#Y>XMW>Y K[E[ ?Y5Y &[ .Y NX Y $XIZHZIY!Z:XNX GYHf GY DY6[ CY $\\5Y CX6Y 5c ;Y1Y#" + "Z MZ&[EYDZ FY9Y IY@X@Y IZ5Z IZ5Z 2Y 5~V EZ>Y *[ 5Z 2Z KZ/[Z EiKh 6X /XC^ BTDX U\"YA\\ 4ZCZ N~d &U>W?X>T K~d EY :W 5Y1Y EkEW KV ,YAY =ZCZ DW6X @W6" + "X 5W@W 'Z>Y Z =Y=Y ;Y>Z =Z>Y JZ>~Q3Z H~Q G~Q F~Q G~Q 5Z !Z !Z \"Z :Z#[)YDZ=Y*[ LZ/Z KZ0Z L[1[ LZ0[ L" + "Z K[E\\ M[6Y@Z*Z(Z#Z(Z$Z(Z$Y'Y >d 7Z2Z KYDY @Y Y Y NY Y !Y 4^3Z*Z $Z3Z HZ3Z HZ3Z HZ2Y 5Y Y NX Y 2[6Z G" + "\\6Y FZ5[ G[5Z GZ5[ GZ5[ G[5Z =[:_ HY0X HY0X GX0X GX0Y GZ5Y F\\5Z GY5Z Z6Y &[ .Y NX Y %WEYJYEX#Z8a GYHe FY DX4[ DY $\\5Y CY8Z 5d Y*Z KZ/Z KZ0Z L[1[ L[1[ LZ J[G\\ L[7Y?Z*Z(Z#Z(Z$Z(Z$" + "Y'Y >c 6Z2Z KYDY ?Y X NX NY Y Y 4\\1Y+[ %Z1Y HY1Y HY1Y HY1Y 5Y Y NX Y 3[5Z G[5Z HZ3Z GZ4[ HZ4Z HZ3Z GZ" + "4[ >Z9` IY0X HY0X GX0X GX0Y FY6Z F\\4Z GY6Y ;W9X9W-X JX,WD[I\\DW,W1[DW-X JX =X 1Y6Z <~d'RKY:U 5~U J" + "~T$~g'~X KY1X GX.X Z ?y DgF` *Z 2k >Z4^ 6Z \"[ 1j >~d =i -[ LW=\\C_?W)YZ=Z =YZ=Z =YZ=Z LZ=~Q3Z H~Q G~Q F~Q G~Q" + " 5Z !Z !Z \"Z Ew5[)YCZ>Y*Z KZ/Z KZ0Z KZ1[ L[1Z KZ I[I\\ K[8Y>[+Z(Z#Z(Z$Z(Z$Y'Y =a 5Z2Z KYDY ?Y Y X MX Y Y" + " 4\\1Y+Z $Y0Y IZ1Y IZ1Y IZ0X 5Y Y NX Y 3Z3Y GZ3Y HZ3Z HZ2Z IZ2Z IZ3Z GZ3Z >Z:a IY0X HY0X GX0X GX0Y FZ7Y E[" + "3Z GY6Y ;W9X9W-W HW,WC[K\\CW,W2[CW-W HW =X 1Z7Z <~d NX:U 5~V M~X%~e&~Y LX0Y HX.X =ZJY 6Y=Z W " + " NZ 3Y X@X ?]IT ?hCW 7h2X ;Y CY 7TAVAT 1X .X 8Z.Y 4Z G\\ 6g 5X=Z ?X?a EeB^ +Z /f ;[5" + "^ 4i ;~d :i 1[ LWr *Y " + "9Z(Z KZ8Z'Z@XBX@Y D\\ &` $\\ )X JX DY &X E[2Z HZ %Z3\\ IZ/X 8Y 4Z2[ GZ3Y 4Y NX 0XE\\ &Y FZ4[5Y*[4Z IZ" + "2Z H[2Y KY2[ ;[ +X DY .Y1Y FZ7Z!Y?WLX?X H[IZ ;Y7Y '[ ,Y NX NY *Q NV@WLW?U#Z8` FYHd .^FY EX2[ DX $[3Y CX8Y" + " 5YMY [/[IuI[.\\ 4X 4\\ =X =\\$\\" + " =X MZAU -Z &X8Y G~W 6X 0W<\\ FUEX MT iNW 8[D[ K~d &T=WE\\QZZeBX] ,Z 1j <[7_ 7i 8~d 7i 5[ KW=Z=" + "\\?W*Y:Y F{ FZ !Z\"Z\"~Q Dy![1j&~] :Z \"Z 4e &Z 2YDXCXDY(YBZ@Z*Z KZ\"Z[/[IuI[/\\ 3X 3\\ >X >\\\"\\ >X MZAU -Z 'X6X 5c " + "%X 1X;\\ GUEX MT NgMW 9[D[ J~d &T=m;T K~d In 4TA[ 4Y1Y BhEW 3Z DX )i 5[D[ IX9W5Z3W8WFj?TA[BX5Z KY" + ";Z @Z;Z ?Y:Y @Z;Z ?Z;Y ?Y;Z NZ<~Q3Z H~Q G~Q F~Q G~Q 5Z !Z !Z \"Z Ew5[)YAY?Y*Z KZ/Z KZ1[ KZ1[ L[1Z KZ G[M\\ IZ8" + "X<[+Z(Z#Z(Z$Z(Z$Y'Y <_ 4Z2Z KYD[ @X NX Y NY X NX 3Z/Y-Z $Z/Y KZ/Y KZ/Y KZ/Y 6Y Y NX Y 4Z2Z HZ3Y IZ1Z I" + "Z1Z JY1Z JZ1Z IZ1Z @Z;XNZ JY0X HY0X GX0X GX0Y EY8Y D[2Z GY8Y ;X9X8W.W HW-W@hAW-X4[@W.W:[:W =X 0Z9Z I" + "[ 7YY ~m 4Z 3Y W?X >g =cAW?]'[K\\5Y ;Y CZ %V M" + "X /X 7Y-Z 5Z H[ 4l ;XZ>Z.[IuI[0\\ 2X 2\\ ?X ?\\ \\ ?X MY@U 8y ;X6X 4a $X 1X9[ HUEX MT MeLW :[D[ I~d &T=l:T " + "K~d Io 5m 3Y1Y AgEW 3Z Nl 2g 3[D[%lDX5Z>mDXFk@mAW5[ LZ:Y @Y:Z ?Y:Y @Z:Y ?Y:Z AZ:Y NZ<~Q3Z H~Q G~Q F~Q G" + "~Q 5Z !Z !Z \"Z Ew5[)YAZ@Y*Z KZ/Z KZ1[ KZ1[ L[1Z K[ Gh HZ9X;[+Z(Z#Z(Z$Z(Z$Y'Y ;] 3Z2Z KYC[ AX NX Y NY Y X" + " 3Y.Y-Z $Y.Y KY.Y KY.Y KY.Y 6Y Y NX Y 4Z1Y HY2Y IZ1Z IY0Z KZ0Z KZ1Z IY0Z @Y;XMZ JY0X HY0X GX0X GX0Y DY9Y D" + "Z0Y GY9Z ;W8X8W.W HW-W?f?W.W4[?W.W:[:W =X 0Z9Y HZ 5X_@XAa*[I\\6Y ;Y CZ %V MX /X 7Y-Z 5Z I[ 3n >X;Z ] G`9\\ .Z 4s @[9` " + " =i /i ;Z IV=Y9Z>V+Z:Z G~P JZ !Z\"Z\"~Q Dy!Z1l'~] :Z \"Z 4g (Z 2YDYEXCY(YAZAZ*Z KZ\"}$Z K['z 5r /Y 9Z(Z JZ;Z" + "$ZAW@WAZ F_ %\\ $[ &X IX EY &Y FZ0Y IZ %Y/Z IY.Y 9Y 4Y0Z GY1Y 5Y NX 0XH[ \"Y FY3Z3Y+Z2Y JZ0Z IZ0Y MY0" + "Z ;Z *Z FY .Y1Y DY9Y MYAWJXAY F[MZ 8Z:Y )[ +Z MX N[ 7g1U U<^;U&Z6^ EYHj 9gJY FX/Y CY &Z2Y BYY1Y%Z" + " J[*ZBYBZ HY9Y IY@X@Y KY0Z MY/Y 4Y 6~W GZ:Z ,[ 6Z 2Z KZ/Z;Z;Z*Z(Z([>Z?[.ZHuI[1\\ 1X 1\\ @X @\\ M\\ @X NZ" + "@U 8y ;W4X 5` #X 1X8Z HUEX MT LbJW ;ZC[ H~d &T=j8U L~d Io 5l 2Y1Y @fEW 3Z Nl 0c 0[CZ&lDW5[>mEXE\\N^" + "AlAX6\\ LZ:Z AY9Y @Z:Z AY9Y @Z:Z AY9Z!Z;~Q3Z H~Q G~Q F~Q G~Q 5Z !Z !Z \"Z Ew5[)Y@ZAY*Z KZ/Z KZ1[ KZ1[ L[1Z K" + "[ Ff GZ:X:[+Z(Z#Z(Z$Z(Z$Y'Y :\\ 3Z2Z KYC\\ BY X NX NY Y X 3Y-X-Y #Y-X KY-X KY-X KY-X 6Y Y NX Y 5Z0Y HY" + "2Y IY/Y JZ0Z KZ0Z KY/Z KZ/Y AZ;WKY JY0X HY0X GX0X GX0Y DY:Z DZ0Y FY:Y :WK~KW.WK}KW-W>d>W.W5[>W.W:[:W =X /" + "Y:Z IZ 4Y=T 6~[%~b'~_%~\\ NY/X HX.X >ZHY 6Y?Y N~m 4Z 3Y !X@X ;l @[>WBe,ZG\\7Y ;Y" + " CZ %V ;~c LX 7Y-Z 5Z J\\ 2n @Y;Z N\\ G`8\\ /Z 5u A\\V+Y8Y G~R LZ !Z\"Z\"~Q" + " Dy![2l'~] :Z \"Z 4h )Z 2YCXEXCY(Y@ZBZ*Z KZ\"|#Z K['x 0q 1Y 9Z(Z IZY1Y%Z IZ*YAYBZ HY9Y IY@X@Y KY/Y MY/Y 4Y 6~W GY9Z " + "-[ 5Z 2[ LZ/Z;Z;Z*Z(Z'[?Z?[.[IuI[2~n BX B~n AX A~m AX NZ@U 8y dEW 3Z Nl ._ ,ZCZ'lEX6\\>mEWDVCZBkAX6] LY8Y BZ9Z AY8Y BZ9Z AY8Y BZ9Z!Z;~Q3Z H~Q " + "G~Q F~Q G~Q 5Z !Z !Z \"Z Ew5[)Y@ZAY*Z KZ/Z KZ1[ KZ1[ L[1Z KZ Ee FZ;Y:[+Z(Z#Z(Z$Z(Z$Y'Y :[ 2Z2Z KYB\\ CY X NX" + " NY Y Y 4Y-Y.Y #Y-X KY-X KY-Y LY-Y 7Y Y NX Y 5Z0Z IY2Y JZ/Z KZ/Y KY/Z KY/Z KZ/Y#~d$ZX /Z;Z JZ 2X>U 6~\\'~c&~^$~Z MY/X HX.X >YGZ 7Z@Y " + "N~m 4Z 3Y !X@X :n 'WBg.ZE\\8X :Y CZ %V <~e NX 6Y-Y 4Z K\\ #a AX:Z M\\ H_6[ 0Z" + " 6aI` A]?c ?f $f ?Z IW>Y7Y>V,Z8Z HZ8` MZ !Z\"Z\"Z MZ 1[2l'Z(Z :Z \"Z 4ZN] *Z 2YCXFYCY(Y@ZBZ*Z KZ\"{\"Z " + "K['v +o 2Y 9Z(Z IZq:X !U:[9U&Y5] DY?d =jLX FY/Z C[ " + ")Y1Y AX=Z 6ZIY >Y1Y%Z IZ*YAYAY HY9Y IY@X@Y KY/Y NZ/Z 5Y 5Y-Y HZ8Y .[ 4Z 1Z LZ/Z;Z;Z*Z(Z'[?Z@[-[ L[3~o BX B~o BX" + " B~o BX NZ@U 8y mFXDS?YBi?W5] CY 4Z8Y BY7Y BZ8Z CY7Y AY8Z CZ8Y!Y:Z Z !Z !Z \"Z Ew5[)Y?ZBY*Z KZ/Z KZ1[ KZ" + "1[ L[1Z KZ Dc E[=Y9[+Z(Z#Z(Z$Z(Z$Y'Y 9Z 2Z2Z KYB^ &i 0i 1i /i 0i 0i Ej-Y/Z $Z-Y MZ-Y MZ-Y LY-Y 7Y Y NX Y 5Y/" + "Z IY1X JZ/Z KZ/Z LY.Y LZ/Z KZ/Z$~d$Z=WIZ KY0X HY0X GX0X GX0Y CYX .Y;Y JZ 1Y?U 6~\\(~e'~]\"~X LX.X HX.X >YFY 7ZAZ N~m 4Z 3Y !W?X 9p +XCi0ZC\\9X " + " :Y CZ %V <~e NX 6Z.Y 4Z L\\ M^ CY:Z L[ H^4Z 0Z 7^A^ C_Ce ?c Mc @Z HW>X6Y>V,Y7Z HZ5^ NZ !Z\"" + "Z\"Z MZ 1[2l'Z(Z :Z \"Z 4ZM] +Z 2YBXGXBY(Y?ZCZ*Z KZ\"z![ LZ&w 'k 3Y 9Z(Z IZ=Z\"ZCX@XCZ Gc &Z &\\ $X HX FY " + " >q FY.Y JY $Y/Z JY,X 9Y 5Y.Y GY1Y 5Y NX 0XL\\ NY FY3Z3Y+Y1Y JY.Z JY/Z NY/Y ;Y (^ KY .Y1Y CY;Y KYCXIXCY " + "Bc 4Y\\IYMX FY/Z B\\ +Y1Y AY>Y 5ZIZ ?Y1Y%Z IZ*YAYAY HY9Y IY@X@Y KY/Y NZ" + "/Z 5Y 5Y-Y HZ8Z 0\\ 4Z 1Z LZ/Z;Z;Z*Z(Z&[@Z@[-[ L[4~p BX B~o BX B~p CX NY?U 8y mFWCQ;XAe>X6UNW CY 4Y7Z DZ7Y BZ8Z CY7Z CZ7" + "Y CY7Z#Z:Z Z !Z !Z \"Z :Z#[)Y?ZBY*Z KZ/Z KZ0Z KZ1[ L[1Z KZ Ca D[>Y8[+Z(Z#Z(Z$Z(Z$Y'Y 9Z 2Z3[ " + "KYA^ /q 9r 9q 7q 8q 9r Mq,Y/Z $Y,Y MY,Y MY,Y MZ-Y 7Y Y NX Y 5Y.Y IY1X JZ/Z KY.Z LY.Y LZ/Z KY.Z$~d$Y=XIZ KY0X" + " HY0X GX0X GX0Y CYX .YW-Y6Y HZ2\\ Z !Z\"Z\"Z MZ 1[2l'Z(Z :Z \"Z 4ZL] ,Z 2YBXGXBY(Y?Z" + "CZ*Z KZ\"x N[ LZ&x #f 3Y 9Z(Z HZ>Z\"ZCW>WCZ Hd &Z &[ #X HX FY At FY.Y JY $Y/Z JY,Y :Y 5Y.Y GY1Y 5Y NX" + " 0XM\\ MY FY3Y2Y+Y1Y JY.Z JY.Y Z/Y ;Y (b Y .Y1Y CY;Y KYCWHXCY Bb 3Y=Y *[ 6e JX Ke KzF^ !U9Y7T'Z4[ CY7] @[E" + "XNX GZ.Y Ai 9Y1Y AY>Y 5YHZ ?Y1Y&[ IZ+ZAYAY HY9Y IY@X@Y KY/Y NZ.Y 5Y 5Y-Y IZ6Y 0[ 3Z 1Z LZ/Z;Z;Z*Z(Z&\\AZA[,[ L[" + "4~p BX B~o BX C~q CX NY?U 8y Z !Z !Z \"Z :Z#[)Y>ZCY*Z K" + "Z/Z KZ0Z L[1[ L[1Z KZ B_ C[>X7[+Z(Z#Z(Z$Z(Z$Y'Y 9Z 2Z3[ KY@_ 5u XHZ KY0X HY0X GX0X GX0Y BY=Y BY.Y FY=Z 9WK~KW/WJ}JW.W:\\:W.W" + "9[:W/W9[9W >X .Z=Y JZ /X@U 6~^*~g&~Y N~V KX.Y IX.X ?ZFZ 7ZBY L~l 4Z 3Y \"X@X 3n /X" + "CZIZ2Z@\\W.Z6" + "Z IZ1[ Z !Z#[\"Z MZ 1[2l'Z(Z :Z \"Z 4ZK] -Z 2YBXHYBY(Y>ZDZ*Z KZ\"v L[ LZ&z !c 4Y 9Z(Z HZ>Z\"ZDX>XDY Ge 'Z '[ " + "\"X GX GY Dw FY.Y JY %Z/Z J~W :Y 5Y.Y GY1Y 5Y NX 0XN\\ LY FY3Y2Y+Y1Y JY.Z JY.Y Z/Y ;Y 'e $Y .Y1Y CZ=Z" + " KYDXGWDY @a 3Z>Y +[ 5d IX Ic L~d !U8X7T'Z4[ CY5\\ AZCa GY-Y @h 9Y1Y @X?Z 6ZGY ?Y1Y&[9X9Z+ZAYAZ IY9Y IY@X@Y " + "KY/Z Y-Y 5Y 5Y.Z IZ6Z 2[ 2Z 1Z M[/Z;Z<[*Z(Z%[AZB\\,[ LZ3~p BX B~o BX C~q CX NY?U 8y Z !Z !Z \"Z :Z#[)Y>ZCY*Z KZ/Z KZ0Z L[1[ L[1[ LZ A] B[?X6Z*Z(Z#Z(Z$Z(Z$Y'Y 9Z 2Z3[ KY?" + "_ 8w ?x ?w =w >w >w$~u/Y #~W M~W M~W M~W 7Y Y NX Y 6Z.Y IX0X JY-Y KY.Z MZ.Z MY-Y KY-Y$~d$Y?XFY KY0X HY0X GX0" + "X GX0Y BY>Z BY.Y EY>Y 8WK~KW/WJ}JW.W;]:W.W:[9W/W9[9W >X -Y>Z KZ .YAU 6~^*~g%~W L~T JX.Y IX.X ?YEZ 7Z" + "CZ L~k :y KY \"X@X 0m 1WCYEY3Y>\\=X 9Y BY %V <~e =l X 5Z.Y 4Z \\ E[ GY8Z JZ I]" + "2Z 2Z 8[7[ BqMZ ?^ C^ @Y GV=W4X>V-Y5Z IZ0[!Z !Z#[\"Z MZ 1[2l'Z(Z :Z \"Z 4ZJ] .Z 2YAXIXAY(Y=YDZ*Z L[\"s" + " I[ LZ&[Cc Na 5Y 9Z(Z HZ?Z YDX>XEZ Hg (Z (\\ \"X GX GY Fy FY.Y KZ %Z/Z J~W :Y 5Y.Y GY1Y 5Y NX 0e KY" + " FY3Y2Y+Y1Y KZ.Z JY.Y Y.Y ;Y &h (Y .Y1Y BY=Y IXDXGWDY ?_ 1Y?Z ,[ 4b GX Ga L~c T6V6T'Z4[ CY4\\ CZ@_ GY-Y >f " + "9Y1Y @Y@Y 5YFZ @Y1Y&Z8X9[,ZAYAZ IY9Y IY@X@Y KX.Z Y-Y 5Y 5Y.Z IY5Z 3[ 1Z 1Z M[/[WEY9T -X EY1Y 1WEW 3Z 6ZCZ 7X7" + "UKV HW*W KX6ULW CY 5Y5Z FZ5Z EY4Y FZ5Z EZ5Y EY5Z%Z9Z Z !Z !Z \"Z :Z#Z(Y=ZDY*[ LZ/Z KZ0Z L[0Z " + "LZ0[ LZ A] B[@X5Z*Z(Z#Z(Z$Z(Z$Y'Y 9Z 2Z4[ JY>` Y 8WK~KW/WJ}JW.W<_;W.W;[8W/W9[9W >X -Z?Z " + " LZ -YBU 5~^*~h%~U J~R IX.Y IX.X @ZDY 6YCZ LW 'y JY \"W?X ,j 3WCYCY4Y=\\>X 9Y CZ" + " %V <~e =l X 5Z.Y 4Z !\\ C[ IY7Z JZ I]2Z 3[ 9[5[ BoLZ ?a Ia @Y HW>X3W>V.Z4Y IZ/Z!Z !Z#[\"Z MZ 0" + "Z Z'Z(Z :Z \"Z 4ZI] /Z 2YAXIXAY(Y=ZEZ*Z L[\"o DZ LZ&Z<^ M_ 5Y 9Z(Z GZ@Z ZEX>XEZ I[MZ (Z )\\ !X GX GY " + "Gz FY.Y KZ %Y-Y J~W :Y 5Y.Y GY1Y 5Y NX 0c IY FY3Y2Y+Y1Y KZ.Z JY.Y Y.Y ;Y %j +Y .Y1Y BY=Y IYEXGXEY >] 0Y?Y ,[ " + "3` EX E_ L\\Cx NT6V6T'Z4Z BY2Z CY>^ GY-Y ;c 9Y1Y @YAZ 6ZEY @Y1Y&Z8X9[,ZAYAZ IY9Y IY@X@Y KX.Z Y-Y 5Y 5Y.Z JZ" + "4Y 4\\ 1Z 1[ NZ.[" + "WDX:U -X EY1Y 1WEW 3Z 5YBY 7W6UKV IX*W KW6UKW CY 6Z4Y FZ5Z FZ4Z GZ4Y EY4Z GZ4Y%Y8Z <[ IZ !Z " + " Z !Z >Z !Z !Z \"Z :Z#Z(Y=ZDY*[ LZ/Z L[0Z L[0Z LZ0[ LZ B_ BZAY5Z*Z(Z#Z(Z$Z(Z$Y'Y 9Z 2Z5\\ JY=` ?{ B{ Bz @z B{ " + "B{'~x/Y #~W M~W M~W M~W 7Y Y NX Y 6Z.Y IX0X JY-Y LZ-Y MZ.Z MY-Y KY-Y$~d$Y@WDY KY0X HY0X GX0X GX0Y AY@Z AY.Y " + "DY@Z 8WK~KW/WJ}JW.W=aX ,Y?Y LZ +XBU 6~_+~i%~U I~P HX.Y IX.X @ZDZ 7YCY KX " + " (y JY \"W?W (h 5XCXAX5Z<\\@Y 9Y CZ $T ;~e =l X 5Z/Z 4Z \"\\ AZ IX6Z JZ I\\1[ 4Z 8Z3Z AmKZ" + " ?d d AZ HW>X3W>V.Z4Z JZ.Z\"[ \"Z#[\"Z MZ 0Z Z'Z(Z :Z \"Z 4ZH] 0Z 2YAYKX@Y(YWCX;U -X EY1Y 1WEW 3Z Is 0YAX 8W6UJV IW)W" + " LX7UJW CY 6Z4Z GY3Y FZ4Z GY3Y FZ4Z GY3Z'Z8Z <[ IZ !Z Z !Z >Z !Z !Z \"Z :Z#Z(Yc=W.W=[6W/X:[:X >X ,Y@Z M[ " + "+YCT 5~`,~i$~S H~P HX.Y IX.X @YCZ 7ZDY KX )y HX #X@X (TNc 6WCX@X5Y:\\AX 8Y CZ :~e" + " =l !X 4Z/Z 4Z #\\ @[ KY6Z IZ I[0Z 4Z 9Z2[ @jJZ ?f %g AZ HW>X3W>V.Y2Y JZ.Z\"[ \"Z#Z!Z MZ 0Z Z'Z(Z" + " :Z \"Z 4ZG] 1Z 2Y@XKX@Y(YWBXZ !Z !Z \"Z :Z#Z(YW.W>[5W.W:[:W =W +ZAY LZ *YDU 5~`,~i#~Q F} GX.Y IX.X AZBY 7ZEZ KX " + ")y HX 6~e 9TJ_ 7XCX?X6Y9\\BX 8Y CZ KX Nl !X 4Z/Z 4Z $\\ >Z LY5Z IZ I[0Z 5Z 8Z1Z >fHY =h " + " +i @Z HW>X3W?W/Z2Z KZ.[#[ \"Z#Z!Z MZ 0Z Z'Z(Z :Z \"Z 4ZF] 2Z 2Y@XLY@Y(Y;ZGZ*[ MZ!Z /Z M[&Z7[ K\\ 6Y 9Z(Z FZ" + "BZ MYFXY FY.Y KZ %Y-Y K~X :Y 5Y.Y GY1Y 5Y NX 0e KY FY3Y2Y+Y1Y KZ-Y JY.Y" + " Y-X ;Y !m 2Y .Y1Y AZAZ GYGXEXGY >] .ZBY -[ 1e JX Ke LU4k IU8Y8T'Y2X AY0Y EX:[ FY-Z Ah 9Y1Y >XCZ 6YBY AY1Y&" + "Z8X8Z,Y@YAZ IY9Y IY@X@Y LY-Y Y-Y 5Y 5Z/Y JZ2Z 8[ .Z 0[!Z,[=Z=[)Z(Z\"]FZG]'Z M[1] 1X 1\\ @X @\\ L\\ AX DX 4" + "Z?U -Z (X4X H~W ;\\;W GTDX\"U s A[D[ 6X %T>WBXZ !Z !Z \"Z :Z$[(Y;ZFY)Z M[/[ MZ/[ MZ/Z M[/Z M[ Ee EZC" + "X3[*Z(Z#Z(Z$Z(Z$Y(Z 9Z 2Z8^ IY9` Fb=Y Eb=Y Eb=X Cb>Y Eb=Y Eb=Y*b=~V/Y #~W M~W M~W M~W 7Y Y NX Y 6Y-Z JX0X JY" + "-Y LZ-Y MY-Z MY-Y LZ-Y CZCXBY KY0X HY0X GX0X GX0Y @YBZ @Y.Y CYBY 6W8X8W.W HW-W@g@X.W?[4W.W:[:W =W *YBZ " + " MZ (XDU 5~`,~i\"~ D{ FX.Y IX.X AZBZ 7YEY IX +y GX 6~e 9TG] 8WBW>X6Y8\\DY 8Y CZ " + " KX Nl !X 4Z/Z 4Z %\\ =Z LX4Z IZ I[0Z 5Z 9Z0Z X3W?W/~S KZ-Z\"Z \"Z#Z!Z MZ 0[!Z" + "'Z(Z :Z \"Z 4ZE] 3Z 2Y?XMX?Y(Y;ZGZ)Z MZ!Z /[ N[&Z6[ K\\ 7Y 9Z(Z FZCZ LZGX^ .YCZ ." + "[ )_ KX L_ ES/e FU8Z9T'Z3X AY0Y FY:[ FY-Z Cj 9Y1Y >XCY 6ZBZ BY1Y&Z8X9[,Y@YAZ IY9Y IY@X@Y LY-Y Y-Y 5Y 5Z/Y J" + "Z2Z 9\\ .Z /Z!Z,\\>Z>[(Z(Z!]GZH^'[ N[0\\ 1X 2\\ ?X ?[ M\\ @X DX 4Z?U -Z 'W4W G~W :]>X GTDY#U s @[D[ 7" + "X %U?WAX>U ,X EY1Y 1WEW \"s 3ZC[ 9X7UHV KW(W MX7UHW CY 7~S J~S H~S I~S I~S I~S)} ;Z IZ !Z Z" + " !Z >Z !Z !Z \"Z :Z$[(Y;ZFY)Z MZ-Z MZ/[ N[/[ N[/Z MZ Eg F[EX2[*Z(Z#Z(Z$Z(Z$Y(Z 9Z 2Z9^ HY7_ G]8Y F^8Y F^8X D]8" + "Y E]8Y F^8Y+^8~V/Y #~W M~W M~W M~W 7Y Y NX Y 6Y-Z JX0X JY-Y LZ-Y MY-Z MY-Y LZ-Y BYDXAY KY0X HY0X GX0X GX0Y" + " @ZCY ?Y.Y CYBY 5W9X8W.W HW-WAiAW,WA[3W.W9Y9W >X *ZCZ 6~d IYET 4~`,~i!| By EX.Y IX.X AYAZ 7ZFY IX " + " Z 3X 6~e 9TF\\ 9WBX=W7Z7\\EX 7Y CZ KX Nl \"X 3Z/Z 4Z &\\ ;Z M~Z %Z I[0Z 6[ 9Z/" + "Y 8ZCZ 8i 6~d 5i ;Z HW>X3W?W0~T KZ-Z\"Z \"Z$[!Z MZ 0[!Z'Z(Z :Z \"Z 4ZD] 4Z 2Y?XMX?Y(Y:ZHZ)Z N[!Z /[ NZ%Z6[" + " J[ 7Y 9Z(Y DZDZ LZGW:WGZ K[GZ +Z -\\ LX EX IY L\\6Y FY.Y KZ %Y-Y K~W 9Y 5Y.Y GY1Y 5Y NX 0XM\\ MY " + "FY3Y2Y+Y1Y KZ.Z JY.Y Y-X ;Y Ji 4Y .Y1Y @YAY FYGWDXGX >` /YCY .[ $\\ LX M\\ AR+` CT9[:U'Z3X AY0Y FY9Z FY-Z " + "D` .Y1Y >YEZ 6YAZ BY1Y&Z8X9[,ZAYAZ IY9Y IY@X@Y LY.Z Y-Y 5Y 5Z/Y KZ1Z 9[ -Z /Z\"[+[>Z>[(Z(Z ^IZJ_&[ NZ.\\ 2X 3" + "\\ >X >[ \\ ?X DX 4Z?U -Z 'X6X G~W 9^@X GUDY$T Ns ?[CZ 8X %U?WAY?U ,X EY1Y 1WEW \"s 4" + "ZCZ 7W7UGV LX)X MW7UGW CY 8~T J~T I~S J~T I~T K~T*~ ;Z IZ !Z Z !Z >Z !Z !Z \"Z :Z$[(Y:ZGY)[ NZ-Z N[.Z N[/[ N" + "[/[ NZ Fi G[FX1Z)Z(Z#Z(Z$Z(Z$Z)Z 9Z 2ZX )YCY 5~d IYFU 4~`,~i!{ @x EX.Y IX.X AY@Y 7ZGZ IX Z 3X 6~e 9TD[ ;XBX=X8" + "Z6\\GY 7Y CY JX Nl \"X 2Y/Z 4Z '\\ :Z M~Z %Z I[0Z 6Z 8Z/Z \"Z 5i 9~d 8i 8Z HW>X3W?W0~U LZ-Z\"[ " + "#Z$[!Z MZ /Z!Z'Z(Z :Z \"Z 4ZC] 5Z 2Y?XNY?Y(Y:ZHZ)[ [!Z .Z NZ%Z5[ K[ 7Y 9Z(Y DZDY KZHX:XHY K[EZ ,Z .\\ KX EX" + " IY LZ4Y FY.Y KZ %Z.Y KZ X DX 4Z?U -Z 'X6X G~W " + "8^BX FUDY%U Ns =ZCZ 9X $U@W@X?T +X EY1Y 1WEW \"s 5ZCZ 7W7UFV LW(W MX8UFW CY 8~U K~T J~U K~" + "T J~U K~T*~ ;[ JZ !Z Z !Z >Z !Z !Z \"Z :Z$Z'Y9YGY)[ [-[ [.Z N[.Z NZ.[ NZ G\\L[ GZGX0Z)Z(Z#Z(Z$Z(Y#Z)Z 9Z 2~ " + "GY4] J[4Y G[4Y G[4X EZ4Y FZ4Y G[4Y,[4X 1Y #Y Y Y Y 9Y Y NX Y 6Y-Z JX0X JY-Y LZ-Y MZ.Z MY-Y KY-Y BYEW?Y" + " KY0X HY0X GX0X GX0Y ?YDY >Y.Y BYDY 4W9X9W-X JX,WD\\J[CW,WC[2W-X JX >X )YDZ 5~d HXFU 4~_+~i z @w DX.Y" + " IX.X BZ@Y 6YGZ IY Y @~e 9TCZ ;WAX=X8Y4\\HX 6Y CY JX Mj !X 2Y/Y 3Z (\\ 9Z" + " M~Z %Z I[0Z 6Z 8Z/Z \"Z 2i <~d ;i 5Z HW>X3W@W/~U LZ-[#[ #Z$Z Z MZ /Z!Z'Z(Z :Z \"Z 4ZB] 6Z 2Y>a>Y(Y9ZIZ)[ " + "Z Z .Z [%Z4Z JZ 7Y 9Z)Z DZEZ JYHX:XIZ KZD[ -Z /\\ JX EX IY MZ3Y FY.Y JY %Z/Z JY Z !Z !Z \"Z :Z%['Y9ZHY(Z [-[ Z" + "-[ Z-Z [-Z [ H\\J[ HZHY1[)Z(Z#Z(Z$Z(Y#Z)Z 9Z 2} FY2\\ KZ3Y GZ3Y GY3Y FZ3Y GZ3Y GZ3Y,Z3X 1Y #Y Y Y Y 9Y Y " + "NX Y 6Y-Z JX0X JY-Y KY.Z MZ.Z MY-Y KY-Y BYFX?Y KY0X HY0X GX0X GX0Y >YEY >Y.Y BYEZ 4X:X9W,W JW+WE\\H[EX,X" + "E[1W,W JW =X )ZEY 4~d HYHU 2~^+~i Nx >u CX.Y IX.X BY?Z 7ZHY GX Z A~e 9TCZ ~d >i 2Z GV>X3W@W0~V LZ-[\"Z " + "#Z%[ Z MZ /[\"Z'Z(Z :Z \"Z 4ZA] 7Z 2Y>a>Y(Y9ZIZ(Z Z Z .[![%Z4[ KZ 7Y 9Z)Z CZFZ JZIX:XIZ L[CZ -Z /[ IX DX J" + "Y MY2Y FY.Y JY %Z/Z JY Y CY1Y&Z9Y9Z+ZAYAY HY9Y IY@X@Y LZ/Y N" + "Y-Y 5Y 4Y0Z LZ.Y =[ *Z .[%Z(]AZA]'Z(Z L~\"[![+\\ 5X 6\\ JTEXET J[&\\ KSDXES $Y 3Y?U -Z &Y:Y F~W 5_GX DU" + "CZ9QAU DZCZ ;X $VAW?YBU +X EY1Y 1WEW DZCZ 6W7UEV NX)X MX8UEW DY 8~V L~V L~W M~V K~V M~V" + ",~P :Z JZ !Z Z !Z >Z !Z !Z \"Z :Z%['Y8ZIY(Z Z+Z Z-[![-[![-[![ I\\H[ I[JY0[(Y(Z#Z(Z$Z)Z#Z)Z 9Z 2| EY1\\ LY2Y " + "HZ2Y HZ3Y FY2Y GY2Y GY2Y-Z2X 1Y #Y Y Y Y 9Y Y NX Y 6Z.Y IX0X JY-Y KY.Z MZ.Z MY-Y KY.Z BYGX?Z KY1Y HY0X" + " GX0X GX0Y >YFZ >Y.Y AYFY 2W:X:X,W JW+XG\\F[FW+XF[1X,W JW =X (YEY 4~d GXHU 2kNRMk*tNq Mv Y 7ZIZ GY !Z A~e 9TBY `=Y(Y8ZJZ([\"[ Z " + ".[!Z$Z3Z KZ 7Y 9Z)Z CZGZ IZIW8WIZ M[AZ .Z 0\\ IX DX JY MY2Y FY.Y JY $Y/Z JY YEY CYIWBXIX @f 0YGZ 0[ LZ NX NY 'U>WMW?V&Z4Y AY/Y HY8Y" + " EZ.Y FZ %Y1Y Y CY1Y&[:Z:Z+ZAYAY HY9Y IY@X@Y LZ/Y NZ.Y 5Y 4Y0Y KZ.Z ?\\ *Z -['['\\AZB]&Z(Z K|![!Z)\\ 6" + "X 7\\ JVFXFV J[(\\ KUEXFU %Y 3Y?U -Z %YXCU *X EY1Y 1WEW" + " F[CZ 6X8UDV NW)X MX8UDW DY 8~W N~W L~W M~V L~W M~W-~P :[ KZ !Z Z !Z >Z !Z !Z \"Z :Z%['Y8ZIY([\"[+[" + "\"[,Z![-[!Z,[!Z I\\F[ J[KY/Z'Z)Z#Z)Z#Z)Z#Z)Z 9Z 2{ DY0[ MY1Y HY1Y HY2Y FY2Y HZ2Y HY1Y-Y2Y 1Z $Y Y Y Z :Y Y" + " NX Y 6Z.Y IX0X JZ.Y KY.Z MZ.Y LZ.Y KY.Z BYHX>Z KY1Y HY1Y GX0X GX0Y =YGY =Y.Y AYFY 2X;X:W+X LX*WH\\D[HX" + "*WG[0W+X LX =X (YFZ 4~d GYIU 2jLQLj*pNRNq Lt :q AX.Y IY0Y CZ>Y 6YIZ FX !Z A~e 9T" + "BZ >W?W;W8Z2\\MY 4Y DY JX 4X 1Z1Z 3Z +\\ 6Z M~Z %Z HZ0Z 8[ 7Y.Z #Z )i D~d Ci -Z GV=W4XAW/~W M" + "Z-[\"[ $Z&[ NZ MZ .Z\"Z'Z(Z :Z \"Z 4Z?] 9Z 2Y=_=Y(Y8ZJZ([\"[ Z -Z\"[$Z3[ L[ 8Y 9Z)Z BZHZ IZJX8XJY LZ@[ /Z 1\\" + " HX DX JY NY1Y FZ0Z JY $Y/Z JY YEY BXJXAWJY A[N[ 1YGY 0[ JY NX NY 'V@WLX@U$Y5[ BY/Y HX7X DZ.Y FY $Y1Y Z " + "/Z K_MZ BUC]BVBU A[D[ >X #VBW=XDU *X EY1Y 1WEW G[D[ 5W8UCV X*X LW8UCW EZ 8~W N~X M" + "~W N~X M~W N~X.~Q :[ KZ !Z Z !Z >Z !Z !Z \"Z :Z&[&Y7ZJY([\"[+[\"[,[\"Z+[#[+Z\"[ J\\D[ JZKX/['Z*[#[*Z#Z)Z#Z)Z" + " 9Z 2z CY/Z MY1Y HY2Z HY2Y GY1Y HY1Y HY1Y-Y2Z 2Z $Z !Z !Z !Z :Y Y NX Y 6Z.Y IX0X JZ/Z KY.Z LY.Y LZ/Z KY.Z " + " BYHW=Z KY1Y GX1Y GX1Y GX0Y =YHZ =Y/Z @YHY 1X;X;X*W LW)XJ\\B[IX*XI[0X*W LW Z 7ZJY EY !Z 1X@X &TAY ?X?W;W8Z1\\NX 3Y DY JX 5Y 0" + "Y1Z 3Z ,\\ 5Z M~Z %Z HZ0Z 8Z 6Y.Z #Z &i G~d Fi )X FV=X5XAW0~Y NZ-[!Z $Z&[ NZ MZ .[#Z'Z(Z :Z \"Z 4Z>] :Z 2" + "Y=_=Y(Y7ZKZ'Z#[ NZ -[#[$Z2[ M[ 8Y 9Z)Z BZHZ HYJX8XKZ M[?Z /Z 2\\ GX CX KY NY1Y FZ0Z JZ %Y/Z JZ =Y 4" + "Y0Z GY1Y 5Y NX 0XG\\ $Y FY3Y2Y+Y1Y JZ/Y IZ0Y MY/Y ;Y 8[ 8Y .Y1Y >ZGZ BYKXAXKY B[LZ 0YHY 1[ IY NX Z &VB" + "XKXBV$Y5[ BY/Y HX8Y CY/Z GY #Y1Y Z !Z !Z \"Z :Z&[&" + "Y7ZJY'[#Z)Z#[+[#[+[#[+[#[ K\\B[ K[MX.['Z*Z!Z*Z#Z)Z#Z)Z 9Z 2x AY.Z NY2Z HY2Z IY1Y GY1Y HY1Y HY2Z-X1Z 2Z $Z !Z !Z" + " !Z :Y Y NX Y 5Y/Z IX0X JZ/Z KZ/Y KY.Y LZ/Z KZ/Y AYIWW;W8Z0e 3Y EZ JX 5X /Z2Y 2Z -\\ 4Z M~Z %Z HZ0Z 8Z 6Z/Z $Z #j J~d Ii CW>X6Y" + "BX0~Y NZ-[![ %Z'\\ NZ MZ -Z#Z'Z(Z :Z \"Z 4Z=] ;Z 2Y<][ 0" + "Z 3\\ FX CX KY NY2Z FZ0Y IZ %Y/Z JZ =Y 4Y0Z GY1Y 5Y NX 0XF\\ %Y FY3Y2Y+Y1Y JZ/Y IZ0Y MY/Y ;Y 7Z 8Y" + " .Y2Z =YGY AYKW@XKY BZJZ 1YIY 1[ HY NX Y %WEYIYFW#Y5[ BY/Y HX8Y CY/Z GY #Y1Y ;XIY 6Y;Z EY1Y%Z:Z:Z*ZBYBZ " + "HY9Y IY@X@Y LZ/Y MY/Z 4Y 4Y2Y KZ,Z B[ 'Z +[+[#_FZF_$Z(Z Gt JZ$[%\\ 9X :\\ J\\IXI[ I\\/\\ K[HXI[ (Y 3Z@U -Z " + "%^F^ /Z X \"f >VBnCU >[D[ @X \"VCWZ !Z !Z \"Z :Z'[%Y6ZKY'[$[)[$[*[$[*[%[*[$[ K\\@[ Le.[&Z*Z!Z*Z\"Z*Z#Z*[ 9Z 2v " + "?Y.Z NY2Z HX1Z IY1Y GY1Y HY2Z HX1Z.Y1Z 1Y #Y Y Y Y :Y Y NX Y 5Y/Z IX0X IY/Z KZ/Y KY/Z KY/Z KZ/Y 7\\ 7ZKW" + ";Y IX1Y GX1Y GY2Y GY2Z YJX(XJY/X)X Y W;W7Y/c 2Y EY IX 5X /Z3Z 2Z .\\" + " 3Z M~Z &Z FY1Z 8[ 6Z/Z $Z i L~d Li @W>Y7YBW0Z*Y NZ-[![ %Z'[ MZ MZ -[$Z'Z(Z :Z \"Z 4Z<] Z !Z !Z \"Z :Z(\\%" + "Y6ZKY&[%[)\\&[)[%[)[%[)[%[ L\\>[ Ld.[&Z*Z!Z*Z\"Z+[\"Z+Z 8Z 2s YJY .X=X=Y(" + "X!X'YJWX.Y HY2Y CZW=X8ZC" + "W/Z*Z Z-Z N[ &Z(\\ MZ MZ -\\%Z'Z(Z :Z \"Z 4Z;] =Z 2Y<]Y 4Z2[ GY1Y 5Y NX 0XD\\ 'Y FY3Y2Y+Y1Y IY0Z IZ1Z MZ1Z ;Y 6Y" + " 8Y .Y2Z =ZIZ @XLX?WLY C[H[ 2YKZ 3[ EX NX Y $hFh\"Z7\\ BY0Y GX9Y BZ1Z FX \"Y1Y ;YKY 6Y9Y EY2Z%Z;[:Z*ZBYB" + "Y GY9Y IY@XAZ L[1Y LZ1Z 3Y 3Y3Y LZ*Z D[ &Z *[-[ aJZJa\"Z(Z Cl F\\'[\"\\ ;X <\\ F\\KXK\\ F\\3\\ H\\JXK\\ 'Y " + "2ZAU -Z 'z 1Z X Na ;V@jDV :ZCZ BX UDW;XIU 'X EY2Z 1WEW KZCZ 3X9U@V\"W*X LX9VAW H[ " + "8Z*Z\"Y)Y!Z*Z\"Z*Y!Z*Z\"Z*Z1Z3Z 8[ MZ !Z Z !Z >Z !Z !Z \"Z :Z(\\%Y5ZLY&[&['[&[([&[)\\'\\)[&[ L\\<[ Mc.[$Z,[!" + "[,[\"Z+Z!Z+Z 8Z 2n 7Y-Y NX1Z IY2[ IY2Z GY2Z HY2Z IY2[.Y2\\ 2Z $Z !Z !Z !Z ;Y Y NX Y 5Z0Y HX0X IZ1Z IY0Z KZ0" + "Y JZ1Z IZ1Z 7\\ 6YMX;Z IY3Z GY2Y GY2Y GY2Z ;YKY ;Z1Z >YJY .Y>X=X'Y#Y&XIU:UJY&YJU.X'Y#Y ;X &YJZ #Z JXLU" + " -dIQId%kKRKk El 2j >X.Y HY2Y CY;Z 7ZMZ BZ #Z 3X@X %TAX @WZ 2Y;[;Y(Y5ZMZ&\\([ LZ +['[\"Z0[ Z 7Y 8[,Z ?YKZ EYLX6XLY [:[ 2Z 5\\ DX BX LY NY3[ F[2Z HZ %Y1" + "[ IZ >Y 3Y2[ GY1Y 5Y NX 0XC\\ (Y FY3Y2Y+Y1Y IZ2Z H[2Z LY1Z ;Y 6Z 9Y .Y2Z Z !Z" + " !Z \"Z :Z)\\$Y5ZLY%[(\\'\\(['\\(['['['[(\\ M\\:[ Ma-[$Z,Z NZ,Z![,Z!Z,[ 8Z 2Z #Y-Y NX2[ IY2[ IY2Z GY3[ HX2[ IY2" + "[.Y2\\ 2Z $Z !Z !Z Y ;Y Y NX Y 5Z1Z HX0X IZ1Z IZ1Z JZ2Z JZ1Z IZ1Z 7\\ 6c:Z IY3Z GY3Z GY3Z GY3[ ;YKY ;[2Z =" + "YLY ,Y?X>Y&Y%Y%YIS8SJY$YJS.Y&Y%Y :X &ZKY #Z IYNU ,cISIb#jKRJi Cj 1i =X.Y GY4Y BY:Y 7ZMZ AZ " + " $[,P )W?X %TBY AXXMY DZDZ 2YLY 3[ DY X Y \"eCd NY8^ CY0Y GX:Y @Z2Z FX \"Y1Y :YMY 6Y7Y " + "FY2Z%[<\\a@V 7YBY CX NV LV BZ3Z 1WEW LYBY 2W8U?V#W+X KX9U" + "?W J[ 7Z(Y#Z)Z#Z(Z$Z)Z\"Y(Z$Z(Y2Z2Z 7\\\"P NZ !Z Z !Z >Z !Z !Z \"Z :Z*\\#Y4ZMY%\\)[%[)\\&[)\\'\\)\\'\\)[ M\\8" + "[ N`-[#Z,Z NZ,Z Z-[![-[ 8Z 2Z #Y-Y NX2[ IY2[ IY3[ GY3[ HY3[ HX2[.Y3^ 2Z $Z !Z !Z !Z ZMZ DZMW4WMZ![7Z 3Z 7\\ BX AX MY NY3" + "[ F\\4Z FZ &Z3\\ HZ ?Y 3Z4\\ GY1Y 5Y NX 0X@[ *Y FY3Y2Y+Y1Y HZ3Z H\\4Z KZ3[ ;Y 5Y 9Y -Y4[ ;YKY >YNX=WNY D[D[ " + "3YMY 3[ CY X Y !cAb MZ9^ CZ2Z GX:Y @Z3Z EX \"Y1Y :YMY 7Z7Y FZ4[$Z<\\Z !Z !Z \"Z :Z+]#Y4ZMY$[*\\%\\*[%\\+\\%\\+\\%\\+\\ N\\6[ N^-\\#[.[ N[.[ [.Z NZ-Z 7Z 2Z #Y-Y NY4\\ IY3" + "\\ IY3[ GY3[ HY4\\ HX3\\.Y3^ 2Z $Z !Z !Z !Z i i 2WZ4" + "Z EY #Y1Y 9XNZ 7Y6Z GZ4[$Z=]=['ZDYDZ FY9Y HZBXBZ K]5Z J[5[ 2Y 2Z7Y L[(Z H[ #Z '\\5[ F~ LZ(Z :Z :\\-\\ KW :X :" + "V >r >V/V @s #Z 2[CU -Z +[MeL[ 5Z X G\\ :W!V 3W@W 7V!W AZ4[ 1WEW LW@W 1W7s,X-" + "Y JX8t$\\ 7Z'Z%Z'Z$Z'Y%Z'Z$Z'Y%Z'Z4Z1Z 6\\&S NZ !Z Z !Z >Z !Z !Z \"Z :Z,]\"Y3ZNY$\\,\\#\\,\\$\\,\\$\\-\\$\\," + "\\ N\\4[ ]-\\![/Z LZ/[ N[/[ N[/[ 7Z 2Z #Y-Y NY4\\ HY5] IY4\\ GY4\\ HY4\\ HY4\\.Z5` 2Z $Z !Z !Z !Z =Y Y NX Y " + "3Z4Z GX0X H[5[ GZ4Z GZ4Z H[5[ GZ4[ 6\\ 5_9[ HZ5[ GZ5[ FY5[ FY5\\ :YNZ :\\4Z ;YNY )YAXAZ\"Z+Z!Z*Y Y*Z\"Z+Z 8" + "X $YMY %[ F^ '\\FSF\\ LcGRGc >f ,c :X.Y FZ7Y BY8Y 7e >[ %[1S -Y 'X@X ;Q:TCZ CX:X=X" + "5[.] /Y HY HX NZ GZ 'X +[8Z 0Z 4\\ 0[ 'Z M\\ CZ6[ 9Z 2[3[ '[ 0Y Y ?f f BX DW=\\C_J[.Z&Z\"Z0\\ " + "J\\(T'Z._ JZ MZ *])Z'Z(Z :Z \"Z 4Z6] BZ 2Y JY(Y3e#\\.\\ JZ )]/\\ NZ.[ NQ'[ 6Y 6[0[ =ZNZ CYNX4XNY!Z4[ 5Z 8[ @X" + " AX MY NY5] F]6Z DZ &Z5] G[ AY 2[8^ GY1Y 5Y NX 0X>[ ,Y FY3Y2Y+Y1Y H[6[ G]6Z IZ5\\ ;Y 6Y 8Y -Z6\\ ;Z" + "MZ =b=b EZ@Z 3d 5[ AY X Y L[:\\ IZ;` D[4Z FXZ5[ EY #Y1Y 9c 7Z5Y GZ5\\$[>^>['[EYE[ FY9Y HZBXCZ J]5Z " + "IZ5Z 1Y 1Y8Z LZ&Z J[ \"Z &\\8] E| KZ(Z :Z :]/] JU 9X 9T